|
@@ -53,6 +53,8 @@ class AgentApplicationService:
|
|
|
skill_client: SkillServiceClient | None = None,
|
|
skill_client: SkillServiceClient | None = None,
|
|
|
event_client: EventServiceClient | None = None,
|
|
event_client: EventServiceClient | None = None,
|
|
|
react_max_steps: int = 5,
|
|
react_max_steps: int = 5,
|
|
|
|
|
+ react_max_tool_calls: int = 10,
|
|
|
|
|
+ react_tool_retry_count: int = 1,
|
|
|
) -> None:
|
|
) -> None:
|
|
|
self.agent_repository = agent_repository
|
|
self.agent_repository = agent_repository
|
|
|
self.agent_version_repository = agent_version_repository
|
|
self.agent_version_repository = agent_version_repository
|
|
@@ -64,6 +66,8 @@ class AgentApplicationService:
|
|
|
self.skill_client = skill_client
|
|
self.skill_client = skill_client
|
|
|
self.event_client = event_client
|
|
self.event_client = event_client
|
|
|
self.react_max_steps = react_max_steps
|
|
self.react_max_steps = react_max_steps
|
|
|
|
|
+ self.react_max_tool_calls = react_max_tool_calls
|
|
|
|
|
+ self.react_tool_retry_count = react_tool_retry_count
|
|
|
|
|
|
|
|
def create_agent(self, payload: AgentCreateRequest) -> AgentDefinition:
|
|
def create_agent(self, payload: AgentCreateRequest) -> AgentDefinition:
|
|
|
return self.agent_repository.create(
|
|
return self.agent_repository.create(
|
|
@@ -435,6 +439,7 @@ class AgentApplicationService:
|
|
|
agent_version=agent_version,
|
|
agent_version=agent_version,
|
|
|
memory_results=memory_results,
|
|
memory_results=memory_results,
|
|
|
capability_context=self._format_react_instruction(
|
|
capability_context=self._format_react_instruction(
|
|
|
|
|
+ agent_run=agent_run,
|
|
|
selected_tools=selected_tools,
|
|
selected_tools=selected_tools,
|
|
|
skill_invocations=skill_invocations,
|
|
skill_invocations=skill_invocations,
|
|
|
),
|
|
),
|
|
@@ -442,6 +447,7 @@ class AgentApplicationService:
|
|
|
react_steps: list[dict[str, JSONValue]] = []
|
|
react_steps: list[dict[str, JSONValue]] = []
|
|
|
tool_invocations: list[dict[str, JSONValue]] = []
|
|
tool_invocations: list[dict[str, JSONValue]] = []
|
|
|
final_answer: str | None = None
|
|
final_answer: str | None = None
|
|
|
|
|
+ tool_call_count = 0
|
|
|
|
|
|
|
|
max_steps = self._read_int(
|
|
max_steps = self._read_int(
|
|
|
agent_version.model_config_json,
|
|
agent_version.model_config_json,
|
|
@@ -488,6 +494,16 @@ class AgentApplicationService:
|
|
|
final_answer = response.content
|
|
final_answer = response.content
|
|
|
break
|
|
break
|
|
|
|
|
|
|
|
|
|
+ max_tool_calls = self._read_int(
|
|
|
|
|
+ agent_version.model_config_json,
|
|
|
|
|
+ "react_max_tool_calls",
|
|
|
|
|
+ default=self.react_max_tool_calls,
|
|
|
|
|
+ )
|
|
|
|
|
+ if tool_call_count >= max(max_tool_calls, 0):
|
|
|
|
|
+ final_answer = "Tool call budget exhausted."
|
|
|
|
|
+ react_step["observation"] = final_answer
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
tool_code = action.get("tool_code")
|
|
tool_code = action.get("tool_code")
|
|
|
matching_tools = [
|
|
matching_tools = [
|
|
|
item for item in selected_tools if item.tool_code == tool_code
|
|
item for item in selected_tools if item.tool_code == tool_code
|
|
@@ -505,11 +521,12 @@ class AgentApplicationService:
|
|
|
agent_run.input_json = {
|
|
agent_run.input_json = {
|
|
|
str(item_key): item_value for item_key, item_value in tool_input.items()
|
|
str(item_key): item_value for item_key, item_value in tool_input.items()
|
|
|
}
|
|
}
|
|
|
- current_invocations = self._invoke_selected_tools(
|
|
|
|
|
|
|
+ current_invocations = self._invoke_react_tool_with_retry(
|
|
|
agent_run=agent_run,
|
|
agent_run=agent_run,
|
|
|
agent_version=agent_version,
|
|
agent_version=agent_version,
|
|
|
- selected_tools=matching_tools[:1],
|
|
|
|
|
|
|
+ tool_ref=matching_tools[0],
|
|
|
)
|
|
)
|
|
|
|
|
+ tool_call_count += len(current_invocations)
|
|
|
agent_run.input_json = original_input_json
|
|
agent_run.input_json = original_input_json
|
|
|
tool_invocations.extend(current_invocations)
|
|
tool_invocations.extend(current_invocations)
|
|
|
observation = self._format_react_observation(current_invocations)
|
|
observation = self._format_react_observation(current_invocations)
|
|
@@ -535,6 +552,7 @@ class AgentApplicationService:
|
|
|
"agent_version_id": agent_version.id,
|
|
"agent_version_id": agent_version.id,
|
|
|
"react_enabled": True,
|
|
"react_enabled": True,
|
|
|
"react_steps": react_steps,
|
|
"react_steps": react_steps,
|
|
|
|
|
+ "react_tool_call_count": tool_call_count,
|
|
|
"tool_invocations": tool_invocations,
|
|
"tool_invocations": tool_invocations,
|
|
|
"skill_invocations": skill_invocations,
|
|
"skill_invocations": skill_invocations,
|
|
|
**memory_metadata,
|
|
**memory_metadata,
|
|
@@ -931,19 +949,85 @@ class AgentApplicationService:
|
|
|
def _format_react_instruction(
|
|
def _format_react_instruction(
|
|
|
self,
|
|
self,
|
|
|
*,
|
|
*,
|
|
|
|
|
+ agent_run: AgentRun,
|
|
|
selected_tools: list[AgentToolRefContract],
|
|
selected_tools: list[AgentToolRefContract],
|
|
|
skill_invocations: list[dict[str, JSONValue]],
|
|
skill_invocations: list[dict[str, JSONValue]],
|
|
|
) -> str:
|
|
) -> str:
|
|
|
|
|
+ tool_schemas = self._build_react_tool_schemas(
|
|
|
|
|
+ agent_run=agent_run,
|
|
|
|
|
+ selected_tools=selected_tools,
|
|
|
|
|
+ )
|
|
|
return (
|
|
return (
|
|
|
"Use ReAct JSON only. Respond with one JSON object per turn.\n"
|
|
"Use ReAct JSON only. Respond with one JSON object per turn.\n"
|
|
|
"To call a tool: "
|
|
"To call a tool: "
|
|
|
'{"action":"tool","tool_code":"code","input_json":{...}}\n'
|
|
'{"action":"tool","tool_code":"code","input_json":{...}}\n'
|
|
|
"To finish: "
|
|
"To finish: "
|
|
|
'{"action":"finish","answer":"final answer"}\n'
|
|
'{"action":"finish","answer":"final answer"}\n'
|
|
|
- f"Available tools: {[item.model_dump(mode='json') for item in selected_tools]}\n"
|
|
|
|
|
|
|
+ f"Available tools: {tool_schemas}\n"
|
|
|
f"Pre-run skill results: {skill_invocations}"
|
|
f"Pre-run skill results: {skill_invocations}"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ def _build_react_tool_schemas(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ agent_run: AgentRun,
|
|
|
|
|
+ selected_tools: list[AgentToolRefContract],
|
|
|
|
|
+ ) -> list[dict[str, JSONValue]]:
|
|
|
|
|
+ schemas: list[dict[str, JSONValue]] = []
|
|
|
|
|
+ for ref in selected_tools:
|
|
|
|
|
+ schema: dict[str, JSONValue] = {
|
|
|
|
|
+ "tool_code": ref.tool_code,
|
|
|
|
|
+ "tool_binding_id": ref.tool_binding_id,
|
|
|
|
|
+ "required": ref.required,
|
|
|
|
|
+ "config_json": ref.config_json,
|
|
|
|
|
+ }
|
|
|
|
|
+ if ref.tool_binding_id is not None and self.tool_client is not None:
|
|
|
|
|
+ try:
|
|
|
|
|
+ detail = self.tool_client.get_tool_binding_detail(
|
|
|
|
|
+ tenant_id=agent_run.tenant_id,
|
|
|
|
|
+ binding_id=ref.tool_binding_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ schema.update(
|
|
|
|
|
+ {
|
|
|
|
|
+ "name": detail.tool_definition.name,
|
|
|
|
|
+ "description": detail.tool_definition.description,
|
|
|
|
|
+ "tool_type": detail.tool_definition.tool_type,
|
|
|
|
|
+ "input_schema_json": detail.tool_version.input_schema_json or {},
|
|
|
|
|
+ "output_schema_json": detail.tool_version.output_schema_json or {},
|
|
|
|
|
+ "timeout_ms": detail.tool_version.timeout_ms,
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ except ToolServiceClientError as exc:
|
|
|
|
|
+ schema["schema_error"] = str(exc)
|
|
|
|
|
+ schemas.append(schema)
|
|
|
|
|
+ return schemas
|
|
|
|
|
+
|
|
|
|
|
+ def _invoke_react_tool_with_retry(
|
|
|
|
|
+ self,
|
|
|
|
|
+ *,
|
|
|
|
|
+ agent_run: AgentRun,
|
|
|
|
|
+ agent_version: AgentVersion,
|
|
|
|
|
+ tool_ref: AgentToolRefContract,
|
|
|
|
|
+ ) -> list[dict[str, JSONValue]]:
|
|
|
|
|
+ retry_count = self._read_int(
|
|
|
|
|
+ agent_version.model_config_json,
|
|
|
|
|
+ "react_tool_retry_count",
|
|
|
|
|
+ default=self.react_tool_retry_count,
|
|
|
|
|
+ )
|
|
|
|
|
+ attempts: list[dict[str, JSONValue]] = []
|
|
|
|
|
+ for attempt_index in range(max(retry_count, 0) + 1):
|
|
|
|
|
+ current = self._invoke_selected_tools(
|
|
|
|
|
+ agent_run=agent_run,
|
|
|
|
|
+ agent_version=agent_version,
|
|
|
|
|
+ selected_tools=[tool_ref],
|
|
|
|
|
+ )
|
|
|
|
|
+ for item in current:
|
|
|
|
|
+ item["attempt_index"] = attempt_index
|
|
|
|
|
+ attempts.extend(current)
|
|
|
|
|
+ if current and current[-1].get("status") == "completed":
|
|
|
|
|
+ break
|
|
|
|
|
+ return attempts
|
|
|
|
|
+
|
|
|
def _format_react_observation(
|
|
def _format_react_observation(
|
|
|
self,
|
|
self,
|
|
|
tool_invocations: list[dict[str, JSONValue]],
|
|
tool_invocations: list[dict[str, JSONValue]],
|