agents: Add more robust response handling to executor.go (#1316)

* agents: fix handling of observation suffix in tool inputs

Add test coverage to ensure the executor properly trims nObservation:
suffix from tool inputs before passing them to tools. This prevents
tools from receiving malformed input that includes the observation
marker.

- Add TestExecutorTrimsObservationSuffix to verify trimming behavior
- Add test case in MRKL output parser for action inputs with observation suffix
- Add tools field to testAgent to support tool testing

---------

Co-authored-by: Travis Cline <travis.cline@gmail.com>
This commit is contained in:
xiazemin
2025-06-29 07:09:12 +08:00
committed by GitHub
parent 2389d7aa71
commit c2f9ad7f34
3 changed files with 69 additions and 2 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ func (e *Executor) doAction(
}), nil
}
observation, err := tool.Call(ctx, action.ToolInput)
observation, err := tool.Call(ctx, strings.TrimSuffix(action.ToolInput, "\nObservation:"))
if err != nil {
return nil, err
}
+58 -1
View File
@@ -23,6 +23,7 @@ type testAgent struct {
err error
inputKeys []string
outputKeys []string
tools []tools.Tool
recordedIntermediateSteps []schema.AgentStep
recordedInputs map[string]string
@@ -50,7 +51,7 @@ func (a testAgent) GetOutputKeys() []string {
}
func (a *testAgent) GetTools() []tools.Tool {
return nil
return a.tools
}
func TestExecutorWithErrorHandler(t *testing.T) {
@@ -171,3 +172,59 @@ func TestExecutorWithOpenAIFunctionAgent(t *testing.T) {
require.True(t, strings.Contains(result, "2012") || strings.Contains(result, "March"),
"correct answer 2012 or March not in response")
}
// mockTool implements the tools.Tool interface for testing
type mockTool struct {
name string
description string
receivedInputPtr *string
}
func (m *mockTool) Name() string {
return m.name
}
func (m *mockTool) Description() string {
return m.description
}
func (m *mockTool) Call(_ context.Context, input string) (string, error) {
*m.receivedInputPtr = input
return "mock result", nil
}
func TestExecutorTrimsObservationSuffix(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Create a mock tool that records what input it receives
var receivedInput string
mockToolInst := &mockTool{
name: "mock_tool",
description: "A mock tool for testing",
receivedInputPtr: &receivedInput,
}
// Create a test agent that returns an action with trailing "\nObservation:"
testAgent := &testAgent{
actions: []schema.AgentAction{
{
Tool: "mock_tool",
ToolInput: "test input\nObservation:",
Log: "Action: mock_tool\nAction Input: test input\nObservation:",
},
},
inputKeys: []string{"input"},
outputKeys: []string{"output"},
tools: []tools.Tool{mockToolInst},
}
executor := agents.NewExecutor(testAgent, agents.WithMaxIterations(1))
_, err := chains.Call(ctx, executor, map[string]any{"input": "test question"})
// We expect ErrNotFinished since our test agent doesn't provide a finish action
require.ErrorIs(t, err, agents.ErrNotFinished)
// Verify that the tool received the input with "\nObservation:" trimmed off
require.Equal(t, "test input", receivedInput, "Tool should receive input with \\nObservation: suffix trimmed")
}
+10
View File
@@ -36,6 +36,16 @@ func TestMRKLOutputParser(t *testing.T) {
expectedFinish: nil,
expectedErr: nil,
},
{
input: "Action: calculator\nAction Input: 5 + 3\nObservation:",
expectedActions: []schema.AgentAction{{
Tool: "calculator",
ToolInput: "5 + 3\nObservation:",
Log: "Action: calculator\nAction Input: 5 + 3\nObservation:",
}},
expectedFinish: nil,
expectedErr: nil,
},
}
a := OneShotZeroAgent{}