mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-17 10:10:00 +08:00
✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 (#2146)
* ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 - 【重构】将原 services/llm 重构并迁移至全新的 services/ai 架构,提供向下兼容垫片 - 【新增】引入 Agent、Team、Workflow 三大智能体与工作流编排范式 - 【新增】引入基于 RAG 的长期向量记忆与中期槽位记忆系统 - 【新增】引入基于 Docker 的安全代码执行沙箱环境 - 【新增】支持 MCP 协议,允许动态管理和调用 MCP 服务 - 【新增】引入输入输出安全合规护栏与自愈反思机制 - 【优化】重构并优化多厂商 API 适配器 (Gemini, OpenAI, DeepSeek, GLM 等) - 【优化】优化日志脱敏与 Token 预估机制 - 【移除】移除旧版 llm default 和 llm reset-key 命令,新增 llm mcp 管理命令 * 🔧 chore(deps): 更新项目依赖与配置 - 添加 mcp、jieba 和 aiodocker 依赖到配置文件及 requirements.txt - 在 pyright 配置中设置 reportMissingImports 为 none - 调整 .gitignore 中 resources 目录的忽略规则 * ♻️ refactor(tools): 重构工具终止机制并清理知识库日志输出 - 统一使用 `context.state["__end_run__"]` 替代 `EndRunResult` 控制任务结束 - 移除文件系统和向量知识库检索工具中 `ToolResult` 的 `.with_log` 调用 - 调整指令处理器(Directive)的返回值为 `tool_res.output` - 修复部分类型检查警告并优化联合类型判断语法 * ♻️ refactor(tools): 重构工具副作用指令与控制流熔断机制 - 引入 `DirectivePayload` 及 `ToolResult` 的子类以结构化表达工具副作用 - 移除通过 `context.state` 传递魔术变量的隐式控制流设计 - 重构 `DirectiveManager` 处理器接口,直接在处理器中修改 `AgentState` 并构建 `AgentRunResult` - 在 `StandardAgentExecutor` 中统一通过 `directive_manager` 调度工具返回的副作用指令 - 补全 `MessageBuilder` 中部分核心方法的文档注释 * 🐛 fix(sandbox): 修复 Docker 沙箱容器状态检测与会话清理逻辑 -【修复】修正 `is_alive` 中直接读取私有属性的问题,改用 `show()` 返回值 -【修复】解决 `execute_code` 中缓存的执行器与当前会话不一致的问题 -【优化】在清理工作区前增加容器存活检测,避免向已死容器发送请求 -【优化】创建容器时增加运行状态校验,若已停止则自动从缓存中移除并重建 -【优化】优化容器销毁和清理逻辑,静默处理容器不存在 (404) 的异常 * 📝 docs(core): 补充核心模块初始化方法的文档注释 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
bdc1374848
commit
80fc5b86a7
@@ -0,0 +1,11 @@
|
||||
from .agent import Agent
|
||||
from .models import (
|
||||
AgentConfig,
|
||||
Persona,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Agent",
|
||||
"AgentConfig",
|
||||
"Persona",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,142 @@
|
||||
import asyncio
|
||||
from typing import Any, Generic, cast
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
|
||||
from zhenxun.services.ai.core.exceptions import (
|
||||
ConcurrencyInterruptException,
|
||||
ConcurrencyRejectException,
|
||||
ControlFlowExit,
|
||||
InterventionHandledException,
|
||||
)
|
||||
from zhenxun.services.ai.core.messages import UsageInfo
|
||||
from zhenxun.services.ai.flow.base import BaseRunnable
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext
|
||||
from zhenxun.services.ai.run.models import AgentRunEnd, AgentRunError
|
||||
from zhenxun.services.ai.run.ui import UIController
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.message import MessageUtils
|
||||
|
||||
T_Deps = TypeVar("T_Deps", default=Any)
|
||||
T_Out = TypeVar("T_Out", default=str)
|
||||
|
||||
|
||||
class AgentRunner(Generic[T_Out]):
|
||||
"""
|
||||
智能体运行器。
|
||||
负责将大模型的纯净数据流包装为平台交互动作(发消息、UI渲染)。
|
||||
自带 ContextVars 隐式上下文提取魔法。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
runnable: BaseRunnable,
|
||||
context: RunContext | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self.runnable = runnable
|
||||
self.context = context or RunContext(**kwargs)
|
||||
|
||||
is_stateless = (
|
||||
getattr(self.runnable.runtime_config, "stateless", True)
|
||||
if hasattr(self.runnable, "runtime_config")
|
||||
else True
|
||||
)
|
||||
if is_stateless and self.context.session_id:
|
||||
if not self.context.session_id.startswith("stateless_"):
|
||||
import uuid
|
||||
|
||||
self.context.session_id = (
|
||||
f"stateless_{self.context.session_id}_{uuid.uuid4().hex[:8]}"
|
||||
)
|
||||
if self.context.session:
|
||||
self.context.session.session_id = self.context.session_id
|
||||
|
||||
@property
|
||||
def _bot(self) -> Bot | None:
|
||||
return self.context.get_bot()
|
||||
|
||||
@property
|
||||
def _event(self) -> Event | None:
|
||||
return self.context.get_event()
|
||||
|
||||
async def reply(
|
||||
self, prompt: Any = None, reply_to: bool = False, **kwargs: Any
|
||||
) -> AgentRunResult[T_Out]:
|
||||
"""交互式执行:将 Agent 运行过程中的工具调用状态和最终结果自动发送给用户。"""
|
||||
final_result = None
|
||||
|
||||
profile = kwargs.pop("profile", None)
|
||||
|
||||
try:
|
||||
async with self.runnable.run_stream(
|
||||
prompt=prompt,
|
||||
context=self.context,
|
||||
profile=profile,
|
||||
**kwargs,
|
||||
) as stream_result:
|
||||
async for stream_event in stream_result.stream_events():
|
||||
if isinstance(stream_event, AgentRunEnd):
|
||||
final_result = stream_event.result
|
||||
|
||||
elif isinstance(stream_event, AgentRunError):
|
||||
raise stream_event.error
|
||||
|
||||
except ControlFlowExit as e:
|
||||
if isinstance(e, InterventionHandledException):
|
||||
logger.info(f"✨ {self.runnable.name} 触发运行时干预: {e.message}")
|
||||
if e.display_content and self._bot and self._event:
|
||||
await MessageUtils.build_message(str(e.display_content)).send(
|
||||
reply_to=reply_to
|
||||
)
|
||||
return cast(
|
||||
AgentRunResult[T_Out], AgentRunResult(output="", usage=UsageInfo())
|
||||
)
|
||||
|
||||
if isinstance(e, ConcurrencyRejectException):
|
||||
logger.warning(
|
||||
f"⏳ {self.runnable.name} 触发并发拒绝 (REJECT): {e.message}"
|
||||
)
|
||||
return cast(
|
||||
AgentRunResult[T_Out], AgentRunResult(output="", usage=UsageInfo())
|
||||
)
|
||||
|
||||
if isinstance(e, ConcurrencyInterruptException):
|
||||
logger.warning(
|
||||
f"🛑 {self.runnable.name} 触发并发中断 (INTERRUPT): {e.message}"
|
||||
)
|
||||
return cast(
|
||||
AgentRunResult[T_Out], AgentRunResult(output="", usage=UsageInfo())
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"{self.runnable.name} 控制流正常中断: {type(e).__name__} - {e}"
|
||||
)
|
||||
await UIController.handle_control_flow_exit_display(
|
||||
e, self.context, reply_to
|
||||
)
|
||||
|
||||
raise asyncio.CancelledError()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.runnable.name} 运行失败: {e}", e=e)
|
||||
if self._bot and self._event:
|
||||
await MessageUtils.build_message(f"❌ 运行发生错误: {e}").send()
|
||||
raise e
|
||||
|
||||
if final_result and final_result.output and self._bot and self._event:
|
||||
if isinstance(final_result.output, UniMessage):
|
||||
await final_result.output.send(
|
||||
self._event, bot=self._bot, reply_to=reply_to
|
||||
)
|
||||
final_result.output = final_result.output.extract_plain_text()
|
||||
else:
|
||||
final_msg = str(final_result.output)
|
||||
await MessageUtils.build_message(final_msg).send()
|
||||
|
||||
if final_result is None:
|
||||
raise RuntimeError("智能体运行流异常结束:未返回最终结果。")
|
||||
|
||||
return cast(AgentRunResult[T_Out], final_result)
|
||||
@@ -0,0 +1,156 @@
|
||||
import asyncio
|
||||
from typing import Any, cast
|
||||
|
||||
from zhenxun.services.ai.capabilities import AbstractCapability
|
||||
from zhenxun.services.ai.capabilities.base import CapabilityOrdering
|
||||
from zhenxun.services.ai.core.engine.structured_parser import (
|
||||
BaseOutputProcessor,
|
||||
SubmitFinalResultExecutable,
|
||||
)
|
||||
from zhenxun.services.ai.core.exceptions import UpstreamServerException
|
||||
from zhenxun.services.ai.core.messages import TaskLifecycleEvent
|
||||
from zhenxun.services.ai.core.options import BaseOutputDefinition, ToolOutput
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext, Task
|
||||
from zhenxun.services.log import logger
|
||||
|
||||
|
||||
class OutputValidationCapability(AbstractCapability):
|
||||
"""输出拦截与校验能力组件 (支持纯文本及结构化护栏)"""
|
||||
|
||||
def get_ordering(self) -> Any:
|
||||
from zhenxun.services.ai.capabilities.builtin import (
|
||||
ReflexionCapability,
|
||||
)
|
||||
|
||||
return CapabilityOrdering(wraps=[ReflexionCapability])
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
output_type: Any | None = None,
|
||||
guardrails: list[Any] | None = None,
|
||||
raw_schema: dict[str, Any] | None = None,
|
||||
):
|
||||
self.output_type = output_type
|
||||
self.raw_schema = raw_schema
|
||||
|
||||
from zhenxun.services.ai.guardrails import parse_guardrails
|
||||
|
||||
self.guardrails = parse_guardrails(guardrails)
|
||||
self.processor = None
|
||||
self.submit_tool = None
|
||||
|
||||
if self.output_type is not None:
|
||||
if isinstance(self.output_type, BaseOutputDefinition):
|
||||
out_type = self.output_type.type_
|
||||
tool_name_override = (
|
||||
self.output_type.name
|
||||
if isinstance(self.output_type, ToolOutput)
|
||||
else None
|
||||
)
|
||||
else:
|
||||
out_type = cast(type[Any], self.output_type)
|
||||
tool_name_override = None
|
||||
|
||||
self.processor = BaseOutputProcessor(
|
||||
response_model=out_type,
|
||||
)
|
||||
self.submit_tool = SubmitFinalResultExecutable(
|
||||
self.processor, self.guardrails
|
||||
)
|
||||
if tool_name_override:
|
||||
self.submit_tool.name = tool_name_override
|
||||
elif self.raw_schema is not None:
|
||||
self.processor = BaseOutputProcessor(
|
||||
response_model=None,
|
||||
raw_schema=self.raw_schema,
|
||||
)
|
||||
self.submit_tool = SubmitFinalResultExecutable(
|
||||
self.processor, self.guardrails
|
||||
)
|
||||
|
||||
async def get_system_prompts(self, context: RunContext) -> list[str]:
|
||||
"""动态注入结构化要求提示词"""
|
||||
if self.submit_tool:
|
||||
return [
|
||||
"### ⚠️ [核心任务:结构化输出要求]\n"
|
||||
"当前任务处于严格的 **结构化输出模式**。\n"
|
||||
"当你完成所有调查和思考后,必须且只能调用 "
|
||||
f"`{self.submit_tool.name}` 工具来提交最终结果,"
|
||||
"禁止用纯文本直接作答。\n"
|
||||
"(📌 提示:最终需要返回的数据结构要求,"
|
||||
"请严格查阅并遵循 "
|
||||
f"`{self.submit_tool.name}` 工具的参数 Schema 定义,"
|
||||
"将其视为唯一的数据约束)"
|
||||
]
|
||||
return []
|
||||
|
||||
async def get_tools(self, context: RunContext) -> list[Any]:
|
||||
"""动态挂载提交最终结果的工具"""
|
||||
if self.submit_tool:
|
||||
return [self.submit_tool]
|
||||
return []
|
||||
|
||||
async def wrap_model_request(self, context, llm_context, handler):
|
||||
"""将 Processor 和 Guardrails 传给底层的 IvrCapability"""
|
||||
llm_context.request.extra["output_processor"] = self.processor
|
||||
llm_context.request.extra["guardrails"] = self.guardrails
|
||||
return await handler(llm_context)
|
||||
|
||||
async def wrap_run(self, context: RunContext, handler: Any) -> AgentRunResult[Any]:
|
||||
"""运行结束后,校验是否成功提取了结构化数据"""
|
||||
result = await handler()
|
||||
if self.output_type is not None or self.raw_schema is not None:
|
||||
if result.structured_data is not None:
|
||||
result.output = result.structured_data
|
||||
else:
|
||||
tool_name = self.submit_tool.name if self.submit_tool else "unknown"
|
||||
logger.error(f"Agent 未能调用 {tool_name} 提交结构化数据。")
|
||||
raise UpstreamServerException(
|
||||
"模型未能输出符合要求的结构化数据。",
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class TaskTrackingCapability(AbstractCapability):
|
||||
"""数据契约任务状态追踪与事件遥测组件"""
|
||||
|
||||
def __init__(self, task: Task, agent_name: str):
|
||||
self.task = task
|
||||
self.agent_name = agent_name
|
||||
|
||||
async def wrap_run(self, context: RunContext, handler: Any) -> AgentRunResult[Any]:
|
||||
"""任务生命周期追踪"""
|
||||
|
||||
task_name = self.task.name or self.task.id[:8]
|
||||
logger.debug(f"📋 **开始任务**: `{task_name}` (由 {self.agent_name} 执行)")
|
||||
context.run.add_event(TaskLifecycleEvent(task_name=task_name, action="start"))
|
||||
try:
|
||||
result = await handler()
|
||||
logger.debug(f"✅ **任务完成**: `{task_name}`")
|
||||
context.run.add_event(
|
||||
TaskLifecycleEvent(task_name=task_name, action="complete")
|
||||
)
|
||||
return result
|
||||
except asyncio.CancelledError as e:
|
||||
logger.warning(f"⚠️ **任务被强制取消**: `{task_name}`")
|
||||
context.run.add_event(
|
||||
TaskLifecycleEvent(
|
||||
task_name=task_name,
|
||||
action="fail",
|
||||
error_msg="任务执行被中止或取消",
|
||||
)
|
||||
)
|
||||
raise e
|
||||
except BaseException as error:
|
||||
event_error = (
|
||||
error if isinstance(error, Exception) else Exception(str(error))
|
||||
)
|
||||
logger.error(f"❌ **任务失败**: `{task_name}` - {event_error}")
|
||||
context.run.add_event(
|
||||
TaskLifecycleEvent(
|
||||
task_name=task_name,
|
||||
action="fail",
|
||||
error_msg=str(event_error),
|
||||
)
|
||||
)
|
||||
raise error
|
||||
@@ -0,0 +1,20 @@
|
||||
from .builders import (
|
||||
AgentProfileResolver,
|
||||
CapabilityBuilder,
|
||||
ContextBuilder,
|
||||
ToolBuilder,
|
||||
)
|
||||
from .directive import DirectiveManager, directive, directive_manager
|
||||
from .executor import BaseAgentExecutor, StandardAgentExecutor
|
||||
|
||||
__all__ = [
|
||||
"AgentProfileResolver",
|
||||
"BaseAgentExecutor",
|
||||
"CapabilityBuilder",
|
||||
"ContextBuilder",
|
||||
"DirectiveManager",
|
||||
"StandardAgentExecutor",
|
||||
"ToolBuilder",
|
||||
"directive",
|
||||
"directive_manager",
|
||||
]
|
||||
@@ -0,0 +1,434 @@
|
||||
from collections.abc import Callable
|
||||
import copy
|
||||
import inspect
|
||||
from typing import Any, cast
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
|
||||
from zhenxun.services.ai.capabilities import CombinedCapability
|
||||
from zhenxun.services.ai.context.memory.models import MemoryConfig
|
||||
from zhenxun.services.ai.core.messages import LLMMessage
|
||||
from zhenxun.services.ai.core.options import GenerationConfig
|
||||
from zhenxun.services.ai.core.templates import PromptTemplate
|
||||
from zhenxun.services.ai.flow.agent.models import Persona
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.di import DependencyInjector
|
||||
from zhenxun.services.ai.tools.engine.registry import (
|
||||
ToolCollection,
|
||||
tool_provider_manager,
|
||||
)
|
||||
from zhenxun.services.ai.tools.models import GlobalToolFilter, ResolvedToolPayload
|
||||
from zhenxun.utils.pydantic_compat import model_copy
|
||||
|
||||
|
||||
class AgentProfileResolver:
|
||||
"""Agent 配置解析器:负责提取与合并 Agent 的运行时 Profile"""
|
||||
|
||||
@staticmethod
|
||||
def resolve_memory(
|
||||
agent_memory_config: MemoryConfig, override_memory: Any | None
|
||||
) -> MemoryConfig:
|
||||
from zhenxun.services.ai.context.memory.builder import MemoryBuilder
|
||||
|
||||
if override_memory is not None:
|
||||
return MemoryBuilder.resolve(override_memory)
|
||||
return model_copy(agent_memory_config, deep=True)
|
||||
|
||||
@staticmethod
|
||||
def resolve_generation_config(
|
||||
base_config: GenerationConfig,
|
||||
cap_config: GenerationConfig | None,
|
||||
profile_config: GenerationConfig | None,
|
||||
) -> GenerationConfig:
|
||||
final_gen_config = model_copy(base_config, deep=True)
|
||||
if cap_config:
|
||||
final_gen_config = final_gen_config.merge_with(cap_config)
|
||||
if profile_config:
|
||||
final_gen_config = final_gen_config.merge_with(profile_config)
|
||||
return final_gen_config
|
||||
|
||||
|
||||
class CapabilityBuilder:
|
||||
"""拦截器能力组装器:负责合并 Agent, Task, Profile 和全局的中间件"""
|
||||
|
||||
@staticmethod
|
||||
async def build_for_run(
|
||||
agent_name: str,
|
||||
namespace: str,
|
||||
output_type: Any | None,
|
||||
raw_schema: dict | None,
|
||||
agent_guardrails: list,
|
||||
task_guardrails: list,
|
||||
task_obj: Any | None,
|
||||
agent_capabilities: list,
|
||||
profile_capabilities: list | None,
|
||||
context: RunContext,
|
||||
) -> CombinedCapability:
|
||||
from zhenxun.services.ai.capabilities import (
|
||||
AbstractCapability,
|
||||
DynamicCapability,
|
||||
)
|
||||
from zhenxun.services.ai.flow.agent.capabilities import (
|
||||
OutputValidationCapability,
|
||||
TaskTrackingCapability,
|
||||
)
|
||||
|
||||
dynamic_caps = []
|
||||
combined_guardrails = agent_guardrails + task_guardrails
|
||||
|
||||
if output_type is not None and output_type is not str:
|
||||
dynamic_caps.append(
|
||||
OutputValidationCapability(output_type, combined_guardrails)
|
||||
)
|
||||
elif raw_schema is not None:
|
||||
dynamic_caps.append(
|
||||
OutputValidationCapability(
|
||||
None, combined_guardrails, raw_schema=raw_schema
|
||||
)
|
||||
)
|
||||
elif combined_guardrails:
|
||||
dynamic_caps.append(OutputValidationCapability(None, combined_guardrails))
|
||||
|
||||
if task_obj:
|
||||
dynamic_caps.append(TaskTrackingCapability(task_obj, agent_name))
|
||||
|
||||
run_level_caps = []
|
||||
if profile_capabilities:
|
||||
for cap in profile_capabilities:
|
||||
if isinstance(cap, AbstractCapability):
|
||||
run_level_caps.append(cap)
|
||||
elif callable(cap):
|
||||
run_level_caps.append(DynamicCapability(cap))
|
||||
|
||||
from zhenxun.services.ai.run import (
|
||||
GLOBAL_CAPABILITIES,
|
||||
)
|
||||
|
||||
base_caps = GLOBAL_CAPABILITIES.get("global", []).copy()
|
||||
if namespace != "global" and namespace in GLOBAL_CAPABILITIES:
|
||||
base_caps.extend(GLOBAL_CAPABILITIES[namespace])
|
||||
|
||||
combined_cap = CombinedCapability(
|
||||
base_caps
|
||||
+ getattr(context, "capabilities", [])
|
||||
+ agent_capabilities
|
||||
+ run_level_caps
|
||||
+ dynamic_caps
|
||||
)
|
||||
return cast(CombinedCapability, await combined_cap.for_run(context))
|
||||
|
||||
|
||||
class ContextBuilder:
|
||||
"""系统提示词与上下文记忆构建器"""
|
||||
|
||||
@staticmethod
|
||||
async def build_prompts(
|
||||
instruction: str | PromptTemplate,
|
||||
system_prompts: list[Any],
|
||||
run_context: RunContext,
|
||||
run_scoped_cap: CombinedCapability,
|
||||
persona: Persona | None = None,
|
||||
) -> tuple[str, list[Any]]:
|
||||
"""解析提示词,返回 (静态系统提示词文本, 动态独立消息列表) 元组"""
|
||||
|
||||
static_instructions = []
|
||||
dynamic_messages = []
|
||||
|
||||
for sp_func in system_prompts:
|
||||
sig = inspect.signature(sp_func)
|
||||
if len(sig.parameters) > 0:
|
||||
injected_kwargs = await DependencyInjector.resolve_all(
|
||||
sig=sig,
|
||||
call_kwargs={},
|
||||
context=run_context,
|
||||
)
|
||||
res = (
|
||||
(await sp_func(**injected_kwargs))
|
||||
if is_coroutine_callable(sp_func)
|
||||
else sp_func(**injected_kwargs)
|
||||
)
|
||||
else:
|
||||
res = (await sp_func()) if is_coroutine_callable(sp_func) else sp_func()
|
||||
if res:
|
||||
if isinstance(res, LLMMessage):
|
||||
dynamic_messages.append(res)
|
||||
elif isinstance(res, list) and all(
|
||||
isinstance(m, LLMMessage) for m in res
|
||||
):
|
||||
dynamic_messages.extend(res)
|
||||
else:
|
||||
if isinstance(res, list):
|
||||
for item in res:
|
||||
if item:
|
||||
dynamic_messages.append(LLMMessage.system(str(item)))
|
||||
else:
|
||||
dynamic_messages.append(LLMMessage.system(str(res)))
|
||||
|
||||
if persona:
|
||||
persona_parts = [
|
||||
f"## 扮演角色 (Role)\n{persona.role}",
|
||||
f"## 核心目标 (Goal)\n{persona.goal}",
|
||||
]
|
||||
if persona.backstory:
|
||||
persona_parts.append(f"## 角色背景 (Backstory)\n{persona.backstory}")
|
||||
static_instructions.append("\n\n".join(persona_parts))
|
||||
|
||||
if instruction:
|
||||
static_instructions.append("## 本次任务指令 (Task)")
|
||||
|
||||
if instruction:
|
||||
if isinstance(instruction, PromptTemplate):
|
||||
static_instructions.append(instruction.format_with_context(run_context))
|
||||
else:
|
||||
static_instructions.append(str(instruction))
|
||||
|
||||
caps = (
|
||||
run_scoped_cap.capabilities
|
||||
if run_scoped_cap
|
||||
else getattr(run_context, "capabilities", [])
|
||||
)
|
||||
for cap in caps:
|
||||
cap_prompts = await cap.get_system_prompts(run_context)
|
||||
for prompt_text in cap_prompts:
|
||||
if prompt_text and prompt_text.strip():
|
||||
dynamic_messages.append(LLMMessage.system(prompt_text))
|
||||
|
||||
static_text = "\n\n".join(static_instructions)
|
||||
|
||||
render_context = {
|
||||
"deps": run_context.deps,
|
||||
"bot": getattr(run_context.deps, "bot", None),
|
||||
"event": getattr(run_context.deps, "event", None),
|
||||
"matcher": getattr(run_context.deps, "matcher", None),
|
||||
}
|
||||
if run_context.state:
|
||||
render_context.update(run_context.state)
|
||||
|
||||
rendered_dynamic_messages = []
|
||||
from zhenxun.services.ai.core.messages import TextPart
|
||||
|
||||
for msg in dynamic_messages:
|
||||
if msg.role == "system":
|
||||
new_content = []
|
||||
changed = False
|
||||
for part in msg.content:
|
||||
if isinstance(part, TextPart) and part.text:
|
||||
try:
|
||||
rendered_text = PromptTemplate(part.text).render(
|
||||
**render_context
|
||||
)
|
||||
new_content.append(TextPart(text=rendered_text))
|
||||
if rendered_text != part.text:
|
||||
changed = True
|
||||
except Exception:
|
||||
new_content.append(part)
|
||||
else:
|
||||
new_content.append(part)
|
||||
if changed:
|
||||
new_msg = msg.model_copy(deep=True)
|
||||
new_msg.content = new_content
|
||||
rendered_dynamic_messages.append(new_msg)
|
||||
else:
|
||||
rendered_dynamic_messages.append(msg)
|
||||
else:
|
||||
rendered_dynamic_messages.append(msg)
|
||||
|
||||
return (
|
||||
PromptTemplate(static_text).render(**render_context),
|
||||
rendered_dynamic_messages,
|
||||
)
|
||||
|
||||
|
||||
class ToolBuilder:
|
||||
"""系统工具集合解析与构建器"""
|
||||
|
||||
@staticmethod
|
||||
async def resolve_tools(
|
||||
tool_definitions: list[Any],
|
||||
toolset_funcs: list[Any],
|
||||
system_tools: list[Any],
|
||||
namespace: str,
|
||||
tool_filter: GlobalToolFilter | None,
|
||||
run_context: RunContext,
|
||||
run_scoped_cap: CombinedCapability,
|
||||
) -> ResolvedToolPayload:
|
||||
"""解析、合并并过滤工具集"""
|
||||
defs_to_resolve = list(tool_definitions)
|
||||
|
||||
for ts_func in toolset_funcs:
|
||||
sig = inspect.signature(ts_func)
|
||||
injected_kwargs = {}
|
||||
if len(sig.parameters) > 0:
|
||||
injected_kwargs = await DependencyInjector.resolve_all(
|
||||
sig=sig,
|
||||
call_kwargs={},
|
||||
context=run_context,
|
||||
)
|
||||
|
||||
res = (
|
||||
(await ts_func(**injected_kwargs))
|
||||
if is_coroutine_callable(ts_func)
|
||||
else ts_func(**injected_kwargs)
|
||||
)
|
||||
|
||||
if res is not None:
|
||||
if isinstance(res, list):
|
||||
defs_to_resolve.extend(res)
|
||||
else:
|
||||
defs_to_resolve.append(res)
|
||||
|
||||
if system_tools:
|
||||
for st in system_tools:
|
||||
if st not in defs_to_resolve:
|
||||
defs_to_resolve.append(st)
|
||||
|
||||
caps = (
|
||||
run_scoped_cap.capabilities
|
||||
if run_scoped_cap
|
||||
else getattr(run_context, "capabilities", [])
|
||||
)
|
||||
for cap in caps:
|
||||
cap_tools = await cap.get_tools(run_context)
|
||||
defs_to_resolve.extend(cap_tools)
|
||||
|
||||
payload = await tool_provider_manager.resolve_tools(
|
||||
defs_to_resolve, namespace, context=run_context
|
||||
)
|
||||
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
async def prepare_effective_tools(
|
||||
effective_tools: list[Any],
|
||||
context: RunContext,
|
||||
tool_filters: list[Callable],
|
||||
run_scoped_cap: CombinedCapability,
|
||||
) -> ToolCollection:
|
||||
"""处理生命周期:在工具发往执行器前,进行最终的 Schema 拦截和清洗"""
|
||||
current_tool_defs = []
|
||||
for t_exec in effective_tools:
|
||||
if hasattr(t_exec, "get_definition"):
|
||||
t_def = await t_exec.get_definition(context)
|
||||
if t_def:
|
||||
current_tool_defs.append(t_def)
|
||||
|
||||
if tool_filters:
|
||||
for filter_func in tool_filters:
|
||||
sig = inspect.signature(filter_func)
|
||||
call_kwargs = {"tool_defs": current_tool_defs}
|
||||
resolved_kwargs = await DependencyInjector.resolve_all(
|
||||
sig, call_kwargs, context
|
||||
)
|
||||
filtered_kwargs = {
|
||||
k: v for k, v in resolved_kwargs.items() if k in sig.parameters
|
||||
}
|
||||
_res = (
|
||||
await filter_func(**filtered_kwargs)
|
||||
if is_coroutine_callable(filter_func)
|
||||
else filter_func(**filtered_kwargs)
|
||||
)
|
||||
if _res is not None:
|
||||
current_tool_defs = list(_res)
|
||||
|
||||
_cap_res = await run_scoped_cap.prepare_tools(context, current_tool_defs)
|
||||
if _cap_res is not None:
|
||||
current_tool_defs = list(_cap_res)
|
||||
|
||||
final_defs_map = {d.name.lower(): d for d in current_tool_defs if d}
|
||||
final_effective_tools = ToolCollection()
|
||||
for t_exec in effective_tools:
|
||||
t_name = getattr(t_exec, "name", "unknown")
|
||||
if t_name.lower() in final_defs_map:
|
||||
cloned_tool = copy.copy(t_exec)
|
||||
cloned_tool._dynamic_def = final_defs_map[t_name.lower()]
|
||||
final_effective_tools.append(cloned_tool)
|
||||
return final_effective_tools
|
||||
|
||||
|
||||
class SessionBuilder:
|
||||
"""会话与记忆域构建器:负责隔离前缀计算和读写门面装配"""
|
||||
|
||||
@staticmethod
|
||||
def build_session_and_memory(
|
||||
context: RunContext,
|
||||
namespace: str,
|
||||
agent_name: str,
|
||||
effective_memory: MemoryConfig,
|
||||
) -> tuple[Any, Any, Any]:
|
||||
from zhenxun.services.ai.context.memory.engine import MemoryReader, MemoryWriter
|
||||
from zhenxun.services.ai.context.memory.types import SessionMetadata
|
||||
from zhenxun.services.ai.utils.scope import ScopeSelector
|
||||
|
||||
bot_id = None
|
||||
bot_inst = context.get_bot()
|
||||
if bot_inst and hasattr(bot_inst, "self_id"):
|
||||
bot_id = str(bot_inst.self_id)
|
||||
|
||||
selector = ScopeSelector(
|
||||
user_id=context.get_user_id(),
|
||||
group_id=context.get_group_id(),
|
||||
platform=context.get_platform(),
|
||||
bot_id=bot_id,
|
||||
namespace=namespace,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
|
||||
all_scopes = {"/"}
|
||||
scope_name_mapping = {}
|
||||
|
||||
if effective_memory.short_term and effective_memory.short_term.isolation:
|
||||
sel = effective_memory.short_term.isolation.resolve(
|
||||
deps=context.deps,
|
||||
prefix="",
|
||||
default_namespace=namespace,
|
||||
default_agent=agent_name,
|
||||
)
|
||||
all_scopes.add(sel.scope_prefix)
|
||||
|
||||
for config_part in [effective_memory.slots, effective_memory.long_term]:
|
||||
if config_part and hasattr(config_part, "scopes") and config_part.scopes:
|
||||
for name, builder in config_part.scopes.items():
|
||||
sel = builder.resolve(
|
||||
deps=context.deps,
|
||||
prefix="",
|
||||
default_namespace=namespace,
|
||||
default_agent=agent_name,
|
||||
)
|
||||
all_scopes.add(sel.scope_prefix)
|
||||
scope_name_mapping[sel.scope_prefix] = name
|
||||
|
||||
parts = selector.get_scope_parts()
|
||||
for i in range(len(parts)):
|
||||
all_scopes.add("/" + "/".join(parts[: i + 1]))
|
||||
|
||||
accessible_scopes = sorted(all_scopes, key=lambda x: len(x.split("/")))
|
||||
|
||||
short_term_builder = (
|
||||
effective_memory.short_term.isolation
|
||||
if effective_memory.short_term
|
||||
else effective_memory.base_isolation
|
||||
)
|
||||
short_term_selector = short_term_builder.resolve(
|
||||
deps=context.deps,
|
||||
prefix="",
|
||||
default_namespace=namespace,
|
||||
default_agent=agent_name,
|
||||
)
|
||||
|
||||
session_metadata = SessionMetadata(
|
||||
session_id=short_term_selector.scope_prefix,
|
||||
selector=selector,
|
||||
scope_prefix=selector.scope_prefix,
|
||||
accessible_scopes=accessible_scopes,
|
||||
scope_name_mapping=scope_name_mapping,
|
||||
)
|
||||
reader = MemoryReader(
|
||||
session_meta=session_metadata, memory_config=effective_memory
|
||||
)
|
||||
writer = MemoryWriter(
|
||||
session_meta=session_metadata,
|
||||
memory_config=effective_memory,
|
||||
context=context,
|
||||
)
|
||||
|
||||
return session_metadata, reader, writer
|
||||
@@ -0,0 +1,115 @@
|
||||
from collections import defaultdict
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from zhenxun.services.ai.flow.agent.models import AgentRunResources, AgentState
|
||||
from zhenxun.services.ai.run.models import AgentRunResult, HandoffPayload
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.pydantic_compat import model_construct
|
||||
from zhenxun.utils.utils import infer_plugin_namespace
|
||||
|
||||
DirectiveHandlerFunc = Callable[
|
||||
[AgentState, AgentRunResources, ToolResult], Awaitable[None]
|
||||
]
|
||||
|
||||
|
||||
class DirectiveManager:
|
||||
"""工具指令路由注册中心"""
|
||||
|
||||
def __init__(self):
|
||||
self._handlers: dict[str, dict[str, DirectiveHandlerFunc]] = defaultdict(dict)
|
||||
|
||||
def register(
|
||||
self, name: str, handler: DirectiveHandlerFunc, namespace: str = "global"
|
||||
) -> None:
|
||||
self._handlers[namespace][name] = handler
|
||||
logger.debug(f"已注册工具副作用指令: '{name}' -> Namespace: '{namespace}'")
|
||||
|
||||
def get_handler(
|
||||
self, name: str, namespace: str = "global"
|
||||
) -> DirectiveHandlerFunc | None:
|
||||
"""优先从指定 namespace 找,找不到回退到 global"""
|
||||
ns_dict = self._handlers.get(namespace, {})
|
||||
if name in ns_dict:
|
||||
return ns_dict[name]
|
||||
return self._handlers.get("global", {}).get(name)
|
||||
|
||||
|
||||
directive_manager = DirectiveManager()
|
||||
|
||||
|
||||
def directive(name: str | None = None, namespace: str | None = None):
|
||||
"""
|
||||
注册一个自定义工具副作用指令处理器的装饰器。
|
||||
|
||||
参数:
|
||||
name: 指令的名称,如果不填则默认使用被装饰的函数名。
|
||||
namespace: 插件命名空间,如果不填则基于代码调用栈自动推断。
|
||||
"""
|
||||
|
||||
def decorator(func: DirectiveHandlerFunc):
|
||||
dir_name = name or func.__name__
|
||||
ns = namespace if namespace is not None else infer_plugin_namespace()
|
||||
directive_manager.register(dir_name, func, ns)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@directive("submit_structured", namespace="global")
|
||||
async def handle_submit_structured(
|
||||
state: AgentState, resources: AgentRunResources, tool_res: ToolResult
|
||||
) -> None:
|
||||
parsed_obj = (
|
||||
tool_res.directive.payload.get("parsed_obj") if tool_res.directive else None
|
||||
)
|
||||
logger.info("✅ 拦截到结构化结果提交,结束循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=None,
|
||||
messages=state.messages,
|
||||
structured_data=parsed_obj,
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
|
||||
@directive("end_run", namespace="global")
|
||||
async def handle_end_run(
|
||||
state: AgentState, resources: AgentRunResources, tool_res: ToolResult
|
||||
) -> None:
|
||||
output = (
|
||||
tool_res.directive.payload.get("output", tool_res.output)
|
||||
if tool_res.directive
|
||||
else tool_res.output
|
||||
)
|
||||
logger.debug("✅ 捕获到工具发出的终止信号,提前结束推理循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=output,
|
||||
messages=state.messages,
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
|
||||
@directive("handoff", namespace="global")
|
||||
async def handle_handoff(
|
||||
state: AgentState, resources: AgentRunResources, tool_res: ToolResult
|
||||
) -> None:
|
||||
payload = tool_res.directive.payload if tool_res.directive else {}
|
||||
handoff = HandoffPayload(
|
||||
target=payload.get("target", "unknown"),
|
||||
reason=payload.get("reason", ""),
|
||||
context_data=payload.get("context_data", ""),
|
||||
)
|
||||
output_text = f"已触发控制权移交 -> {handoff.target}。原因: {handoff.reason}"
|
||||
logger.info(f"✅ 拦截到移交(Handoff)信号: 移交给 -> {handoff.target}。结束循环。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=output_text,
|
||||
messages=state.messages,
|
||||
usage=state.usage,
|
||||
handoff=handoff,
|
||||
)
|
||||
@@ -0,0 +1,691 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any, cast
|
||||
|
||||
from zhenxun.services.ai.core.engine.context_renderer import ContextConverter
|
||||
from zhenxun.services.ai.core.engine.token_counter import (
|
||||
parse_usage_info,
|
||||
token_counter,
|
||||
)
|
||||
from zhenxun.services.ai.core.exceptions import (
|
||||
ControlFlowExit,
|
||||
UpstreamServerException,
|
||||
)
|
||||
from zhenxun.services.ai.core.messages import (
|
||||
AgentMessage,
|
||||
AssistantContentUnion,
|
||||
AssistantMessage,
|
||||
AudioPart,
|
||||
ChatRequest,
|
||||
ChatResponse,
|
||||
FilePart,
|
||||
ImagePart,
|
||||
LLMMessage,
|
||||
TextPart,
|
||||
ToolCallPart,
|
||||
ToolReturnPart,
|
||||
VideoPart,
|
||||
)
|
||||
from zhenxun.services.ai.core.models import LLMContext
|
||||
from zhenxun.services.ai.core.options import GenerationConfig
|
||||
from zhenxun.services.ai.core.stream_events import (
|
||||
LLMEndEvent,
|
||||
LLMStartEvent,
|
||||
ToolStreamChunkEvent,
|
||||
)
|
||||
from zhenxun.services.ai.flow.agent.engine.directive import (
|
||||
DirectiveHandlerFunc,
|
||||
)
|
||||
from zhenxun.services.ai.flow.agent.models import AgentRunResources, AgentState
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext
|
||||
from zhenxun.services.ai.tools.engine.executor import ToolExecutor
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.pydantic_compat import dump_json_safely, model_construct
|
||||
|
||||
|
||||
class BaseAgentExecutor(ABC):
|
||||
"""
|
||||
Agent 核心执行器基类 (Template Method Pattern)。
|
||||
定义了基于生命周期的大模型控制流。第三方开发者可通过重写特定钩子,
|
||||
"""
|
||||
|
||||
async def run(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> AgentRunResult[Any]:
|
||||
"""
|
||||
核心模板方法 (Template Method)。
|
||||
组织整个大模型推导与工具调用的生命周期循环。如无必要,请勿重写此方法。
|
||||
"""
|
||||
await self.on_start(state, resources)
|
||||
|
||||
try:
|
||||
for cycle_index in range(resources.config.max_cycles):
|
||||
state.current_cycle = cycle_index
|
||||
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.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
await self.filter_tool_calls(state, resources)
|
||||
if state.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
await self.execute_tools(state, resources)
|
||||
|
||||
await self.handle_tool_results(state, resources)
|
||||
if state.is_finished:
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
return await self.on_fallback(state, resources)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
@abstractmethod
|
||||
async def on_start(self, state: AgentState, resources: AgentRunResources) -> None:
|
||||
"""生命周期: Agent 启动时调用,用于初始化状态或资源。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def on_cycle_start(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""生命周期: 每次推理循环开始时调用。可用于 Token 预估或防死循环检测。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def build_llm_request(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""生命周期: 构造请求大模型的 Messages 上下文和 Extra 参数。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def execute_llm(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""生命周期: 触发大模型 API 请求并返回响应。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def handle_llm_response(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""
|
||||
生命周期: 处理大模型返回的结果,解析 Token 用量,
|
||||
并将模型回复追加至对话历史。
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def filter_tool_calls(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""生命周期: 从大模型的响应中提取并过滤出需要在本地客户端执行的工具调用请求。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def execute_tools(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""生命周期: 并发执行提取出的工具,并收集结果或异常。"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def handle_tool_results(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
"""
|
||||
生命周期: 处理工具返回的结果。
|
||||
包括异常拦截、UI 渲染、Handoff 移交指令以及将结果追加至对话历史。
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def on_fallback(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> AgentRunResult[Any]:
|
||||
"""生命周期: 当大模型思考循环达到 max_cycles 时触发,执行兜底策略。"""
|
||||
pass
|
||||
|
||||
|
||||
class StandardAgentExecutor(BaseAgentExecutor):
|
||||
"""
|
||||
LLM 任务执行器(核心推理引擎)。
|
||||
负责:生命周期回调触发、工具循环调用、
|
||||
错误反思(Reflexion)、Token消耗追踪。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, directive_handlers: dict[str, DirectiveHandlerFunc] | None = None
|
||||
):
|
||||
self.tool_executor = ToolExecutor()
|
||||
self._directive_handlers: dict[str, DirectiveHandlerFunc] = (
|
||||
directive_handlers or {}
|
||||
)
|
||||
|
||||
def _can_retry_via_llm(self, result: ToolResult) -> bool:
|
||||
"""通过新版的专属字段直接判断是否允许重试"""
|
||||
return result.is_retryable
|
||||
|
||||
def _check_follow_up(
|
||||
self, state: AgentState, resources: AgentRunResources, session_info: Any
|
||||
) -> bool:
|
||||
"""检查追加队列,排空并合并数据到上下文,返回是否发现新消息"""
|
||||
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.session.append_only_manager.sync_messages(
|
||||
state.messages
|
||||
)
|
||||
state.is_finished = False
|
||||
state.final_result = None
|
||||
return True
|
||||
return False
|
||||
|
||||
async def _invoke_and_record_llm(
|
||||
self,
|
||||
state: AgentState,
|
||||
resources: AgentRunResources,
|
||||
messages: list[AgentMessage],
|
||||
tools: list[Any] | None,
|
||||
tool_choice: Any = None,
|
||||
) -> ChatResponse:
|
||||
"""执行 LLM 请求,处理基础指标遥测统计,并将新对话上下文追加到状态流"""
|
||||
run_context = resources.run_context
|
||||
cancellation_token = run_context.run.cancellation_token
|
||||
|
||||
current_extra = run_context.state.copy()
|
||||
current_extra["__global_max_cycles__"] = getattr(
|
||||
resources.config, "global_max_cycles", None
|
||||
)
|
||||
current_extra["__sys_capabilities"] = getattr(run_context, "capabilities", [])
|
||||
current_extra["run_context"] = run_context
|
||||
|
||||
flattened_messages = ContextConverter.flatten_to_llm_messages(
|
||||
messages, run_context
|
||||
)
|
||||
|
||||
if run_context.run.event_bus:
|
||||
await run_context.run.event_bus.emit(
|
||||
LLMStartEvent(
|
||||
model_name=resources.model_name or "unknown",
|
||||
messages=flattened_messages,
|
||||
)
|
||||
)
|
||||
|
||||
response = await self._execute_model_request(
|
||||
model_name=resources.model_name,
|
||||
messages=flattened_messages,
|
||||
config=resources.generation_config or GenerationConfig(),
|
||||
run_context=run_context,
|
||||
tools=tools,
|
||||
tool_choice=tool_choice,
|
||||
extra=current_extra,
|
||||
cancellation_token=cancellation_token,
|
||||
)
|
||||
|
||||
if run_context.run.event_bus:
|
||||
await run_context.run.event_bus.emit(LLMEndEvent(response=response))
|
||||
|
||||
assistant_content = (
|
||||
response.content_parts if response.content_parts else response.text
|
||||
)
|
||||
if response.thought_signature and isinstance(assistant_content, list):
|
||||
for part in assistant_content:
|
||||
if part.type == "thought":
|
||||
if part.metadata is None:
|
||||
part.metadata = {}
|
||||
part.metadata["thought_signature"] = response.thought_signature
|
||||
break
|
||||
|
||||
assistant_message = AssistantMessage(
|
||||
content=cast(list[AssistantContentUnion], response.content_parts)
|
||||
)
|
||||
|
||||
if hasattr(response, "parsed_obj") and response.parsed_obj is not None:
|
||||
if not isinstance(response.parsed_obj, str):
|
||||
if assistant_message.metadata is None:
|
||||
assistant_message.metadata = {}
|
||||
assistant_message.metadata["parsed_obj"] = response.parsed_obj
|
||||
|
||||
usage_obj = parse_usage_info(response.usage_info)
|
||||
state.usage += usage_obj
|
||||
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)
|
||||
|
||||
return response
|
||||
|
||||
async def _execute_model_request(
|
||||
self,
|
||||
model_name: str | None,
|
||||
messages: list[LLMMessage],
|
||||
config: GenerationConfig,
|
||||
run_context: RunContext,
|
||||
tools: list[Any] | None = None,
|
||||
tool_choice: Any = None,
|
||||
extra: dict[str, Any] | None = None,
|
||||
cancellation_token: Any = None,
|
||||
) -> ChatResponse:
|
||||
from zhenxun.services.ai.capabilities import CombinedCapability
|
||||
from zhenxun.services.ai.llm.engine.router import LLMOrchestrator
|
||||
|
||||
request = ChatRequest(
|
||||
messages=messages,
|
||||
config=config,
|
||||
tools=tools,
|
||||
tool_choice=tool_choice,
|
||||
extra=extra or {},
|
||||
)
|
||||
|
||||
sys_caps = request.extra.pop("__sys_capabilities", [])
|
||||
llm_context = LLMContext(request=request, cancellation_token=cancellation_token)
|
||||
combined_cap = CombinedCapability(sys_caps)
|
||||
|
||||
async def inner_handler(ctx: LLMContext[Any, Any]) -> ChatResponse:
|
||||
return await LLMOrchestrator.invoke(
|
||||
request=ctx.request,
|
||||
model_name=model_name,
|
||||
task="chat",
|
||||
override_config=config,
|
||||
cancellation_token=ctx.cancellation_token,
|
||||
)
|
||||
|
||||
return await combined_cap.wrap_model_request(
|
||||
run_context, llm_context, inner_handler
|
||||
)
|
||||
|
||||
async def on_start(self, state: AgentState, resources: AgentRunResources) -> None:
|
||||
resources.run_context.run.messages = state.messages
|
||||
|
||||
async def run(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> AgentRunResult[Any]:
|
||||
"""覆盖基类的模板方法,实现灵活的 while 控制流和 FOLLOW_UP 合并"""
|
||||
await self.on_start(state, resources)
|
||||
from zhenxun.services.ai.run.session import session_manager
|
||||
|
||||
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)
|
||||
|
||||
await self.handle_llm_response(state, resources)
|
||||
if state.is_finished:
|
||||
if self._check_follow_up(state, resources, session_info):
|
||||
cycle_count = 0
|
||||
continue
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
await self.filter_tool_calls(state, resources)
|
||||
if state.is_finished:
|
||||
if self._check_follow_up(state, resources, session_info):
|
||||
cycle_count = 0
|
||||
continue
|
||||
assert state.final_result is not None
|
||||
return state.final_result
|
||||
|
||||
await self.execute_tools(state, resources)
|
||||
|
||||
await self.handle_tool_results(state, resources)
|
||||
if state.is_finished:
|
||||
if self._check_follow_up(state, resources, session_info):
|
||||
cycle_count = 0
|
||||
continue
|
||||
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
|
||||
|
||||
async def on_cycle_start(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
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
|
||||
)
|
||||
logger.debug(
|
||||
f"[TokenTracker] (Iter {state.current_cycle + 1}) "
|
||||
f"预估将消耗 {est_tokens} Token "
|
||||
f"(Model: {resources.model_name or 'Unknown'})"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def build_llm_request(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
run_context = resources.run_context
|
||||
|
||||
from zhenxun.services.ai.run.session import session_manager
|
||||
|
||||
session_info = await session_manager.get_or_create(
|
||||
run_context.session_id or "default_session"
|
||||
)
|
||||
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)
|
||||
|
||||
messages_to_send = []
|
||||
if state.static_system_prompt:
|
||||
if isinstance(state.static_system_prompt, list):
|
||||
for sp in state.static_system_prompt:
|
||||
if sp and sp.strip():
|
||||
messages_to_send.append(LLMMessage.system(sp))
|
||||
else:
|
||||
if state.static_system_prompt and state.static_system_prompt.strip():
|
||||
messages_to_send.append(
|
||||
LLMMessage.system(state.static_system_prompt)
|
||||
)
|
||||
|
||||
if state.dynamic_system_messages:
|
||||
messages_to_send.extend(state.dynamic_system_messages)
|
||||
|
||||
if (
|
||||
hasattr(run_context.run, "dynamic_prompts")
|
||||
and run_context.run.dynamic_prompts
|
||||
):
|
||||
for prompt_text in run_context.run.dynamic_prompts.values():
|
||||
if prompt_text and prompt_text.strip():
|
||||
messages_to_send.append(LLMMessage.system(prompt_text))
|
||||
|
||||
messages_to_send.extend(state.messages)
|
||||
|
||||
state.current_request_messages = messages_to_send
|
||||
|
||||
async def execute_llm(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
tools = state.tools
|
||||
|
||||
state.current_response = await self._invoke_and_record_llm(
|
||||
state=state,
|
||||
resources=resources,
|
||||
messages=state.current_request_messages,
|
||||
tools=list(tools) if tools else None,
|
||||
tool_choice=None,
|
||||
)
|
||||
|
||||
async def handle_llm_response(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
response = state.current_response
|
||||
if not response:
|
||||
return
|
||||
|
||||
if not response.tool_calls:
|
||||
logger.debug("✅ AgentExecutor:模型未请求工具调用,推理循环结束。")
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=response.text,
|
||||
messages=state.messages,
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
async def filter_tool_calls(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
response = state.current_response
|
||||
if not response:
|
||||
return
|
||||
tools = state.tools
|
||||
event_bus = resources.run_context.run.event_bus
|
||||
|
||||
completed_call_ids = {
|
||||
p.tool_call_id
|
||||
for p in response.content_parts
|
||||
if isinstance(p, ToolReturnPart)
|
||||
}
|
||||
client_tool_calls = []
|
||||
for call in response.tool_calls:
|
||||
tool_inst = tools.get(call.tool_name) if tools else None
|
||||
is_server_side = call.id in completed_call_ids or (
|
||||
tool_inst and getattr(tool_inst, "execution_side", "client") == "server"
|
||||
)
|
||||
|
||||
if is_server_side:
|
||||
logger.debug(
|
||||
"☁️ [AgentExecutor] 检测到云端工具调用: "
|
||||
f"{call.tool_name},已跳过本地执行。"
|
||||
)
|
||||
async with self.tool_executor._tool_stream_scope(
|
||||
event_bus,
|
||||
call.tool_name,
|
||||
call.args if isinstance(call.args, dict) else {},
|
||||
getattr(call, "intent", None),
|
||||
) as box:
|
||||
return_part = next(
|
||||
(
|
||||
p
|
||||
for p in response.content_parts
|
||||
if isinstance(p, ToolReturnPart)
|
||||
and p.tool_call_id == call.id
|
||||
),
|
||||
None,
|
||||
)
|
||||
if return_part:
|
||||
from zhenxun.services.ai.tools.models import ToolResult
|
||||
|
||||
box["result"] = ToolResult(output=return_part.output)
|
||||
else:
|
||||
client_tool_calls.append(call)
|
||||
|
||||
if not client_tool_calls:
|
||||
logger.info("✅ AgentExecutor:无本地客户端工具需执行,推理循环平滑结束。")
|
||||
|
||||
state.is_finished = True
|
||||
state.final_result = model_construct(
|
||||
AgentRunResult,
|
||||
output=response.text,
|
||||
messages=state.messages,
|
||||
usage=state.usage,
|
||||
)
|
||||
|
||||
state.current_tool_calls = client_tool_calls
|
||||
|
||||
async def execute_tools(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> None:
|
||||
run_context = resources.run_context
|
||||
tools = state.tools
|
||||
event_bus = run_context.run.event_bus
|
||||
tool_calls = state.current_tool_calls
|
||||
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
val_tasks = [
|
||||
self.tool_executor.validate_tool_call(
|
||||
call,
|
||||
tools,
|
||||
run_context,
|
||||
event_bus=event_bus,
|
||||
)
|
||||
for call in tool_calls
|
||||
]
|
||||
validated_calls = await asyncio.gather(*val_tasks)
|
||||
|
||||
exec_tasks = [
|
||||
self.tool_executor.execute_tool_call(
|
||||
val_call,
|
||||
tools,
|
||||
run_context,
|
||||
event_bus=event_bus,
|
||||
)
|
||||
for val_call in validated_calls
|
||||
]
|
||||
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: Any,
|
||||
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:
|
||||
"""处理所有工具执行结果,调度副作用指令并装配对话回传报文。"""
|
||||
tool_calls = state.current_tool_calls
|
||||
tool_results = state.current_tool_results
|
||||
if not tool_calls or not tool_results:
|
||||
return
|
||||
|
||||
from zhenxun.services.ai.flow.agent.engine.directive import directive_manager
|
||||
|
||||
for i, res_or_exc in enumerate(tool_results):
|
||||
original_call = tool_calls[i]
|
||||
tool_res = None
|
||||
|
||||
if not isinstance(res_or_exc, BaseException):
|
||||
_, raw_tool_res = res_or_exc
|
||||
tool_res = raw_tool_res
|
||||
|
||||
msg = self._assemble_tool_message(
|
||||
original_call, res_or_exc, tool_res, state
|
||||
)
|
||||
state.messages.append(msg)
|
||||
|
||||
if tool_res and getattr(tool_res, "directive", None):
|
||||
ns = getattr(resources.run_context.session, "namespace", "global")
|
||||
handler = directive_manager.get_handler(
|
||||
tool_res.directive.name, namespace=ns
|
||||
)
|
||||
|
||||
if handler:
|
||||
await handler(state, resources, tool_res)
|
||||
if state.is_finished:
|
||||
resources.run_context.session.append_only_manager.sync_messages(
|
||||
state.messages
|
||||
)
|
||||
return
|
||||
else:
|
||||
logger.warning(
|
||||
f"⚠️ 未能找到名为 '{tool_res.directive.name}' "
|
||||
f"的指令处理器 (Namespace: {ns})"
|
||||
)
|
||||
|
||||
resources.run_context.session.append_only_manager.sync_messages(state.messages)
|
||||
|
||||
async def on_fallback(
|
||||
self, state: AgentState, resources: AgentRunResources
|
||||
) -> AgentRunResult[Any]:
|
||||
run_context = resources.run_context
|
||||
event_bus = run_context.run.event_bus
|
||||
|
||||
if not resources.config.enable_fallback_summary:
|
||||
raise UpstreamServerException(
|
||||
f"超过最大工具调用循环次数 ({resources.config.max_cycles})。",
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
f"AgentExecutor 达到最大循环次数 ({resources.config.max_cycles}),"
|
||||
"触发兜底总结机制。"
|
||||
)
|
||||
|
||||
if event_bus:
|
||||
await event_bus.emit(
|
||||
ToolStreamChunkEvent(
|
||||
tool_name="System",
|
||||
content="⏳ 思考过程过于复杂,正在强制生成最终总结...",
|
||||
)
|
||||
)
|
||||
|
||||
fallback_msg = LLMMessage.user(
|
||||
"### 🚨 [系统强制指令]\n"
|
||||
"你的任务执行已达到最大工具调用循环次数上限,当前思考流已被框架强制中断。\n"
|
||||
"请**诚实地**向用户总结:你目前进行到了哪一步?遇到了什么困难导致循环耗尽?还有哪些预期步骤未能完成?\n"
|
||||
"**绝对禁止**对用户撒谎声声称你已经完成了任务。严禁再次尝试调用任何工具!请直接输出纯文本结果。"
|
||||
)
|
||||
state.messages.append(fallback_msg)
|
||||
|
||||
fallback_response = await self._invoke_and_record_llm(
|
||||
state=state,
|
||||
resources=resources,
|
||||
messages=state.messages,
|
||||
tools=[],
|
||||
tool_choice="none",
|
||||
)
|
||||
|
||||
return model_construct(
|
||||
AgentRunResult,
|
||||
output=fallback_response.text,
|
||||
messages=state.messages,
|
||||
structured_data=None,
|
||||
usage=state.usage,
|
||||
)
|
||||
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
Agent 相关静态声明类型定义
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from zhenxun.services.ai.context.memory.types import SessionMetadata
|
||||
from zhenxun.services.ai.core.messages import (
|
||||
AgentMessage,
|
||||
ChatResponse,
|
||||
LLMMessage,
|
||||
ToolCallPart,
|
||||
UsageInfo,
|
||||
)
|
||||
from zhenxun.services.ai.core.options import GenerationConfig
|
||||
from zhenxun.services.ai.flow.base import BaseRuntimeConfig
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.tools.engine.registry import ToolCollection
|
||||
from zhenxun.services.ai.tools.models import GlobalToolFilter
|
||||
from zhenxun.utils.pydantic_compat import model_copy
|
||||
|
||||
|
||||
class Persona(BaseModel):
|
||||
"""智能体人设与上下文背景"""
|
||||
|
||||
role: str = Field(...)
|
||||
"""扮演的角色身份"""
|
||||
|
||||
goal: str = Field(...)
|
||||
"""角色的核心目标"""
|
||||
|
||||
backstory: str | None = Field(default=None)
|
||||
"""角色背景故事或性格设定"""
|
||||
|
||||
model_config = ConfigDict(extra="ignore") # type: ignore
|
||||
|
||||
|
||||
class AgentConfig(BaseRuntimeConfig):
|
||||
"""统一的智能体全局与单次运行配置 (Unification of Settings & Profile)"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
max_cycles: int = Field(default=10)
|
||||
"""工具调用最大循环次数"""
|
||||
global_max_cycles: int | None = Field(default=None)
|
||||
"""整个会话生命周期内的绝对最大循环次数上限(覆盖全局配置)。"""
|
||||
enable_parallel_calls: bool = Field(default=True)
|
||||
"""允许并行工具调用"""
|
||||
reflexion_retries: int = Field(default=1)
|
||||
"""反思重试次数"""
|
||||
enable_fallback_summary: bool = Field(default=True)
|
||||
"""达到最大循环次数时,是否触发大模型兜底总结(而不是直接报错)"""
|
||||
enable_hitl: bool | None = Field(default=None)
|
||||
"""是否允许智能体主动挂起任务,向用户求助 (Human-in-the-Loop)。
|
||||
若为 None 则跟随全局设置。
|
||||
"""
|
||||
|
||||
message_history: Sequence[AgentMessage] | None = Field(default=None)
|
||||
"""初始化的底层对话历史记录。"""
|
||||
tool_filter: GlobalToolFilter | None = Field(default=None)
|
||||
"""全局工具过滤器,限制本次运行可用的工具池。"""
|
||||
memory: Any | None = Field(default=None)
|
||||
"""单次运行级别的记忆门面覆盖 (支持 bool, MemoryConfig, MemoryBuilder)。"""
|
||||
generation_config: GenerationConfig | None = Field(default=None)
|
||||
"""单次运行覆盖的大模型生成配置。"""
|
||||
capabilities: list[Any] | None = Field(default=None)
|
||||
"""仅针对本次运行动态注入的临时拦截器/能力组件列表。"""
|
||||
skills: Sequence[Any] | None = Field(default=None)
|
||||
"""仅针对本次运行动态注入的临时技能集合。"""
|
||||
executor: Any | None = Field(default=None)
|
||||
"""单次运行覆盖的核心执行引擎策略 (BaseAgentExecutor)。"""
|
||||
|
||||
verbose_ui: bool = Field(default=False)
|
||||
"""是否在 UI 前端展示细粒度的工具执行中间过程。
|
||||
在不支持流式更新的平台(如QQ)建议保持 False。"""
|
||||
|
||||
def merge_with(self, other: "AgentConfig | dict | None") -> "AgentConfig":
|
||||
"""深度合并另一份配置,生成一个新的覆盖实例"""
|
||||
|
||||
if not other:
|
||||
return model_copy(self, deep=True)
|
||||
|
||||
update_dict = {}
|
||||
if isinstance(other, dict):
|
||||
other_dict = {k: v for k, v in other.items() if v is not None}
|
||||
else:
|
||||
fields_set = getattr(
|
||||
other, "model_fields_set", getattr(other, "__fields_set__", set())
|
||||
)
|
||||
other_dict = {}
|
||||
for k in fields_set:
|
||||
val = getattr(other, k)
|
||||
if val is not None:
|
||||
other_dict[k] = val
|
||||
|
||||
for k, v in other_dict.items():
|
||||
if k in ("capabilities", "skills") and isinstance(v, list):
|
||||
base_list = getattr(self, k) or []
|
||||
update_dict[k] = base_list + v
|
||||
else:
|
||||
update_dict[k] = v
|
||||
|
||||
return model_copy(self, update=update_dict, deep=True)
|
||||
|
||||
|
||||
class AgentState(BaseModel):
|
||||
"""大模型思考循环的有限状态机 (FSM) 流转状态"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
static_system_prompt: str | list[str] = ""
|
||||
"""绝对不变的系统提示词(用于前缀缓存)"""
|
||||
dynamic_system_messages: list[LLMMessage] = Field(default_factory=list)
|
||||
"""包含变量与实时状态的动态独立提示消息列表(绝对头部注入)"""
|
||||
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: Any | None = None
|
||||
"""标记是否触发了移交"""
|
||||
is_finished: bool = False
|
||||
"""标记大模型循环是否彻底结束"""
|
||||
final_result: Any | None = None
|
||||
"""最终的运行结果 (AgentRunResult)"""
|
||||
origin_msg_len: int = 0
|
||||
"""初始进入循环时的消息历史长度 (用于增量保存记忆)"""
|
||||
current_cycle: int = 0
|
||||
"""当前思考循环的轮次索引"""
|
||||
|
||||
current_request_messages: list[AgentMessage] = Field(default_factory=list)
|
||||
"""当前即将发往大模型的实际请求消息"""
|
||||
current_request_extra: dict[str, Any] = Field(default_factory=dict)
|
||||
"""当前请求附加的Extra控制参数"""
|
||||
current_response: ChatResponse | None = None
|
||||
"""大模型最新返回的响应实体"""
|
||||
current_tool_calls: list[ToolCallPart] = Field(default_factory=list)
|
||||
"""当前轮次被提取出准备执行的客户端工具调用"""
|
||||
current_tool_results: list[Any] = Field(default_factory=list)
|
||||
"""当前轮次工具执行的结果或异常收集"""
|
||||
|
||||
|
||||
class AgentRunResources(BaseModel):
|
||||
"""大模型执行过程中的全局静态资源与配置载体"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
run_context: RunContext
|
||||
"""保留依赖注入(DI)与黑板引用的全局运行时上下文"""
|
||||
session_meta: SessionMetadata | None = None
|
||||
"""隔离会话的元信息(Session ID, 命名空间, 权限等)"""
|
||||
memory_reader: Any | None = None
|
||||
"""用于读取短/中/长期上下文记忆的读取器"""
|
||||
memory_writer: Any | None = None
|
||||
"""用于将对话历史安全落盘的写入器"""
|
||||
run_scoped_cap: Any | None = None
|
||||
"""聚合了 Agent/Task/全局 的复合能力拦截器 (CombinedCapability)"""
|
||||
task_obj: Any | None = None
|
||||
"""(如有) 解析后的结构化数据任务契约"""
|
||||
toolkits: list[Any] = Field(default_factory=list)
|
||||
"""当前轮次生效的工具箱列表 (需要执行生命周期挂载)"""
|
||||
config: AgentConfig = Field(default_factory=AgentConfig)
|
||||
"""Agent 全局与运行时的统一策略配置"""
|
||||
generation_config: GenerationConfig | None = None
|
||||
"""大模型生成配置"""
|
||||
model_name: str | None = None
|
||||
"""当前实际调用的模型名称"""
|
||||
Reference in New Issue
Block a user