Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,24 @@ Extensions can be used to:
- Generate extra information from the agent

To create an extension derive and implement the `AgentExtension` interface.

### Tool Call Argument Preprocessing

Extensions can preprocess tool call arguments before the tool runs. Override `modifyToolCallArguments` in your
extension to inspect or modify the arguments. All registered extensions are applied in order. To fail the tool call
(for example, on invalid input), throw an exception from this method. The tool call then fails with
`ErrorType.TOOL_CALL_PREPROCESSING_FAILURE` and the error message is returned to the model.

```java
@Override
public JsonNode modifyToolCallArguments(AgentRunContext<MyRequest> context,
MyAgent agent,
ToolCall toolCall,
JsonNode inputArguments) {
if ("my_tool".equals(toolCall.getToolName())) {
// Add or modify arguments
((ObjectNode) inputArguments).put("region", "in");
}
return inputArguments;
}
```
1 change: 1 addition & 0 deletions docs/docs/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The `ErrorType` enum defines the categories of errors that can occur during agen
| `FILTERED` | The generated content was filtered by the provider. | No |
| `LENGTH_EXCEEDED` | The generated content exceeded the maximum allowed length/tokens. | No |
| `TOOL_CALL_PERMANENT_FAILURE` | A tool call failed with a non-recoverable error. | No |
| `TOOL_CALL_PREPROCESSING_FAILURE` | A tool call failed during argument preprocessing by an extension. | No |
| `TOOL_CALL_TEMPORARY_FAILURE` | A tool call failed with a transient error. | Yes |
| `TOOL_CALL_TIMEOUT` | A tool call exceeded its configured timeout. | Yes |
| `JSON_ERROR` | Error parsing JSON response from the model. | Yes |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.phonepe.sentinelai.core.agentmessages.AgentMessageType;
import com.phonepe.sentinelai.core.agentmessages.requests.GenericText;
import com.phonepe.sentinelai.core.agentmessages.requests.UserPrompt;
import com.phonepe.sentinelai.core.agentmessages.responses.ToolCall;
import com.phonepe.sentinelai.core.earlytermination.EarlyTerminationStrategy;
import com.phonepe.sentinelai.core.earlytermination.NeverTerminateEarlyStrategy;
import com.phonepe.sentinelai.core.errorhandling.DefaultErrorHandler;
Expand Down Expand Up @@ -702,10 +703,10 @@ private void processExtensionData(AgentRunContext<R> context,
}

private ArrayList<ModelOutputDefinition> populateOutputDefinitions(ProcessingMode processingMode) {
final var outputDefinitions = new ArrayList<>(List.of(
new ModelOutputDefinition(OUTPUT_VARIABLE_NAME,
"Output generated by the agent",
outputSchema())));
final var modelOutputDefinition = new ModelOutputDefinition(OUTPUT_VARIABLE_NAME,
"Output generated by the agent",
outputSchema());
final var outputDefinitions = new ArrayList<>(List.of(modelOutputDefinition));
outputDefinitions.addAll(extensions.stream()
.map(extension -> extension.outputSchema(processingMode))
.filter(Optional::isPresent)
Expand All @@ -714,6 +715,25 @@ private ArrayList<ModelOutputDefinition> populateOutputDefinitions(ProcessingMod
return outputDefinitions;
}

@SuppressWarnings("unchecked")
@SneakyThrows
private ToolCall modifyToolCallArguments(AgentRunContext<R> context, ToolCall toolCall) {
final var mapper = context.getAgentSetup()
.getMapper();
var argumentNode = mapper
.readTree(toolCall.getArguments());
for (final var extension : this.extensions) {
argumentNode = extension.modifyToolCallArguments(context, (A) this, toolCall, argumentNode);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fire an eventBus event with the extension class name will be helpful while instrumenting and debugging.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will raise too many events? also events make sens only when arguments are passed input and output .. and we would risk passing sensitive information. the exact reason why i have not added a log there as well.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purpose will not be to pass arguments but to instrument that an extension method was called and took x time.

}
return new ToolCall(toolCall.getSessionId(),
toolCall.getRunId(),
toolCall.getMessageId(),
toolCall.getTimestamp(),
toolCall.getToolCallId(),
toolCall.getToolName(),
mapper.writeValueAsString(argumentNode));
}

private ModelOutput makeModelCall(AgentSetup mergedAgentSetup,
ModelRunContext modelRunContext,
List<ModelOutputDefinition> outputDefinitions,
Expand All @@ -725,7 +745,8 @@ private ModelOutput makeModelCall(AgentSetup mergedAgentSetup,
final var toolRunner = new AgentToolRunner<>(self,
mergedAgentSetup,
toolRunApprovalSeeker,
context);
context,
toolCall -> modifyToolCallArguments(context, toolCall));
final var model = mergedAgentSetup.getModel();
final var safeRunner = new SafeToolRunner(toolRunner,
mergedAgentSetup,
Expand Down Expand Up @@ -779,7 +800,8 @@ private ModelOutput makeAsyncModelCall(AgentSetup mergedAgentSetup,
final var toolRunner = new AgentToolRunner<>(self,
mergedAgentSetup,
toolRunApprovalSeeker,
context);
context,
toolCall -> modifyToolCallArguments(context, toolCall));
final var model = mergedAgentSetup.getModel();
final var safeRunner = new SafeToolRunner(toolRunner,
mergedAgentSetup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;

import com.phonepe.sentinelai.core.agentmessages.AgentMessage;
import com.phonepe.sentinelai.core.agentmessages.responses.ToolCall;
import com.phonepe.sentinelai.core.tools.ToolBox;

import lombok.Value;
Expand Down Expand Up @@ -104,6 +105,25 @@ default List<AgentMessage> messages(AgentRunContext<R> context,
return List.of();
}

/**
* This method can be used to modify the input arguments for a tool call. This can be used to add additional
* parameters to the tool call or modify the existing parameters.
* To fail the tool call in case of invalid input, throw an exception from this method.
*
* @param context Context for the agent run
* @param agent Reference to the agent
* @param toolCall Tool call object
* @param inputArguments Input arguments for the tool call
* @return Modified input arguments for the tool call
*/
@SuppressWarnings("unused")
default JsonNode modifyToolCallArguments(AgentRunContext<R> context,
A agent,
ToolCall toolCall,
JsonNode inputArguments) {
return inputArguments;
}

/**
* This method can be used to set-up async tasks or perform any operations once the extension is registered with
* the agent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;

/**
*
Expand All @@ -76,6 +77,8 @@

AgentRunContext<R> context;

UnaryOperator<ToolCall> toolCallPreProcessor;

/**
* This returns a temporary failure for unhandled exceptions. Can be used to retry if needed.
*
Expand Down Expand Up @@ -155,24 +158,24 @@

@Override
public ToolCallResponse runTool(Map<String, ExecutableTool> tools,
ToolCall toolCall) {
ToolCall providedToolCall) {
final var eventBus = context.getAgentSetup().getEventBus();
if (!toolRunApprovalSeeker.seekApproval(agent, context, toolCall)) {
if (!toolRunApprovalSeeker.seekApproval(agent, context, providedToolCall)) {
log.info("Tool call {} for tool {} was not approved by the user",
toolCall.getToolCallId(),
toolCall.getToolName());
providedToolCall.getToolCallId(),
providedToolCall.getToolName());
eventBus.notify(new ToolCallApprovalDeniedAgentEvent(agent.name(),
context.getRunId(),
AgentUtils
.sessionId(context),
AgentUtils
.userId(context),
toolCall.getToolCallId(),
toolCall.getToolName()));
providedToolCall.getToolCallId(),
providedToolCall.getToolName()));
return new ToolCallResponse(AgentUtils.sessionId(context),
context.getRunId(),
toolCall.getToolCallId(),
toolCall.getToolName(),
providedToolCall.getToolCallId(),
providedToolCall.getToolName(),
ErrorType.TOOL_CALL_PERMANENT_FAILURE,
"Tool call was not approved by the user",
LocalDateTime.now());
Expand All @@ -181,17 +184,44 @@
context.getRunId(),
AgentUtils.sessionId(context),
AgentUtils.userId(context),
toolCall.getToolCallId(),
toolCall.getToolName(),
toolCall.getArguments()));
providedToolCall.getToolCallId(),
providedToolCall.getToolName(),
providedToolCall.getArguments()));
final var stopwatch = Stopwatch.createStarted();
var toolCall = providedToolCall;
try {
toolCall = toolCallPreProcessor.apply(providedToolCall);
}
catch (Exception e) {
final var rootCause = AgentUtils.rootCause(e).getMessage();
final var response = "Tool call failed with error: " + rootCause;
log.info("Tool call {} for tool {} failed with error: {}",
Comment thread
santanusinha marked this conversation as resolved.
providedToolCall.getToolCallId(),
providedToolCall.getToolName(),
rootCause);
eventBus.notify(new ToolCallCompletedAgentEvent(agent.name(),
context.getRunId(),
AgentUtils.sessionId(context),
AgentUtils.userId(context),
toolCall.getToolCallId(),
toolCall.getToolName(),
ErrorType.TOOL_CALL_PREPROCESSING_FAILURE,
response,
Duration.ofMillis(stopwatch
.elapsed(TimeUnit.MILLISECONDS))));
return new ToolCallResponse(AgentUtils.sessionId(context),
context.getRunId(),
providedToolCall.getToolCallId(),
providedToolCall.getToolName(),
ErrorType.TOOL_CALL_PREPROCESSING_FAILURE,
response,
LocalDateTime.now());

Check warning on line 218 in sentinel-ai-core/src/main/java/com/phonepe/sentinelai/core/agent/AgentToolRunner.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Explicitly specify the time zone by passing a ZoneId or a Clock to the .now() method.

See more on https://sonarcloud.io/project/issues?id=PhonePe_sentinel-ai&issues=AaA06_LfrKwcmXfO2BL6&open=AaA06_LfrKwcmXfO2BL6&pullRequest=95
}
final var response = runTool(context, tools, toolCall);
eventBus.notify(new ToolCallCompletedAgentEvent(agent.name(),
context.getRunId(),
AgentUtils.sessionId(
context),
AgentUtils.userId(
context),
AgentUtils.sessionId(context),
AgentUtils.userId(context),
toolCall.getToolCallId(),
toolCall.getToolName(),
response.getErrorType(),
Expand All @@ -201,7 +231,6 @@
return response;
}


/**
* Convert parameters string received from LLM to actual parameters for tool call
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum ErrorType {
FILTERED("Content filtered", false),
LENGTH_EXCEEDED("Content length exceeded", false),
TOOL_CALL_PERMANENT_FAILURE("Tool call failed permanently for tool: %s", false),
TOOL_CALL_PREPROCESSING_FAILURE("Tool call failed in preprocessing: %s", false),
TOOL_CALL_TEMPORARY_FAILURE("Tool call failed temporarily for tool: %s", true),
TOOL_CALL_TIMEOUT("Tool call timed out for tool: %s", true),
JSON_ERROR("Error parsing JSON. Error: %s", true),
Expand Down
Loading
Loading