mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-23 08:56:42 +08:00
fix: unable use agent as a tool in agent component (#17228)
### Summary As title <img width="3774" height="2128" alt="image" src="https://github.com/user-attachments/assets/b1ce69b0-2298-4c0c-92de-8d840938e4f0" />
This commit is contained in:
@@ -34,13 +34,15 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// agentLLMIDPattern matches `<model>@<provider>` and
|
||||
const maxSubAgentDepth = 8
|
||||
|
||||
// agentPatternless matches `<model>@<provider>` and
|
||||
// `<model>@<instance>@<provider>` (the trailing `@<provider>` is
|
||||
// always the last segment — the segment just before the last `@`
|
||||
// is treated as the bare model name for upstream API calls). The
|
||||
// browser component has the same idea at browser.go:88-92, but
|
||||
// keeps the regex greedy for its 2-part fixture; we keep both
|
||||
// behaviours here via the in-function split below.
|
||||
// behaviors here via the in-function split below.
|
||||
|
||||
// agentProviderLastSegmentSplit takes a composite llm_id and
|
||||
// returns (bareModelName, providerName, true) — or ("", "", false)
|
||||
@@ -72,12 +74,14 @@ type AgentComponent struct {
|
||||
// AgentParam captures the (resolved) DSL parameters for an Agent node.
|
||||
type AgentParam struct {
|
||||
ModelID string
|
||||
Description string
|
||||
SystemPrompt string
|
||||
UserPrompt string
|
||||
Thinking string
|
||||
TopP *float64
|
||||
Tools []string // Agent-visible tool names resolved into Eino BaseTool instances
|
||||
ToolParams map[string]map[string]any // node-level tool constructor params keyed by tool name
|
||||
SubAgents []SubAgentTool
|
||||
MaxRounds int
|
||||
MessageHistoryWindowSize int // number of prior conversation turns to include; zero disables history
|
||||
OptimizeMultiTurn bool
|
||||
@@ -99,6 +103,13 @@ type AgentParam struct {
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// SubAgentTool is a child Agent exposed to a parent Agent as an Eino tool.
|
||||
type SubAgentTool struct {
|
||||
Name string
|
||||
Description string
|
||||
Param AgentParam
|
||||
}
|
||||
|
||||
const (
|
||||
agentUserPromptSchemaDefault = "This is the order you need to send to the agent."
|
||||
defaultAgentMessageHistoryWindowSize = 13
|
||||
@@ -532,7 +543,159 @@ func optimizeMultiTurnQuestion(ctx context.Context, p AgentParam, history []map[
|
||||
}
|
||||
|
||||
func buildAgentTools(p AgentParam) ([]einotool.BaseTool, error) {
|
||||
return agenttool.BuildAll(p.Tools, p.ToolParams)
|
||||
tools, err := agenttool.BuildAll(p.Tools, p.ToolParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toolNames := make(map[string]struct{}, len(tools)+len(p.SubAgents))
|
||||
for _, tool := range tools {
|
||||
info, err := tool.Info(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("agent tool info: %w", err)
|
||||
}
|
||||
if info == nil || strings.TrimSpace(info.Name) == "" {
|
||||
return nil, fmt.Errorf("agent tool info: missing name")
|
||||
}
|
||||
if _, exists := toolNames[info.Name]; exists {
|
||||
return nil, fmt.Errorf("duplicate agent tool name %q", info.Name)
|
||||
}
|
||||
toolNames[info.Name] = struct{}{}
|
||||
}
|
||||
for _, subAgent := range p.SubAgents {
|
||||
name := uniqueAgentToolName(subAgent.Name, toolNames)
|
||||
toolNames[name] = struct{}{}
|
||||
tools = append(tools, &subAgentTool{name: name, spec: subAgent})
|
||||
}
|
||||
return tools, nil
|
||||
}
|
||||
|
||||
type subAgentTool struct {
|
||||
name string
|
||||
spec SubAgentTool
|
||||
}
|
||||
|
||||
func (t *subAgentTool) Info(_ context.Context) (*schema.ToolInfo, error) {
|
||||
params := map[string]*schema.ParameterInfo{
|
||||
"user_prompt": {
|
||||
Type: schema.String,
|
||||
Desc: agentUserPromptSchemaDefault,
|
||||
Required: true,
|
||||
},
|
||||
"reasoning": {
|
||||
Type: schema.String,
|
||||
Desc: "Supervisor's reasoning for choosing this agent. Explain why this agent is being invoked and what is expected of it.",
|
||||
Required: true,
|
||||
},
|
||||
"context": {
|
||||
Type: schema.String,
|
||||
Desc: "All relevant background information, prior facts, decisions, and state needed by the agent to solve the current query.",
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
name := t.name
|
||||
if name == "" {
|
||||
name = normalizeAgentToolName(t.spec.Name)
|
||||
}
|
||||
return &schema.ToolInfo{
|
||||
Name: name,
|
||||
Desc: subAgentToolDescription(t.spec),
|
||||
ParamsOneOf: schema.NewParamsOneOfByParams(params),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type subAgentDepthKey struct{}
|
||||
|
||||
func subAgentDepth(ctx context.Context) int {
|
||||
if v, ok := ctx.Value(subAgentDepthKey{}).(int); ok {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *subAgentTool) InvokableRun(ctx context.Context, argsJSON string, _ ...einotool.Option) (string, error) {
|
||||
depth := subAgentDepth(ctx)
|
||||
if depth >= maxSubAgentDepth {
|
||||
return "", fmt.Errorf("sub-agent tool %q: max nesting depth (%d) exceeded", normalizeAgentToolName(t.spec.Name), maxSubAgentDepth)
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, subAgentDepthKey{}, depth+1)
|
||||
|
||||
inputs := map[string]any{}
|
||||
if strings.TrimSpace(argsJSON) != "" {
|
||||
if err := json.Unmarshal([]byte(argsJSON), &inputs); err != nil {
|
||||
return "", fmt.Errorf("sub-agent tool %q: decode arguments: %w", normalizeAgentToolName(t.spec.Name), err)
|
||||
}
|
||||
}
|
||||
|
||||
out, err := NewAgentComponent(t.spec.Param).Invoke(ctx, inputs)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sub-agent tool %q: %w", normalizeAgentToolName(t.spec.Name), err)
|
||||
}
|
||||
if content, ok := out["content"].(string); ok {
|
||||
return content, nil
|
||||
}
|
||||
payload, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sub-agent tool %q: encode output: %w", normalizeAgentToolName(t.spec.Name), err)
|
||||
}
|
||||
return string(payload), nil
|
||||
}
|
||||
|
||||
func subAgentToolDescription(spec SubAgentTool) string {
|
||||
if strings.TrimSpace(spec.Description) != "" {
|
||||
return strings.TrimSpace(spec.Description)
|
||||
}
|
||||
if strings.TrimSpace(spec.Param.Description) != "" {
|
||||
return strings.TrimSpace(spec.Param.Description)
|
||||
}
|
||||
return "This is an agent for a specific task."
|
||||
}
|
||||
|
||||
func uniqueAgentToolName(name string, used map[string]struct{}) string {
|
||||
base := normalizeAgentToolName(name)
|
||||
if _, exists := used[base]; !exists {
|
||||
return base
|
||||
}
|
||||
for n := 2; ; n++ {
|
||||
candidate := fmt.Sprintf("%s_%d", base, n)
|
||||
if _, exists := used[candidate]; !exists {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeAgentToolName(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return "agent"
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
lastSeparator := false
|
||||
for _, r := range name {
|
||||
valid := r == '_' || r == '-' ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9')
|
||||
if valid {
|
||||
b.WriteRune(r)
|
||||
lastSeparator = false
|
||||
continue
|
||||
}
|
||||
if !lastSeparator {
|
||||
b.WriteByte('_')
|
||||
lastSeparator = true
|
||||
}
|
||||
}
|
||||
|
||||
out := strings.Trim(b.String(), "_-")
|
||||
if out == "" {
|
||||
return "agent"
|
||||
}
|
||||
if out[0] >= '0' && out[0] <= '9' {
|
||||
out = "agent_" + out
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// NewAgentComponent builds an AgentComponent from raw params.
|
||||
@@ -1104,6 +1267,9 @@ func mergeAgentParam(base AgentParam, inputs map[string]any) AgentParam {
|
||||
} else if v, ok := stringFrom(inputs, "llm_id"); ok {
|
||||
p.ModelID = v
|
||||
}
|
||||
if v, ok := stringFrom(inputs, "description"); ok {
|
||||
p.Description = v
|
||||
}
|
||||
if v, ok := stringFrom(inputs, "system_prompt"); ok {
|
||||
p.SystemPrompt = v
|
||||
} else if v, ok := stringFrom(inputs, "sys_prompt"); ok {
|
||||
@@ -1128,6 +1294,9 @@ func mergeAgentParam(base AgentParam, inputs map[string]any) AgentParam {
|
||||
if v, ok := intFrom(inputs, "max_rounds"); ok {
|
||||
p.MaxRounds = v
|
||||
}
|
||||
if v, ok := intFrom(inputs, "message_history_window_size"); ok {
|
||||
p.MessageHistoryWindowSize = v
|
||||
}
|
||||
if v, ok := stringFrom(inputs, "driver"); ok {
|
||||
p.Driver = v
|
||||
}
|
||||
@@ -1137,8 +1306,9 @@ func mergeAgentParam(base AgentParam, inputs map[string]any) AgentParam {
|
||||
if v, ok := stringFrom(inputs, "base_url"); ok {
|
||||
p.BaseURL = v
|
||||
}
|
||||
if tools, params, ok := agentToolsFrom(inputs, "tools"); ok {
|
||||
if tools, params, subAgents, ok := agentToolsFrom(inputs, "tools"); ok {
|
||||
p.Tools = tools
|
||||
p.SubAgents = subAgents
|
||||
p.ToolParams = mergeToolParams(p.ToolParams, params)
|
||||
}
|
||||
if v, ok := nestedMapFrom(inputs, "tool_params"); ok {
|
||||
@@ -1158,18 +1328,20 @@ func mergeAgentParam(base AgentParam, inputs map[string]any) AgentParam {
|
||||
|
||||
// agentToolsFrom extracts the Agent tools list. The Go-native shape is
|
||||
// []string; the canvas DSL shape stores tool component objects with
|
||||
// component_name and params.
|
||||
func agentToolsFrom(inputs map[string]any, name string) ([]string, map[string]map[string]any, bool) {
|
||||
// component_name and params. Child Agent objects are returned separately
|
||||
// because they are dynamic tools, not entries in the static tool registry.
|
||||
func agentToolsFrom(inputs map[string]any, name string) ([]string, map[string]map[string]any, []SubAgentTool, bool) {
|
||||
v, ok := inputs[name]
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
switch x := v.(type) {
|
||||
case []string:
|
||||
return x, nil, true
|
||||
return x, nil, nil, true
|
||||
case []any:
|
||||
out := make([]string, 0, len(x))
|
||||
params := make(map[string]map[string]any)
|
||||
subAgents := make([]SubAgentTool, 0)
|
||||
for _, item := range x {
|
||||
switch tool := item.(type) {
|
||||
case string:
|
||||
@@ -1178,6 +1350,10 @@ func agentToolsFrom(inputs map[string]any, name string) ([]string, map[string]ma
|
||||
}
|
||||
out = append(out, tool)
|
||||
case map[string]any:
|
||||
if subAgent, ok := subAgentToolObject(tool); ok {
|
||||
subAgents = append(subAgents, subAgent)
|
||||
continue
|
||||
}
|
||||
toolName, toolParams, ok := agentToolObject(tool)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -1188,9 +1364,36 @@ func agentToolsFrom(inputs map[string]any, name string) ([]string, map[string]ma
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, params, true
|
||||
return out, params, subAgents, true
|
||||
}
|
||||
return nil, nil, false
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
|
||||
func subAgentToolObject(item map[string]any) (SubAgentTool, bool) {
|
||||
componentName, ok := stringFrom(item, "component_name")
|
||||
if !ok || !strings.EqualFold(strings.TrimSpace(componentName), "Agent") {
|
||||
return SubAgentTool{}, false
|
||||
}
|
||||
|
||||
rawParams, _ := item["params"].(map[string]any)
|
||||
param := agentParamFromMap(rawParams)
|
||||
name, _ := stringFrom(item, "function_name")
|
||||
if strings.TrimSpace(name) == "" {
|
||||
name, _ = stringFrom(item, "name")
|
||||
}
|
||||
if strings.TrimSpace(name) == "" {
|
||||
name, _ = stringFrom(item, "id")
|
||||
}
|
||||
description, _ := stringFrom(item, "description")
|
||||
if strings.TrimSpace(description) == "" {
|
||||
description = param.Description
|
||||
}
|
||||
|
||||
return SubAgentTool{
|
||||
Name: normalizeAgentToolName(name),
|
||||
Description: description,
|
||||
Param: param,
|
||||
}, true
|
||||
}
|
||||
|
||||
func agentToolObject(item map[string]any) (string, map[string]any, bool) {
|
||||
@@ -1299,56 +1502,15 @@ func nestedMapFrom(inputs map[string]any, name string) (map[string]map[string]an
|
||||
return out, true
|
||||
}
|
||||
|
||||
func agentParamFromMap(params map[string]any) AgentParam {
|
||||
return mergeAgentParam(AgentParam{
|
||||
MessageHistoryWindowSize: defaultAgentMessageHistoryWindowSize,
|
||||
}, params)
|
||||
}
|
||||
|
||||
// init registers AgentComponent with the orchestrator-owned registry.
|
||||
func init() {
|
||||
Register("Agent", func(params map[string]any) (Component, error) {
|
||||
p := AgentParam{MessageHistoryWindowSize: defaultAgentMessageHistoryWindowSize}
|
||||
if v, ok := stringFrom(params, "model_id"); ok {
|
||||
p.ModelID = v
|
||||
} else if v, ok := stringFrom(params, "llm_id"); ok {
|
||||
p.ModelID = v
|
||||
}
|
||||
if v, ok := stringFrom(params, "system_prompt"); ok {
|
||||
p.SystemPrompt = v
|
||||
} else if v, ok := stringFrom(params, "sys_prompt"); ok {
|
||||
p.SystemPrompt = v
|
||||
}
|
||||
if promptSystem, promptUser, ok := promptMessagesFromParams(params); ok {
|
||||
p.SystemPrompt = appendPromptText(p.SystemPrompt, promptSystem)
|
||||
p.UserPrompt = promptUser
|
||||
}
|
||||
if v, ok := stringFrom(params, "user_prompt"); ok && p.UserPrompt == "" {
|
||||
p.UserPrompt = v
|
||||
}
|
||||
if v, ok := floatFrom(params, "top_p"); ok {
|
||||
f := v
|
||||
p.TopP = &f
|
||||
}
|
||||
if tools, toolParams, ok := agentToolsFrom(params, "tools"); ok {
|
||||
p.Tools = tools
|
||||
p.ToolParams = mergeToolParams(p.ToolParams, toolParams)
|
||||
}
|
||||
if v, ok := nestedMapFrom(params, "tool_params"); ok {
|
||||
p.ToolParams = mergeToolParams(p.ToolParams, v)
|
||||
}
|
||||
if v, ok := stringFrom(params, "thinking"); ok && v != "" && v != "default" {
|
||||
p.Thinking = v
|
||||
}
|
||||
if v, ok := intFrom(params, "max_rounds"); ok {
|
||||
p.MaxRounds = v
|
||||
}
|
||||
if v, ok := intFrom(params, "message_history_window_size"); ok {
|
||||
p.MessageHistoryWindowSize = v
|
||||
}
|
||||
if v, ok := stringFrom(params, "driver"); ok {
|
||||
p.Driver = v
|
||||
}
|
||||
if v, ok := stringFrom(params, "api_key"); ok {
|
||||
p.APIKey = v
|
||||
}
|
||||
if v, ok := stringFrom(params, "base_url"); ok {
|
||||
p.BaseURL = v
|
||||
}
|
||||
return NewAgentComponent(p), nil
|
||||
return NewAgentComponent(agentParamFromMap(params)), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,6 +212,34 @@ func TestAgent_UsesPromptsListForSysQuery(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_NewTreatsSchemaDefaultUserPromptAsSysQueryPlaceholder(t *testing.T) {
|
||||
var gotPrompt string
|
||||
withAgentRunner(t, func(_ context.Context, p AgentParam) (*schema.Message, error) {
|
||||
gotPrompt = p.UserPrompt
|
||||
return &schema.Message{Role: schema.Assistant, Content: "ok"}, nil
|
||||
})
|
||||
|
||||
cmp, err := New("Agent", map[string]any{
|
||||
"model_id": "stub",
|
||||
"api_key": "test-key",
|
||||
"user_prompt": agentUserPromptSchemaDefault,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New(Agent): %v", err)
|
||||
}
|
||||
|
||||
state := runtime.NewCanvasState("run-1", "task-1")
|
||||
state.Sys["query"] = "用户真正的问题"
|
||||
ctx := runtime.WithState(context.Background(), state)
|
||||
|
||||
if _, err := cmp.Invoke(ctx, nil); err != nil {
|
||||
t.Fatalf("Invoke: %v", err)
|
||||
}
|
||||
if gotPrompt != "用户真正的问题" {
|
||||
t.Fatalf("runner prompt = %q, want sys.query placeholder resolved", gotPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_EmptyConfiguredUserPromptDoesNotFallbackToSysQuery(t *testing.T) {
|
||||
var gotSystemPrompt, gotUserPrompt string
|
||||
withAgentRunner(t, func(_ context.Context, p AgentParam) (*schema.Message, error) {
|
||||
@@ -574,6 +602,143 @@ func TestAgent_Registered(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_CanvasSubAgentToolBuildsDynamicTool(t *testing.T) {
|
||||
c, err := New("Agent", map[string]any{
|
||||
"model_id": "stub",
|
||||
"user_prompt": "parent prompt",
|
||||
"tools": []any{
|
||||
map[string]any{
|
||||
"component_name": "Agent",
|
||||
"id": "child-node",
|
||||
"name": "NewPumasLick",
|
||||
"params": map[string]any{
|
||||
"model_id": "stub",
|
||||
"description": "child agent description",
|
||||
"prompts": []any{
|
||||
map[string]any{"role": "user", "content": "child prompt"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New(Agent): %v", err)
|
||||
}
|
||||
agent, ok := c.(*AgentComponent)
|
||||
if !ok {
|
||||
t.Fatalf("New(Agent) returned %T, want *AgentComponent", c)
|
||||
}
|
||||
if len(agent.param.Tools) != 0 {
|
||||
t.Fatalf("regular tools = %v, want none", agent.param.Tools)
|
||||
}
|
||||
if len(agent.param.SubAgents) != 1 {
|
||||
t.Fatalf("sub agents = %d, want 1", len(agent.param.SubAgents))
|
||||
}
|
||||
|
||||
tools, err := buildAgentTools(agent.param)
|
||||
if err != nil {
|
||||
t.Fatalf("buildAgentTools: %v", err)
|
||||
}
|
||||
if len(tools) != 1 {
|
||||
t.Fatalf("len(tools) = %d, want 1", len(tools))
|
||||
}
|
||||
info, err := tools[0].Info(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("tool.Info: %v", err)
|
||||
}
|
||||
if info.Name != "NewPumasLick" {
|
||||
t.Fatalf("tool name = %q, want NewPumasLick", info.Name)
|
||||
}
|
||||
if info.Desc != "child agent description" {
|
||||
t.Fatalf("tool desc = %q, want child agent description", info.Desc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_CanvasSubAgentToolNamesAreUniqueAfterNormalization(t *testing.T) {
|
||||
c, err := New("Agent", map[string]any{
|
||||
"model_id": "stub",
|
||||
"user_prompt": "parent prompt",
|
||||
"tools": []any{
|
||||
map[string]any{
|
||||
"component_name": "Agent",
|
||||
"name": "你好",
|
||||
"params": map[string]any{
|
||||
"model_id": "stub",
|
||||
"user_prompt": "child one",
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"component_name": "Agent",
|
||||
"name": "世界",
|
||||
"params": map[string]any{
|
||||
"model_id": "stub",
|
||||
"user_prompt": "child two",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New(Agent): %v", err)
|
||||
}
|
||||
agent, ok := c.(*AgentComponent)
|
||||
if !ok {
|
||||
t.Fatalf("New(Agent) returned %T, want *AgentComponent", c)
|
||||
}
|
||||
|
||||
tools, err := buildAgentTools(agent.param)
|
||||
if err != nil {
|
||||
t.Fatalf("buildAgentTools: %v", err)
|
||||
}
|
||||
if len(tools) != 2 {
|
||||
t.Fatalf("len(tools) = %d, want 2", len(tools))
|
||||
}
|
||||
var names []string
|
||||
for _, tool := range tools {
|
||||
info, err := tool.Info(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("tool.Info: %v", err)
|
||||
}
|
||||
names = append(names, info.Name)
|
||||
}
|
||||
if got, want := strings.Join(names, ","), "agent,agent_2"; got != want {
|
||||
t.Fatalf("tool names = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_SubAgentToolInvokableRunCallsChildAgent(t *testing.T) {
|
||||
var got AgentParam
|
||||
withAgentRunner(t, func(_ context.Context, p AgentParam) (*schema.Message, error) {
|
||||
got = p
|
||||
return &schema.Message{Role: schema.Assistant, Content: "child answer"}, nil
|
||||
})
|
||||
|
||||
tool := &subAgentTool{spec: SubAgentTool{
|
||||
Name: "Child Agent",
|
||||
Param: AgentParam{
|
||||
ModelID: "stub",
|
||||
UserPrompt: "default child prompt",
|
||||
MaxRounds: 1,
|
||||
},
|
||||
}}
|
||||
|
||||
out, err := tool.InvokableRun(context.Background(), `{"user_prompt":"ask child","reasoning":"because","context":"facts"}`)
|
||||
if err != nil {
|
||||
t.Fatalf("InvokableRun: %v", err)
|
||||
}
|
||||
if out != "child answer" {
|
||||
t.Fatalf("InvokableRun output = %q, want child answer", out)
|
||||
}
|
||||
if !strings.Contains(got.UserPrompt, "REASONING:\nbecause") {
|
||||
t.Fatalf("UserPrompt = %q, want reasoning included", got.UserPrompt)
|
||||
}
|
||||
if !strings.Contains(got.UserPrompt, "CONTEXT:\nfacts") {
|
||||
t.Fatalf("UserPrompt = %q, want context included", got.UserPrompt)
|
||||
}
|
||||
if !strings.Contains(got.UserPrompt, "QUERY:\nask child") {
|
||||
t.Fatalf("UserPrompt = %q, want query included", got.UserPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
// exhaustStepsModel is a scripted ToolCallingChatModel that emits a
|
||||
// tool_call on every Generate and never returns final content. It
|
||||
// is the input driver for TestAgent_ReActExhaustsSteps, which needs
|
||||
|
||||
Reference in New Issue
Block a user