mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-13 07:50:00 +08:00
♻️ refactor(agent): 重构 Agent 状态管理与执行器流程,优化 Token 预估与自愈反思机制 (#2150)
- 统一使用 `run_context.run.messages` 作为消息历史的单一数据源,清理 `AgentState` 冗余字段 - 将工具消息装配逻辑 `assemble_tool_message` 提取并重构至 `ToolExecutor` - 引入 `token_drift` 动态校准偏移量,并精确计算工具与系统提示词的 Token 开销 - 重构 `ReflexionCapability` 自愈反思引擎,基于异常多态与模板字典动态生成反馈提示词 - 支持通过 `resolve_model_capabilities` 解析并合并用户自定义的模型能力覆盖 - 在执行器循环中支持 `should_reset_cycle`,以优雅处理外部干预(如用户追加指示) - 扩展 `capabilities` 中对 `gpt-[5-9]*` 等新型号模型的能力定义与上下文限制 Co-authored-by: webjoin111 <455457521@qq.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from zhenxun.services.ai.config import get_llm_config
|
||||
from zhenxun.services.ai.context.knowledge.base import BaseKnowledge
|
||||
from zhenxun.services.ai.context.memory.builder import MemoryBuilder
|
||||
from zhenxun.services.ai.context.memory.models import MemoryConfig
|
||||
from zhenxun.services.ai.core.engine.token_counter import token_counter
|
||||
from zhenxun.services.ai.core.exceptions import (
|
||||
ControlFlowExit,
|
||||
)
|
||||
@@ -29,6 +30,7 @@ from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.flow.core.models import InterventionPolicy
|
||||
from zhenxun.services.ai.guardrails import GuardrailSource, parse_guardrails
|
||||
from zhenxun.services.ai.llm.builder import IntentBuilder
|
||||
from zhenxun.services.ai.llm.manager import resolve_model_capabilities
|
||||
from zhenxun.services.ai.message_builder import MessageBuilder
|
||||
from zhenxun.services.ai.run import (
|
||||
AgentRunResult,
|
||||
@@ -700,6 +702,10 @@ class Agent(
|
||||
self.model_name() if callable(self.model_name) else self.model_name
|
||||
)
|
||||
|
||||
resources.model_capabilities = await resolve_model_capabilities(
|
||||
context.run.current_model
|
||||
)
|
||||
|
||||
return state, resources
|
||||
|
||||
async def on_context_build(
|
||||
@@ -752,10 +758,29 @@ class Agent(
|
||||
if tool_payload.injected_prompts:
|
||||
static_prompts_list.extend(tool_payload.injected_prompts)
|
||||
|
||||
final_tools = await ToolBuilder.prepare_effective_tools(
|
||||
effective_tools, context, self.tool_filters, run_scoped_cap
|
||||
)
|
||||
|
||||
base_overhead = 0
|
||||
for sp in static_prompts_list:
|
||||
if sp:
|
||||
base_overhead += token_counter._count_text(str(sp))
|
||||
for m in dynamic_messages:
|
||||
base_overhead += token_counter.count_message(
|
||||
m, context.run.current_model or ""
|
||||
)
|
||||
for t in final_tools:
|
||||
t_def = getattr(t, "_dynamic_def", None)
|
||||
if t_def and getattr(t_def, "parameters", None):
|
||||
base_overhead += token_counter.count_tools_schema(t_def.parameters)
|
||||
|
||||
messages_for_run = (
|
||||
await memory_context.read(
|
||||
model_name=context.run.current_model or "",
|
||||
capabilities=resources.model_capabilities,
|
||||
override_history=resources.config.message_history,
|
||||
base_overhead=base_overhead,
|
||||
)
|
||||
if memory_context
|
||||
else []
|
||||
@@ -772,17 +797,14 @@ class Agent(
|
||||
if resources.memory_context:
|
||||
await resources.memory_context.write([msgs[-1]])
|
||||
|
||||
final_tools = await ToolBuilder.prepare_effective_tools(
|
||||
effective_tools, context, self.tool_filters, run_scoped_cap
|
||||
)
|
||||
context.session.append_only_manager.build(static_prompts_list, final_tools)
|
||||
context.session.append_only_manager.sync_messages(messages_for_run)
|
||||
|
||||
state.messages = messages_for_run
|
||||
context.run.messages = messages_for_run
|
||||
state.tools = final_tools
|
||||
state.static_system_prompt = static_prompts_list
|
||||
state.dynamic_system_messages = dynamic_messages
|
||||
state.origin_msg_len = len(messages_for_run)
|
||||
state._origin_msg_len = len(messages_for_run)
|
||||
|
||||
async def on_execute(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
@@ -793,7 +815,7 @@ class Agent(
|
||||
for tk in resources.toolkits:
|
||||
if hasattr(tk, "before_llm_request"):
|
||||
await DependencyInjector.invoke(
|
||||
tk.before_llm_request, {"messages": state.messages}, context
|
||||
tk.before_llm_request, {"messages": context.run.messages}, context
|
||||
)
|
||||
|
||||
config_exec = resources.config.executor if resources.config else None
|
||||
@@ -802,12 +824,11 @@ class Agent(
|
||||
or self.executor
|
||||
or StandardAgentExecutor(directive_handlers=self.directive_handlers)
|
||||
)
|
||||
resources.model_name = context.run.current_model
|
||||
raw_result: AgentRunResult[Any] = await executor.run(
|
||||
state=state, resources=resources
|
||||
)
|
||||
|
||||
new_msgs = raw_result.messages[state.origin_msg_len :]
|
||||
new_msgs = raw_result.messages[state._origin_msg_len :]
|
||||
if resources.memory_context:
|
||||
await resources.memory_context.write(new_msgs)
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ async def handle_submit_structured(
|
||||
)
|
||||
logger.debug("✅ 拦截到结构化结果提交,结束循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
state.pending_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=None,
|
||||
messages=state.messages,
|
||||
messages=list(resources.run_context.run.messages),
|
||||
structured_data=parsed_obj,
|
||||
usage=state.usage,
|
||||
)
|
||||
@@ -85,10 +85,10 @@ async def handle_end_run(
|
||||
)
|
||||
logger.debug("✅ 捕获到工具发出的终止信号,提前结束推理循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
state.pending_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=output,
|
||||
messages=state.messages,
|
||||
messages=list(resources.run_context.run.messages),
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
@@ -106,10 +106,10 @@ async def handle_handoff(
|
||||
output_text = f"已触发控制权移交 -> {handoff.target}。原因: {handoff.reason}"
|
||||
logger.info(f"✅ 拦截到移交(Handoff)信号: 移交给 -> {handoff.target}。结束循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
state.pending_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=output_text,
|
||||
messages=state.messages,
|
||||
messages=list(resources.run_context.run.messages),
|
||||
usage=state.usage,
|
||||
handoff=handoff,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import json
|
||||
from typing import Any, Literal, cast
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, cast
|
||||
|
||||
from zhenxun.services.ai.capabilities import CombinedCapability
|
||||
from zhenxun.services.ai.core.engine.context_renderer import ContextConverter
|
||||
@@ -11,22 +10,15 @@ from zhenxun.services.ai.core.engine.token_counter import (
|
||||
token_counter,
|
||||
)
|
||||
from zhenxun.services.ai.core.exceptions import (
|
||||
ControlFlowExit,
|
||||
UpstreamServerException,
|
||||
)
|
||||
from zhenxun.services.ai.core.messages import (
|
||||
AgentMessage,
|
||||
AssistantMessage,
|
||||
AudioPart,
|
||||
ChatRequest,
|
||||
ChatResponse,
|
||||
FilePart,
|
||||
ImagePart,
|
||||
LLMMessage,
|
||||
TextPart,
|
||||
ToolCallPart,
|
||||
ToolReturnPart,
|
||||
VideoPart,
|
||||
)
|
||||
from zhenxun.services.ai.core.models import CancellationToken, LLMContext, ToolChoice
|
||||
from zhenxun.services.ai.core.options import GenerationConfig
|
||||
@@ -40,11 +32,11 @@ from zhenxun.services.ai.flow.agent.models import AgentRunResources, AgentState
|
||||
from zhenxun.services.ai.llm.engine.router import LLMOrchestrator
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext
|
||||
from zhenxun.services.ai.run.models import OutputDataT
|
||||
from zhenxun.services.ai.run.session import SessionInfo, session_manager
|
||||
from zhenxun.services.ai.run.session import session_manager
|
||||
from zhenxun.services.ai.tools.engine.executor import ToolExecutor
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
from zhenxun.services.ai.utils.logger import log_agent as logger
|
||||
from zhenxun.utils.pydantic_compat import dump_json_safely, model_construct
|
||||
from zhenxun.utils.pydantic_compat import model_construct
|
||||
|
||||
from .directive import (
|
||||
DirectiveHandlerFunc,
|
||||
@@ -68,29 +60,42 @@ class BaseAgentExecutor(ABC):
|
||||
await self.on_start(state, resources)
|
||||
|
||||
try:
|
||||
for cycle_index in range(resources.config.max_cycles):
|
||||
state.current_cycle = cycle_index
|
||||
cycle_count = 0
|
||||
while cycle_count < resources.config.max_cycles:
|
||||
state.current_cycle = cycle_count
|
||||
state.should_reset_cycle = False
|
||||
await self.on_cycle_start(state, resources)
|
||||
|
||||
await self.build_llm_request(state, resources)
|
||||
await self.execute_llm(state, resources)
|
||||
|
||||
await self.handle_llm_response(state, resources)
|
||||
if state.should_reset_cycle:
|
||||
cycle_count = 0
|
||||
continue
|
||||
if state.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
assert state.pending_result is not None
|
||||
return state.pending_result
|
||||
|
||||
await self.filter_tool_calls(state, resources)
|
||||
if state.should_reset_cycle:
|
||||
cycle_count = 0
|
||||
continue
|
||||
if state.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
assert state.pending_result is not None
|
||||
return state.pending_result
|
||||
|
||||
await self.execute_tools(state, resources)
|
||||
|
||||
await self.handle_tool_results(state, resources)
|
||||
if state.should_reset_cycle:
|
||||
cycle_count = 0
|
||||
continue
|
||||
if state.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
assert state.pending_result is not None
|
||||
return state.pending_result
|
||||
|
||||
cycle_count += 1
|
||||
|
||||
return await self.on_fallback(state, resources)
|
||||
except Exception as e:
|
||||
@@ -179,23 +184,45 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
directive_handlers or {}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_tool_overhead(tools: Iterable[Any] | None) -> int:
|
||||
"""辅助方法:计算工具集合的 Schema Token 开销"""
|
||||
if not tools:
|
||||
return 0
|
||||
overhead = 0
|
||||
for t in tools:
|
||||
if isinstance(t, dict) and "function" in t:
|
||||
overhead += token_counter.count_tools_schema(
|
||||
t["function"].get("parameters", {})
|
||||
)
|
||||
elif hasattr(t, "get_definition"):
|
||||
t_def = getattr(t, "_dynamic_def", None)
|
||||
if t_def and getattr(t_def, "parameters", None):
|
||||
overhead += token_counter.count_tools_schema(t_def.parameters)
|
||||
return overhead
|
||||
|
||||
def _can_retry_via_llm(self, result: ToolResult) -> bool:
|
||||
"""通过新版的专属字段直接判断是否允许重试"""
|
||||
return result.is_retryable
|
||||
|
||||
def _check_follow_up(
|
||||
self, state: AgentState, resources: AgentRunResources, session_info: SessionInfo
|
||||
async def _check_follow_up(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> bool:
|
||||
"""检查追加队列,排空并合并数据到上下文,返回是否发现新消息"""
|
||||
"""检查追加队列,排空并合并数据到上下文,更新状态机标志位"""
|
||||
session_id = resources.run_context.session_id or "default_session"
|
||||
session_info = await session_manager.get_or_create(session_id)
|
||||
follow_ups = session_info.follow_up_queue.drain()
|
||||
if follow_ups:
|
||||
for fm in follow_ups:
|
||||
state.messages.append(LLMMessage.user(f"💬 [用户追加指示]:{fm}"))
|
||||
resources.run_context.run.messages.append(
|
||||
LLMMessage.user(f"💬 [用户追加指示]:{fm}")
|
||||
)
|
||||
resources.run_context.session.append_only_manager.sync_messages(
|
||||
state.messages
|
||||
resources.run_context.run.messages
|
||||
)
|
||||
state.is_finished = False
|
||||
state.final_result = None
|
||||
state.pending_result = None
|
||||
state.should_reset_cycle = True
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -224,13 +251,13 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
|
||||
await run_context.run.emit(
|
||||
LLMStartEvent(
|
||||
model_name=resources.model_name or "unknown",
|
||||
model_name=resources.run_context.run.current_model or "unknown",
|
||||
messages=flattened_messages,
|
||||
)
|
||||
)
|
||||
|
||||
response = await self._execute_model_request(
|
||||
model_name=resources.model_name,
|
||||
model_name=resources.run_context.run.current_model,
|
||||
messages=flattened_messages,
|
||||
config=resources.generation_config or GenerationConfig(),
|
||||
run_context=run_context,
|
||||
@@ -266,8 +293,16 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
if usage_obj.completion_tokens > 0:
|
||||
assistant_message.token_cost = usage_obj.completion_tokens
|
||||
|
||||
state.messages.append(assistant_message)
|
||||
run_context.session.append_only_manager.sync_messages(state.messages)
|
||||
if usage_obj.prompt_tokens > 0:
|
||||
est_prompt_tokens = token_counter.count_context(
|
||||
messages, resources.run_context.run.current_model or "", base_overhead=0
|
||||
)
|
||||
tool_overhead = self._calculate_tool_overhead(tools)
|
||||
est_total = est_prompt_tokens + tool_overhead
|
||||
state.token_drift = usage_obj.prompt_tokens - est_total
|
||||
|
||||
run_context.run.messages.append(assistant_message)
|
||||
run_context.session.append_only_manager.sync_messages(run_context.run.messages)
|
||||
|
||||
return response
|
||||
|
||||
@@ -308,97 +343,53 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
)
|
||||
|
||||
async def on_start(self, state: AgentState, resources: AgentRunResources) -> None:
|
||||
resources.run_context.run.messages = state.messages
|
||||
|
||||
async def _execute_phase(
|
||||
self,
|
||||
phase_func: Callable[[AgentState, AgentRunResources], Awaitable[None]],
|
||||
state: AgentState,
|
||||
resources: AgentRunResources,
|
||||
session_info: SessionInfo,
|
||||
) -> Literal["NEXT", "RESET_CYCLE", "FINISH"]:
|
||||
"""统一处理阶段执行与状态机完成/追加检查的高阶函数"""
|
||||
await phase_func(state, resources)
|
||||
if state.is_finished:
|
||||
if self._check_follow_up(state, resources, session_info):
|
||||
return "RESET_CYCLE"
|
||||
return "FINISH"
|
||||
return "NEXT"
|
||||
|
||||
async def run(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> AgentRunResult[OutputDataT]:
|
||||
"""覆盖基类的模板方法,实现灵活的 while 控制流和 FOLLOW_UP 合并"""
|
||||
await self.on_start(state, resources)
|
||||
session_info = await session_manager.get_or_create(
|
||||
resources.run_context.session_id or "default_session"
|
||||
)
|
||||
|
||||
try:
|
||||
cycle_count = 0
|
||||
while cycle_count < resources.config.max_cycles:
|
||||
state.current_cycle = cycle_count
|
||||
await self.on_cycle_start(state, resources)
|
||||
|
||||
await self.build_llm_request(state, resources)
|
||||
await self.execute_llm(state, resources)
|
||||
|
||||
status = await self._execute_phase(
|
||||
self.handle_llm_response, state, resources, session_info
|
||||
)
|
||||
if status == "RESET_CYCLE":
|
||||
cycle_count = 0
|
||||
continue
|
||||
if status == "FINISH":
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
status = await self._execute_phase(
|
||||
self.filter_tool_calls, state, resources, session_info
|
||||
)
|
||||
if status == "RESET_CYCLE":
|
||||
cycle_count = 0
|
||||
continue
|
||||
if status == "FINISH":
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
await self.execute_tools(state, resources)
|
||||
|
||||
status = await self._execute_phase(
|
||||
self.handle_tool_results, state, resources, session_info
|
||||
)
|
||||
if status == "RESET_CYCLE":
|
||||
cycle_count = 0
|
||||
continue
|
||||
if status == "FINISH":
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
cycle_count += 1
|
||||
|
||||
if self._check_follow_up(state, resources, session_info):
|
||||
return await self.run(state, resources)
|
||||
|
||||
return await self.on_fallback(state, resources)
|
||||
except Exception as e:
|
||||
raise e
|
||||
pass
|
||||
|
||||
async def on_cycle_start(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
state.current_request_messages = []
|
||||
state.current_response = None
|
||||
state.current_tool_calls = []
|
||||
state.current_tool_results = []
|
||||
state.should_reset_cycle = False
|
||||
|
||||
cancellation_token = resources.run_context.run.cancellation_token
|
||||
if cancellation_token:
|
||||
cancellation_token.raise_if_cancelled()
|
||||
|
||||
try:
|
||||
est_tokens = token_counter.count_context(
|
||||
state.messages, resources.model_name or "", base_overhead=0
|
||||
base_overhead = 0
|
||||
if state.static_system_prompt:
|
||||
sp_list = (
|
||||
state.static_system_prompt
|
||||
if isinstance(state.static_system_prompt, list)
|
||||
else [state.static_system_prompt]
|
||||
)
|
||||
for sp in sp_list:
|
||||
if sp:
|
||||
base_overhead += token_counter._count_text(str(sp))
|
||||
for m in state.dynamic_system_messages:
|
||||
base_overhead += token_counter.count_message(
|
||||
m, resources.run_context.run.current_model or ""
|
||||
)
|
||||
base_overhead += self._calculate_tool_overhead(state.tools)
|
||||
|
||||
est_tokens = (
|
||||
token_counter.count_context(
|
||||
resources.run_context.run.messages,
|
||||
resources.run_context.run.current_model or "",
|
||||
base_overhead=base_overhead,
|
||||
)
|
||||
+ state.token_drift
|
||||
)
|
||||
|
||||
est_tokens = max(est_tokens, 0)
|
||||
|
||||
logger.debug(
|
||||
f"(Iter {state.current_cycle + 1}) "
|
||||
f"预估将消耗 {est_tokens} Token "
|
||||
f"(Model: {resources.model_name or 'Unknown'})"
|
||||
f"(Model: {resources.run_context.run.current_model or 'Unknown'})"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -413,8 +404,12 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
steer_msgs = session_info.steer_queue.drain()
|
||||
if steer_msgs:
|
||||
for sm in steer_msgs:
|
||||
state.messages.append(LLMMessage.user(f"💬 [用户实时修正指示]:{sm}"))
|
||||
run_context.session.append_only_manager.sync_messages(state.messages)
|
||||
run_context.run.messages.append(
|
||||
LLMMessage.user(f"💬 [用户实时修正指示]:{sm}")
|
||||
)
|
||||
run_context.session.append_only_manager.sync_messages(
|
||||
run_context.run.messages
|
||||
)
|
||||
|
||||
messages_to_send = []
|
||||
if state.static_system_prompt:
|
||||
@@ -439,7 +434,7 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
if prompt_text and prompt_text.strip():
|
||||
messages_to_send.append(LLMMessage.system(prompt_text))
|
||||
|
||||
messages_to_send.extend(state.messages)
|
||||
messages_to_send.extend(run_context.run.messages)
|
||||
|
||||
state.current_request_messages = messages_to_send
|
||||
|
||||
@@ -466,13 +461,16 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
if not response.tool_calls:
|
||||
logger.debug("✅ 模型未请求工具调用,推理循环结束。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
state.pending_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=response.text,
|
||||
messages=state.messages,
|
||||
messages=list(resources.run_context.run.messages),
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
if state.is_finished:
|
||||
await self._check_follow_up(state, resources)
|
||||
|
||||
async def filter_tool_calls(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
@@ -522,14 +520,16 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
logger.info("✅ 无本地客户端工具需执行,推理循环平滑结束。")
|
||||
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
state.pending_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=response.text,
|
||||
messages=state.messages,
|
||||
messages=list(resources.run_context.run.messages),
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
state.current_tool_calls = client_tool_calls
|
||||
if state.is_finished:
|
||||
await self._check_follow_up(state, resources)
|
||||
|
||||
async def execute_tools(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
@@ -565,51 +565,6 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
tool_results = await asyncio.gather(*exec_tasks, return_exceptions=True)
|
||||
state.current_tool_results = tool_results
|
||||
|
||||
def _assemble_tool_message(
|
||||
self,
|
||||
original_call: ToolCallPart,
|
||||
res_or_exc: BaseException | tuple[ToolCallPart, ToolResult],
|
||||
tool_res: ToolResult | None,
|
||||
state: AgentState,
|
||||
) -> LLMMessage:
|
||||
"""负责处理异常、解析多模态、序列化,并装配为最终的工具消息载体"""
|
||||
media_parts = []
|
||||
final_content = "Success"
|
||||
|
||||
if isinstance(res_or_exc, BaseException):
|
||||
if isinstance(res_or_exc, ControlFlowExit):
|
||||
raise res_or_exc
|
||||
final_content = json.dumps(
|
||||
{"error": str(res_or_exc), "status": "failed"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
elif tool_res is not None:
|
||||
if isinstance(tool_res.output, list):
|
||||
texts = []
|
||||
for item in tool_res.output:
|
||||
if isinstance(item, ImagePart | AudioPart | VideoPart | FilePart):
|
||||
media_parts.append(item)
|
||||
elif isinstance(item, TextPart):
|
||||
texts.append(item.text)
|
||||
else:
|
||||
texts.append(str(item))
|
||||
final_content = " ".join(texts) if texts else "Success"
|
||||
elif isinstance(tool_res.output, str):
|
||||
final_content = tool_res.output
|
||||
else:
|
||||
final_content = dump_json_safely(tool_res.output, ensure_ascii=False)
|
||||
|
||||
tool_usage = getattr(tool_res, "usage", None)
|
||||
if tool_usage is not None:
|
||||
state.usage += tool_usage
|
||||
|
||||
msg = LLMMessage.tool_response(
|
||||
original_call.id, original_call.tool_name, final_content
|
||||
)
|
||||
if media_parts:
|
||||
msg.content.extend(media_parts)
|
||||
return msg
|
||||
|
||||
async def handle_tool_results(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
@@ -627,10 +582,12 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
_, raw_tool_res = res_or_exc
|
||||
tool_res = raw_tool_res
|
||||
|
||||
msg = self._assemble_tool_message(
|
||||
original_call, res_or_exc, tool_res, state
|
||||
msg, usage = ToolExecutor.assemble_tool_message(
|
||||
original_call, res_or_exc, tool_res
|
||||
)
|
||||
state.messages.append(msg)
|
||||
if usage:
|
||||
state.usage += usage
|
||||
resources.run_context.run.messages.append(msg)
|
||||
|
||||
if tool_res and getattr(tool_res, "directive", None):
|
||||
ns = getattr(resources.run_context.session, "namespace", "global")
|
||||
@@ -642,8 +599,10 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
await handler(state, resources, tool_res)
|
||||
if state.is_finished:
|
||||
resources.run_context.session.append_only_manager.sync_messages(
|
||||
state.messages
|
||||
resources.run_context.run.messages
|
||||
)
|
||||
if state.is_finished:
|
||||
await self._check_follow_up(state, resources)
|
||||
return
|
||||
else:
|
||||
logger.warning(
|
||||
@@ -651,7 +610,11 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
f"的指令处理器 (Namespace: {ns})"
|
||||
)
|
||||
|
||||
resources.run_context.session.append_only_manager.sync_messages(state.messages)
|
||||
resources.run_context.session.append_only_manager.sync_messages(
|
||||
resources.run_context.run.messages
|
||||
)
|
||||
if state.is_finished:
|
||||
await self._check_follow_up(state, resources)
|
||||
|
||||
async def on_fallback(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
@@ -680,12 +643,12 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
"请**诚实地**向用户总结:你目前进行到了哪一步?遇到了什么困难导致循环耗尽?还有哪些预期步骤未能完成?\n"
|
||||
"**绝对禁止**对用户撒谎声声称你已经完成了任务。严禁再次尝试调用任何工具!请直接输出纯文本结果。"
|
||||
)
|
||||
state.messages.append(fallback_msg)
|
||||
run_context.run.messages.append(fallback_msg)
|
||||
|
||||
fallback_response = await self._invoke_and_record_llm(
|
||||
state=state,
|
||||
resources=resources,
|
||||
messages=state.messages,
|
||||
messages=run_context.run.messages,
|
||||
tools=[],
|
||||
tool_choice="none",
|
||||
)
|
||||
@@ -695,7 +658,7 @@ class StandardAgentExecutor(BaseAgentExecutor):
|
||||
model_construct(
|
||||
AgentRunResult,
|
||||
output=fallback_response.text,
|
||||
messages=state.messages,
|
||||
messages=list(run_context.run.messages),
|
||||
structured_data=None,
|
||||
usage=state.usage,
|
||||
),
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
|
||||
|
||||
from zhenxun.services.ai.capabilities import CapabilitySource, CombinedCapability
|
||||
from zhenxun.services.ai.context.memory.builder import MemoryBuilder
|
||||
@@ -18,10 +18,11 @@ from zhenxun.services.ai.core.messages import (
|
||||
ToolCallPart,
|
||||
UsageInfo,
|
||||
)
|
||||
from zhenxun.services.ai.core.models import ModelCapabilities
|
||||
from zhenxun.services.ai.core.options import GenerationConfig
|
||||
from zhenxun.services.ai.flow.core.models import BaseRuntimeConfig
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.models import AgentRunResult, AgentTask, HandoffPayload
|
||||
from zhenxun.services.ai.run.models import AgentRunResult, AgentTask
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
from zhenxun.services.ai.tools.engine.registry import ToolCollection
|
||||
from zhenxun.utils.pydantic_compat import model_copy
|
||||
@@ -121,27 +122,15 @@ class AgentState(BaseModel):
|
||||
tools: ToolCollection | None = None
|
||||
"""当前轮次生效的、已完成鉴权和过滤的工具集合"""
|
||||
|
||||
messages: list[AgentMessage] = Field(default_factory=list)
|
||||
"""大模型将看到的完整历史消息列表 (执行历史)"""
|
||||
usage: UsageInfo = Field(default_factory=UsageInfo)
|
||||
"""累计的 Token 消耗"""
|
||||
structured_result: Any | None = None
|
||||
"""拦截到的结构化输出结果"""
|
||||
early_result_output: Any | None = None
|
||||
"""拦截到的早期终止输出结果"""
|
||||
should_terminate: bool = False
|
||||
"""标记是否应提前终止循环"""
|
||||
handoff_triggered: HandoffPayload | None = None
|
||||
"""标记是否触发了移交"""
|
||||
is_finished: bool = False
|
||||
"""标记大模型循环是否彻底结束"""
|
||||
final_result: AgentRunResult[Any] | None = None
|
||||
"""最终的运行结果 (AgentRunResult)"""
|
||||
origin_msg_len: int = 0
|
||||
"""初始进入循环时的消息历史长度 (用于增量保存记忆)"""
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
"""供第三方开发者或生命周期钩子使用的自定义插槽,
|
||||
在单个 Agent FSM 循环中存取临时变量"""
|
||||
|
||||
_origin_msg_len: int = PrivateAttr(default=0)
|
||||
"""内部变量:初始进入循环时的消息历史长度 (用于增量保存记忆,防止被外部业务篡改)"""
|
||||
|
||||
current_cycle: int = 0
|
||||
"""当前思考循环的轮次索引"""
|
||||
|
||||
current_request_messages: list[AgentMessage] = Field(default_factory=list)
|
||||
"""当前即将发往大模型的实际请求消息"""
|
||||
current_request_extra: dict[str, Any] = Field(default_factory=dict)
|
||||
@@ -153,6 +142,17 @@ class AgentState(BaseModel):
|
||||
current_tool_results: list[Any] = Field(default_factory=list)
|
||||
"""当前轮次工具执行的结果或异常收集"""
|
||||
|
||||
should_reset_cycle: bool = False
|
||||
"""标记是否需要重置当前思考循环(例如由于外部干预插入了新消息)"""
|
||||
is_finished: bool = False
|
||||
"""标记大模型循环是否彻底结束"""
|
||||
pending_result: AgentRunResult[Any] | None = None
|
||||
"""单一数据源:等待返回的最终运行结果 (SSOT)"""
|
||||
usage: UsageInfo = Field(default_factory=UsageInfo)
|
||||
"""累计的 Token 消耗"""
|
||||
token_drift: int = 0
|
||||
"""动态 Token 校准偏移量 (真实 - 预估)"""
|
||||
|
||||
|
||||
class AgentRunResources(BaseModel):
|
||||
"""大模型执行过程中的全局静态资源与配置载体"""
|
||||
@@ -175,5 +175,5 @@ class AgentRunResources(BaseModel):
|
||||
"""Agent 全局与运行时的统一策略配置"""
|
||||
generation_config: GenerationConfig | None = None
|
||||
"""大模型生成配置"""
|
||||
model_name: str | None = None
|
||||
"""当前实际调用的模型名称"""
|
||||
model_capabilities: ModelCapabilities | None = None
|
||||
"""当前底层大模型的能力配置(合并了用户自定义覆盖)"""
|
||||
|
||||
Reference in New Issue
Block a user