初步实现Route Agent

This commit is contained in:
asypost committed 2026-11-06 21:03:50 +08:00
1 parent 77ff26094d
commit c7274f6f00
12 files changed
+681 -251

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
from abc import abstractmethod
from collections.abc import Iterator
import json
from typing import Any
from openai.types.chat import ChatCompletionMessage, ChatCompletionMessageToolCallUnion
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
from pydantic import BaseModel, TypeAdapter
from mini_agent.agent.context import ConversationContext
from mini_agent.agent.message import Message
class BaseAgent:
@property
@abstractmethod
def id(self) -> str:
pass
@property
@abstractmethod
def name(self) -> str:
pass
@property
@abstractmethod
def description(self) -> str:
pass
@property
@abstractmethod
def compatibility(self) -> list[str]:
pass
@property
@abstractmethod
def system_prompt(self) -> str:
pass
def serialize_json(self, data: Any) -> str:
if isinstance(data, BaseModel):
return data.model_dump_json(ensure_ascii=False)
return json.dumps(data, ensure_ascii=False, default=str)
def _initialize_context(self, context: ConversationContext, question: str) -> None:
"""
初始化会话context
"""
if len(context.messages) == 0:
context.messages.append({"role": "system", "content": self.system_prompt})
context.messages.append({"role": "user", "content": question})
def _build_message_param(self, message: ChatCompletionMessage) -> ChatCompletionMessageParam:
"""
将ChatCompletionMessage转换成ChatCompletionMessageParam
"""
message_param: ChatCompletionMessageParam = {
"role": message.role,
"content": message.content,
}
if message.tool_calls:
adapter = TypeAdapter(list[ChatCompletionMessageToolCallUnion])
message_param["tool_calls"] = adapter.dump_python(message.tool_calls, exclude_none=True)
return message_param
@abstractmethod
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
pass
+58
View File
@@ -0,0 +1,58 @@
from abc import abstractmethod
import json
from typing import Any, override
from openai.types.chat import ChatCompletionMessageToolCallUnion
from mini_agent.agent.base_agent import BaseAgent
from mini_agent.agent.context import ConversationContext
from mini_agent.tools.tool_repository import ToolRepository
class BaseToolAgent(BaseAgent):
@property
@abstractmethod
def tool_repository(self) -> ToolRepository:
pass
@override
def _initialize_context(self, context: ConversationContext, question: str) -> None:
super()._initialize_context(context, question)
if len(context.discovered_tools) == 0 and self.tool_repository:
context.add_tool(self.tool_repository)
def _parse_tool_call(
self, tool_call: ChatCompletionMessageToolCallUnion
) -> tuple[str, dict[str, Any]]:
"""
解析Tool call,返回(tool_id, arguments)
"""
if tool_call.type != "function" and tool_call.type != "custom":
raise ValueError(f"不支持的工具调用类型{tool_call.type}")
if tool_call.type == "function":
tool_id = tool_call.function.name
raw_arguments = tool_call.function.arguments
else:
tool_id = tool_call.custom.name
raw_arguments = tool_call.custom.input
arguments = json.loads(raw_arguments)
if not isinstance(arguments, dict):
raise ValueError("工具参数必须是 JSON 对象")
return (tool_id, arguments)
def _execute_tool_call(
self, tool_id: str, arguments: dict[str, Any], context: ConversationContext
) -> Any:
"""
执行tool call
"""
observation = self.tool_repository.call_tool(tool_id, arguments)
# 更新当前可用工具
if tool_id == self.tool_repository.id:
available_tools = []
for item in observation:
if item.name not in context.discovered_tool_names:
context.add_tool(item)
available_tools.append(item.to_openai())
observation = available_tools
return observation
+3 -2
View File
@@ -6,6 +6,7 @@ from mini_agent.tools.tool import Tool
class ConversationContext(BaseModel):
conversation_id: str = Field(description="会话ID")
run_id: str = Field(description="当前会话轮id")
messages: list[ChatCompletionMessageParam] = Field(description="消息历史", default_factory=list)
_discovered_tools: list[Tool] = PrivateAttr(default_factory=list)
@@ -17,6 +18,6 @@ class ConversationContext(BaseModel):
return self._discovered_tools
def add_tool(self, tool: Tool):
if tool.name not in self.discovered_tool_names:
self.discovered_tool_names.append(tool.name)
if tool.id not in self.discovered_tool_names:
self.discovered_tool_names.append(tool.id)
self.discovered_tools.append(tool)
+48
View File
@@ -0,0 +1,48 @@
import os
from openai import OpenAI
from openai.types.chat import (
ChatCompletion,
ChatCompletionMessageParam,
ChatCompletionToolChoiceOptionParam,
ChatCompletionToolParam,
)
class LLMClient:
def __init__(
self,
model: str | None = None,
api_key: str | None = None,
base_url: str | None = None,
timeout: int | None = None,
) -> None:
self.model = model or os.getenv("LLM_MODEL_ID")
self.base_url = base_url or os.getenv("LLM_BASE_URL")
self.api_key = api_key or os.getenv("LLM_API_KEY")
self.timeout = timeout if timeout is not None else int(os.getenv("LLM_TIMEOUT", "60"))
if not all([self.model, self.base_url, self.api_key]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url,
timeout=self.timeout,
)
def think(
self,
messages: list[ChatCompletionMessageParam],
tools: list[ChatCompletionToolParam],
tool_choice: ChatCompletionToolChoiceOptionParam = "auto",
**kwargs,
) -> ChatCompletion:
assert self.model is not None
return self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=tools,
tool_choice=tool_choice,
**kwargs
)
+68 -24
View File
@@ -1,47 +1,91 @@
from datetime import datetime
from enum import StrEnum
from typing import Any
from typing import Any, Literal
from uuid import uuid4
from pydantic import BaseModel, Field
class TextContent(BaseModel):
type: Literal["text"] = "text"
text: str
class ThinkContent(BaseModel):
type: Literal["think"] = "think"
text: str
class ImageContent(BaseModel):
type: Literal["image"] = "image"
url: str
mime_type: str | None = None
class JsonContent(BaseModel):
type: Literal["json"] = "json"
data: Any
class ToolCallContent(BaseModel):
type: Literal["tool_call"] = "tool_call"
id: str
name: str
tool_call_id:str
arguments: dict[str, Any]
class ToolResultContent(BaseModel):
type: Literal["tool_result"] = "tool_result"
tool_call_id: str
result: Any = None
error: str | None = None
class ErrorContent(BaseModel):
type: Literal["error"] = "error"
message: str
code: str | None = None
class AudioContent(BaseModel):
type: Literal["audio"] = "audio"
url: str
mime_type: str | None = None
MessageContent = (
TextContent
| ThinkContent
| ImageContent
| JsonContent
| ToolCallContent
| ToolResultContent
| ErrorContent
)
class MessageRole(StrEnum):
USER = "user"
ASSISTANT = "assistant"
TOOL = "tool"
class MessageType(StrEnum):
TEXT = "text"
THINK = "think"
TOOL_CALL = "tool_call"
TOOL_RESULT = "tool_result"
class MessageStatus(StrEnum):
class RunStatus(StrEnum):
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
class Message(BaseModel):
id: str = Field(default_factory=lambda: str(uuid4()))
conversation_id: str
run_id: str | None = None
id: str = Field(description="消息ID", default_factory=lambda: str(uuid4()))
conversation_id: str = Field(description="会话ID")
run_id: str | None = Field(description="当前轮的ID", default=None)
run_status: RunStatus = Field(description="当前轮的状态")
role: MessageRole
type: MessageType
role: MessageRole = Field(description="当前角色")
content: Any = None
status: MessageStatus = MessageStatus.COMPLETED
tool_call_id: str | None = None
tool_name: str | None = None
arguments: dict[str, Any] | None = None
tool_call_result:Any |None = None
content: list[MessageContent] = Field(description="消息内容", default_factory=list)
created_at: datetime = Field(default_factory=lambda: datetime.now())
+91 -147
View File
@@ -1,154 +1,93 @@
from collections.abc import Iterator
import json
import os
from typing import Any
from typing import override
from uuid import uuid7
from openai import OpenAI
from openai.types.chat import ChatCompletionMessage, ChatCompletionMessageToolCallUnion
from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam
from pydantic import BaseModel, TypeAdapter
from mini_agent.agent.base_tool_agent import BaseToolAgent
from mini_agent.agent.context import ConversationContext
from mini_agent.agent.message import Message, MessageRole, MessageStatus, MessageType
from mini_agent.agent.llm_client import LLMClient
from mini_agent.agent.message import (
ErrorContent,
Message,
MessageRole,
RunStatus,
TextContent,
ThinkContent,
ToolCallContent,
ToolResultContent,
)
from mini_agent.tools.tavily_search import TavilySearch
from mini_agent.tools.tool_repository import ToolRepository
class ReActAgent:
SYSTEM_PROMPT = """
你是一个能够调用外部工具的智能助手。
工作规则:
1. 首先理解用户的问题,判断是否需要外部工具。
2. 如果需要工具,调用工具搜索接口搜索合适的工具。
3. 根据搜索结果选择合适的工具。
4. 工具执行后,根据返回结果判断下一步行动。
5. 当已经获得足够的信息,能够完整回答用户问题时,直接给出最终答案。
注意事项:
- 不允许编造工具执行结果。
- 工具搜索没有结果时,可以尝试调整关键词重新搜索。
- 如果工具无法完成任务,明确告知用户。
- 不要为了调用工具而调用工具。
- 只有在任务已经完成或明确无法继续时才结束回答。
"""
class ReActAgent(BaseToolAgent):
def __init__(
self,
tool_repository: ToolRepository,
model: str | None = None,
api_key: str | None = None,
base_url: str | None = None,
timeout: int | None = None,
llm_client: LLMClient,
max_steps: int = 10,
) -> None:
self.model = model or os.getenv("LLM_MODEL_ID")
self.base_url = base_url or os.getenv("LLM_BASE_URL")
self.api_key = api_key or os.getenv("LLM_API_KEY")
self.timeout = timeout if timeout is not None else int(os.getenv("LLM_TIMEOUT", "60"))
super().__init__()
if not all([self.model, self.base_url, self.api_key]):
raise ValueError("模型ID、API密钥和服务地址必须被提供或在.env文件中定义。")
self.client = OpenAI(
api_key=self.api_key,
base_url=self.base_url,
timeout=self.timeout,
)
self.tool_repos = tool_repository
self.client = llm_client
self.max_steps = max_steps
web_search_tool = TavilySearch()
web_search_tool.config(api_key=os.getenv("TAVILY_KEY", ""))
self.tool_repos.register(web_search_tool)
self._tool_repository = tool_repository
self.tool_repository.register(web_search_tool)
def serialize_json(self, data: Any) -> str:
if isinstance(data, BaseModel):
return data.model_dump_json()
return json.dumps(data, ensure_ascii=False, default=str)
@property
def id(self) -> str:
return "react_agent"
def _initialize_context(self, context: ConversationContext, question: str) -> None:
"""
初始化会话context
"""
if len(context.messages) == 0:
context.messages.append({"role": "system", "content": self.SYSTEM_PROMPT})
if len(context.discovered_tools) == 0:
context.add_tool(self.tool_repos)
context.messages.append({"role": "user", "content": question})
@property
def name(self) -> str:
return "ReAct Agent"
def _build_message_param(self, message: ChatCompletionMessage) -> ChatCompletionMessageParam:
"""
将ChatCompletionMessage转换成ChatCompletionMessageParam
"""
message_param: ChatCompletionMessageParam = {
"role": message.role,
"content": message.content,
}
@property
def description(self) -> str:
return "思考-回答-观察-再思考的模式来解决问题"
if message.tool_calls:
adapter = TypeAdapter(list[ChatCompletionMessageToolCallUnion])
message_param["tool_calls"] = adapter.dump_python(message.tool_calls, exclude_none=True)
return message_param
@property
def compatibility(self) -> list[str]:
return ["通用问题回答", "资料网络搜索"]
def _parse_tool_call(
self, tool_call: ChatCompletionMessageToolCallUnion
) -> tuple[str, dict[str, Any]]:
"""
解析Tool call,返回(tool_name, arguments)
"""
if tool_call.type != "function" and tool_call.type != "custom":
raise ValueError(f"不支持的工具调用类型{tool_call.type}")
if tool_call.type == "function":
tool_name = tool_call.function.name
raw_arguments = tool_call.function.arguments
else:
tool_name = tool_call.custom.name
raw_arguments = tool_call.custom.input
arguments = json.loads(raw_arguments)
if not isinstance(arguments, dict):
raise ValueError("工具参数必须是 JSON 对象")
return (tool_name, arguments)
@property
def tool_repository(self) -> ToolRepository:
return self._tool_repository
def _execute_tool_call(
self, tool_name: str, arguments: dict[str, Any], context: ConversationContext
) -> Any:
"""
执行tool call
"""
observation = self.tool_repos.call_tool(tool_name, arguments)
# 更新当前可用工具
if tool_name == self.tool_repos.name:
available_tools = []
for item in observation:
if item.name not in context.discovered_tool_names:
context.add_tool(item)
available_tools.append(self.tool_repos.tool_to_openai(item))
observation = available_tools
return observation
@property
def system_prompt(self) -> str:
return """
你是一个能够调用外部工具的智能助手。
工作规则:
1. 首先理解用户的问题,判断是否需要外部工具。
2. 如果需要工具,可以从工具库中搜索你所需要的工具。
3. 从工具搜索结果中选择合适的工具并调用执行。
4. 工具执行后,根据返回结果判断下一步行动。
5. 当已经获得足够的信息,能够完整回答用户问题时,直接给出最终答案。
注意事项:
- 不允许编造工具执行结果。
- 工具搜索没有结果时,可以尝试调整关键词重新搜索。
- 如果工具无法完成任务,明确告知用户。
- 不要为了调用工具而调用工具。
- 只有在任务已经完成或明确无法继续时才结束回答。
"""
@override
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
self._initialize_context(context, question)
run_id = str(uuid7())
yield Message(
conversation_id=context.conversation_id,
role=MessageRole.USER,
run_id=run_id,
status=MessageStatus.RUNNING,
type=MessageType.TEXT,
content=question,
)
for _ in range(self.max_steps):
tools = [self.tool_repos.tool_to_openai(tool) for tool in context.discovered_tools]
tools = [tool.to_openai() for tool in context.discovered_tools]
assert self.model is not None
response = self.client.chat.completions.create(
model=self.model,
response = self.client.think(
messages=context.messages,
tools=tools,
tool_choice="auto",
@@ -166,54 +105,58 @@ class ReActAgent:
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.COMPLETED,
role=MessageRole.ASSISTANT,
content=message.content,
status=MessageStatus.COMPLETED,
type=MessageType.TEXT,
content=[TextContent(text=message.content)],
)
return
elif reasoning_content := getattr(message, "reasoning_content", None):
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.RUNNING,
role=MessageRole.ASSISTANT,
type=MessageType.THINK,
content=reasoning_content,
status=MessageStatus.RUNNING,
content=[ThinkContent(text=reasoning_content)],
)
else:
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.FAILED,
role=MessageRole.ASSISTANT,
type=MessageType.TEXT,
status=MessageStatus.FAILED,
content="模型没有返回有效内容",
content=[ErrorContent(message="模型没有返回有效内容")],
)
return
if message.tool_calls:
for tool_call in message.tool_calls:
tool_name = None
tool_id = None
arguments = {}
tool_call_status = MessageStatus.RUNNING
tool_call_error: str | None = None
try:
tool_name, arguments = self._parse_tool_call(tool_call)
tool_id, arguments = self._parse_tool_call(tool_call)
tool = self.tool_repository.get_tool(id=tool_id)
if not tool:
raise KeyError(f"{tool_id}工具不存在")
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
role=MessageRole.ASSISTANT,
status=MessageStatus.RUNNING,
type=MessageType.TOOL_CALL,
content=f"call tool:{tool_name}({arguments})",
tool_name=tool_name,
arguments=arguments,
run_status=RunStatus.RUNNING,
content=[
ToolCallContent(
tool_call_id=tool_call.id,
id=tool.id,
name=tool.name,
arguments=arguments,
)
],
)
observation = self._execute_tool_call(tool_name, arguments, context)
observation = self._execute_tool_call(tool_id, arguments, context)
except Exception as e:
tool_call_status = MessageStatus.FAILED
tool_call_error = str(e)
observation = {"error": str(e)}
# 统一记录工具执行结果
@@ -228,20 +171,21 @@ class ReActAgent:
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.RUNNING,
role=MessageRole.TOOL,
type=MessageType.TOOL_RESULT,
status=tool_call_status,
content=f"call {tool_name or 'unknown'} result:{self.serialize_json(observation)}",
tool_name=tool_name,
arguments=arguments,
tool_call_result=observation,
content=[
ToolResultContent(
tool_call_id=tool_call.id,
result="执行成功" if tool_call_error is None else None,
error=tool_call_error,
)
],
)
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.FAILED,
role=MessageRole.ASSISTANT,
status=MessageStatus.FAILED,
type=MessageType.TEXT,
content=f"已达到最大执行步数 ({self.max_steps}),任务未完成",
content=[ErrorContent(message=f"已达到最大执行步数 ({self.max_steps}),任务未完成")],
)
+236
View File
@@ -0,0 +1,236 @@
from collections.abc import Iterator
from typing import Literal, override
from uuid import uuid7
from pydantic import BaseModel, Field
from mini_agent.agent.base_agent import BaseAgent
from mini_agent.agent.context import ConversationContext
from mini_agent.agent.llm_client import LLMClient
from mini_agent.agent.message import (
ErrorContent,
Message,
MessageRole,
RunStatus,
TextContent,
ThinkContent,
)
class RouteDecision(BaseModel):
action: Literal["route", "direct", "clarify", "reject"] = Field(description="路由动作")
agent_id: str | None = Field(description="Agent id", default=None)
reason: str | None = Field(description="路由理由", default=None)
message: str | None = Field(description="回复用户的消息", default=None)
task: str | None = Field(description="交给子Agent的任务描述")
class RouteAgent(BaseAgent):
def __init__(self, llm_client: LLMClient, max_steps: int = 10) -> None:
super().__init__()
self.sub_agents: dict[str, BaseAgent] = {}
self.llm_client = llm_client
self.max_steps = max_steps
def register_agent(self, agent: BaseAgent):
if agent.id not in self.sub_agents:
self.sub_agents[agent.id] = agent
else:
raise ValueError(f"{agent.id}已经存在")
@property
def system_prompt(self) -> str:
template = """
# 角色
你是一个 AI Agent 路由调度器(Route Agent),负责理解用户请求,并将其分配给最合适的专业 Agent。
你不直接处理用户的业务问题,不执行工具,不编造业务数据。你的核心职责是识别用户意图、选择目标 Agent,并生成清晰的路由决策。
# 核心职责
1. 理解用户当前请求的真实意图。
2. 结合当前会话上下文,理解用户的追问、省略表达和上下文引用。
3. 从系统提供的 Agent 列表中选择最合适的 Agent。
4. 判断用户请求是否需要补充信息。
5. 判断当前请求是否可以直接回答,还是必须交给专业 Agent。
6. 输出符合指定结构的路由决策。
# 路由规则
## 1. Agent 选择
- 必须从系统提供的可用 Agent 列表中选择目标 Agent。
- 优先选择职责与用户请求最匹配的 Agent。
- 不得虚构 Agent,不得调用未注册的 Agent。
- 如果一个请求涉及多个领域,优先选择能够完成主要任务的 Agent。
- 如果需要多个 Agent 协作,只有在系统明确支持多 Agent 调度时才能提出多个目标。
## 2. 上下文理解
- 用户当前消息优先级最高,但不能脱离历史对话理解。
- 如果用户使用“继续”“刚才那个”“再查一下”等表达,应结合历史消息确定实际任务。
- 如果用户是在纠正上一轮结果,应将请求交给原任务对应的 Agent。
- 如果用户明确提出新的任务,应重新进行路由,不要机械沿用上一轮 Agent。
- 不要因为历史上使用过某个 Agent,就将无关的新问题继续分配给它。
## 3. 信息不足
- 如果缺少执行任务所必需的信息,选择需要补充信息的处理方式。
- 只询问真正影响任务执行的必要信息。
- 不要为了补充非必要信息而阻断任务。
- 如果可以使用合理默认值,应优先继续执行。
## 4. 直接回答
以下情况可以直接回答,不需要调用专业 Agent:
- 简单的常识性问题。
- 问候、感谢、告别等日常交流。
- 对当前 Agent 工作状态的简单询问。
- 不涉及外部数据查询的简单解释。
- 用户只是要求修改、解释或讨论当前对话中已经提供的内容。
直接回答时,不得伪装成调用了专业 Agent。
## 5. 无法路由
出现以下情况时,选择无法路由:
- 用户意图无法理解。
- 系统没有合适的 Agent。
- 用户请求超出所有可用 Agent 的职责范围。
- 需要执行的操作不被系统支持。
应简要说明原因,不要虚构处理能力。
# 水利业务路由参考
系统提供以下 Agent,参考这些Agent的职责做出选择:
{}
以上只是路由参考,实际选择必须以系统提供的 Agent 定义为准。
# 输出要求
必须输出符合系统指定 JSON Schema 的结构化结果。
路由决策至少包含以下语义:
- action:决策类型,可选 route、direct、clarify、reject。
- agent_id:目标 Agent 标识。非 route 决策时为空。
- reason:路由原因。
- message:直接回复用户的内容,或者需要向用户询问的内容。
- task:交给目标 Agent 的任务描述,尽可能保留用户原始需求和必要上下文。
不要输出 JSON 以外的解释文字。
# 重要约束
1. 你只负责路由,不负责执行专业任务。
2. 不得调用业务工具。
3. 不得虚构系统中不存在的 Agent。
4. 不得擅自修改用户需求。
5. 不得丢失用户请求中的关键参数。
6. 不要将工具调用需求误判为普通聊天。
7. 对于明确的业务查询,优先路由给专业 Agent。
8. 输出必须符合指定 JSON Schema,不允许输出额外字段。
"""
agent_descriptions = [
f"-{agent.id}: {','.join(agent.compatibility)}" for agent in self.sub_agents.values()
]
return template.format("\n".join(agent_descriptions))
@override
def run(self, question: str, context: ConversationContext) -> Iterator[Message]:
self._initialize_context(context=context, question=question)
run_id = context.run_id
for _ in range(self.max_steps):
response = self.llm_client.think(
messages=context.messages,
tools = [],
tool_choice="auto",
response_format={
"type": "json_schema",
"json_schema": {
"name": "agent_decision",
"strict": True,
"schema": RouteDecision.model_json_schema(),
},
},
)
message = response.choices[0].message
# 保存模型返回消息
message_param = self._build_message_param(message)
context.messages.append(message_param)
if message.content:
decision = RouteDecision.model_validate_json(message.content)
if decision.action == "clarify" or decision.action == "direct":
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.COMPLETED,
role=MessageRole.ASSISTANT,
content=[TextContent(text=decision.message or "")],
)
if decision.action == "direct":
return
elif decision.action == "route":
agent = self.sub_agents.get(decision.agent_id or "")
if not agent:
context.messages.append(
{"role": "user", "content": f"{decision.agent_id} Agent不存在"}
)
else:
yield Message(
conversation_id=context.conversation_id,
run_id=context.run_id,
run_status=RunStatus.RUNNING,
role = MessageRole.ASSISTANT,
content= [ThinkContent(text=decision.reason or "正在路由请求")]
)
agent_context = ConversationContext(conversation_id=context.conversation_id,run_id=str(uuid7()))
for agent_message in agent.run(
context=agent_context, question=decision.task or question
):
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=agent_message.run_status,
content=agent_message.content,
role=agent_message.role,
)
if (
agent_message.run_status == RunStatus.COMPLETED
or agent_message.run_status == RunStatus.FAILED
):
return
elif reasoning_content := getattr(message, "reasoning_content", None):
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.RUNNING,
role=MessageRole.ASSISTANT,
content=[ThinkContent(text=reasoning_content)],
)
else:
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.FAILED,
role=MessageRole.ASSISTANT,
content=[ErrorContent(message="模型没有返回有效内容")],
)
return
yield Message(
conversation_id=context.conversation_id,
run_id=run_id,
run_status=RunStatus.FAILED,
role=MessageRole.ASSISTANT,
content=[ErrorContent(message=f"已达到最大执行步数 ({self.max_steps}),任务未完成")],
)