diff --git a/internal/application/service/agent_parallel_tool_calls_config_test.go b/internal/application/service/agent_parallel_tool_calls_config_test.go new file mode 100644 index 0000000000..4c43478c30 --- /dev/null +++ b/internal/application/service/agent_parallel_tool_calls_config_test.go @@ -0,0 +1,67 @@ +package service + +import ( + "testing" + + "github.com/Tencent/WeKnora/internal/config" + "github.com/Tencent/WeKnora/internal/types" + "github.com/stretchr/testify/require" +) + +// The engine already knows how to run a round's tool calls concurrently +// (AgentEngine.executeToolCallsParallel, gated on AgentConfig.ParallelToolCalls), +// but nothing ever set that flag: buildAgentConfig copies CustomAgentConfig into +// the runtime AgentConfig field by field and this one was absent, so the gate +// read the zero value and the parallel branch was unreachable for every custom +// agent. Failure was silent — tools just kept running one at a time. +func TestAgentConfigCarriesTheParallelToolCallsPreference(t *testing.T) { + for _, tc := range []struct { + name string + want bool + }{ + {name: "opted in", want: true}, + {name: "opted out", want: false}, + } { + t.Run(tc.name, func(t *testing.T) { + svc := &sessionService{ + cfg: &config.Config{}, + webSearchProviderRepo: &sharedAgentWebSearchRepo{}, + } + req := &types.QARequest{ + Session: &types.Session{ID: "session-1", TenantID: 1}, + CustomAgent: &types.CustomAgent{ + TenantID: 1, + Config: types.CustomAgentConfig{ + MaxIterations: 5, + ParallelToolCalls: tc.want, + }, + }, + } + + agentConfig, err := svc.buildAgentConfig(t.Context(), req, &types.Tenant{ID: 1}, 1) + require.NoError(t, err) + require.Equal(t, tc.want, agentConfig.ParallelToolCalls) + }) + } +} + +// Agents saved before this option existed must keep running their tools +// sequentially: tools are not required to be side-effect free, so silently +// turning on concurrency for them would be a behavior change, not a fix. +func TestAgentConfigDefaultsParallelToolCallsOff(t *testing.T) { + svc := &sessionService{ + cfg: &config.Config{}, + webSearchProviderRepo: &sharedAgentWebSearchRepo{}, + } + req := &types.QARequest{ + Session: &types.Session{ID: "session-1", TenantID: 1}, + CustomAgent: &types.CustomAgent{ + TenantID: 1, + Config: types.CustomAgentConfig{MaxIterations: 5}, + }, + } + + agentConfig, err := svc.buildAgentConfig(t.Context(), req, &types.Tenant{ID: 1}, 1) + require.NoError(t, err) + require.False(t, agentConfig.ParallelToolCalls) +} diff --git a/internal/application/service/session_agent_qa.go b/internal/application/service/session_agent_qa.go index c194cd26d7..8cb2d4a6bb 100644 --- a/internal/application/service/session_agent_qa.go +++ b/internal/application/service/session_agent_qa.go @@ -256,6 +256,7 @@ func (s *sessionService) buildAgentConfig( customAgent := req.CustomAgent agentConfig := &types.AgentConfig{ MaxIterations: customAgent.Config.MaxIterations, + ParallelToolCalls: customAgent.Config.ParallelToolCalls, Temperature: customAgent.Config.Temperature, WebSearchEnabled: customAgent.Config.WebSearchEnabled && req.WebSearchEnabled, WebSearchMaxResults: customAgent.Config.WebSearchMaxResults, diff --git a/internal/types/custom_agent.go b/internal/types/custom_agent.go index 6160c50dc5..01b93e61a1 100644 --- a/internal/types/custom_agent.go +++ b/internal/types/custom_agent.go @@ -132,6 +132,10 @@ type CustomAgentConfig struct { // ===== Agent Mode Settings ===== // Maximum iterations for ReAct loop (only for agent type) MaxIterations int `yaml:"max_iterations" json:"max_iterations"` + // Whether independent tool calls in one round run concurrently (default: false). + // Opt-in because tools are not required to be side-effect free; agents that + // depend on tools observing each other's writes must leave this off. + ParallelToolCalls bool `yaml:"parallel_tool_calls" json:"parallel_tool_calls,omitempty"` // Timeout for a single LLM call in seconds (0 = use global default) LLMCallTimeout int `yaml:"llm_call_timeout" json:"llm_call_timeout,omitempty"` // Allowed tools (only for agent type)