mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-13 07:50:00 +08:00
♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统 (#2149)
* ♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统 - 【重构】重构 `BaseRunnable` 并引入统一的 `RunIntent` 意图载体,规范 Agent、Team 和 Workflow 的执行流 - 【解耦】将中期记忆槽和长期向量记忆从 `MemoryConfig` 中解耦,转为独立的能力组件与工具箱进行管理 - 【记忆】移除 `MemoryReader` 和 `MemoryWriter`,统一封装为 `SessionMemoryContext` 会话记忆门面 - 【RAG】重构检索器与存储后端接口,统一采用 `QueryRequest` 进行多维度联合检索,并引入 `InMemoryScorer` 提升打分性能 - 【事件】优化 `EventBus` 异步事件分发机制,引入队列机制确保事件按序处理,避免并发竞态问题 - 【依赖注入】移除 `memory` 注入项,优化 `DependencyInjector` 的签名解析缓存以提升性能 * 🚨 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
922d092650
commit
52f7dbdedf
@@ -1,13 +1,11 @@
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
import inspect
|
||||
from typing import Any, cast
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
|
||||
from zhenxun.services.ai.capabilities import AbstractCapability
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.run import RunContext
|
||||
from zhenxun.services.ai.run.di import DependencyInjector
|
||||
from zhenxun.services.ai.tools.bridges.handoff import HandoffTool
|
||||
from zhenxun.services.ai.tools.core.tool import BaseTool
|
||||
|
||||
from .models import Transition
|
||||
|
||||
@@ -18,8 +16,8 @@ class TeamRoutingCapability(AbstractCapability):
|
||||
def __init__(
|
||||
self,
|
||||
team_name: str,
|
||||
members: list[Any],
|
||||
state_flow: Mapping[str, Sequence[Any]] | Callable | None = None,
|
||||
members: list[BaseRunnable],
|
||||
state_flow: Mapping[str, Sequence[Transition | str]] | Callable | None = None,
|
||||
max_handoffs: int = 3,
|
||||
):
|
||||
self.team_name = team_name
|
||||
@@ -27,7 +25,9 @@ class TeamRoutingCapability(AbstractCapability):
|
||||
self.state_flow = state_flow
|
||||
self.max_handoffs = max_handoffs
|
||||
|
||||
async def _get_allowed_transitions(self, context: RunContext) -> list[Any] | None:
|
||||
async def _get_allowed_transitions(
|
||||
self, context: RunContext
|
||||
) -> list[Transition] | None:
|
||||
"""核心FSM解析:解析静态字典或动态执行函数获取允许的 Transition 列表"""
|
||||
if self.state_flow is None:
|
||||
return None
|
||||
@@ -45,16 +45,10 @@ class TeamRoutingCapability(AbstractCapability):
|
||||
]
|
||||
|
||||
if callable(self.state_flow):
|
||||
sig = inspect.signature(self.state_flow)
|
||||
kwargs = await DependencyInjector.resolve_all(
|
||||
sig, call_kwargs={}, context=context
|
||||
result = await DependencyInjector.invoke(
|
||||
self.state_flow, call_kwargs={}, context=context
|
||||
)
|
||||
|
||||
if is_coroutine_callable(self.state_flow):
|
||||
result = await cast(Callable, self.state_flow)(**kwargs)
|
||||
else:
|
||||
result = cast(Callable, self.state_flow)(**kwargs)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
@@ -62,7 +56,7 @@ class TeamRoutingCapability(AbstractCapability):
|
||||
|
||||
return None
|
||||
|
||||
async def get_tools(self, context: RunContext) -> list[Any]:
|
||||
async def get_tools(self, context: RunContext) -> list[BaseTool]:
|
||||
tools = []
|
||||
allowed_transitions = await self._get_allowed_transitions(context)
|
||||
|
||||
@@ -81,10 +75,7 @@ class TeamRoutingCapability(AbstractCapability):
|
||||
if transition is None:
|
||||
continue
|
||||
|
||||
if getattr(m, "persona", None):
|
||||
desc = f"角色:{m.persona.role},目标:{m.persona.goal}"
|
||||
else:
|
||||
desc = getattr(m, "description", "") or "处理节点"
|
||||
desc = m.profile_summary
|
||||
|
||||
if transition and getattr(transition, "description", ""):
|
||||
desc += f" 【移交条件】:{transition.description}"
|
||||
|
||||
@@ -7,9 +7,10 @@ import uuid
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from zhenxun.services.ai.core.messages import AgentMessage
|
||||
from zhenxun.services.ai.core.messages import AgentMessage, PromptInput
|
||||
from zhenxun.services.ai.core.options import BaseOutputDefinition
|
||||
from zhenxun.services.ai.flow.base import BaseRunnable, BaseRuntimeConfig
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.flow.core.models import BaseRuntimeConfig
|
||||
from zhenxun.services.ai.run import AgentTask
|
||||
|
||||
|
||||
@@ -67,7 +68,7 @@ class CallAction(TeamAction):
|
||||
|
||||
agent: str | BaseRunnable[Any]
|
||||
"""目标 Agent 的名称(字符串)或动态生成的 Agent 实例"""
|
||||
task: str | AgentTask
|
||||
task: PromptInput | AgentTask
|
||||
"""派发给该 Agent 的具体任务或提示词"""
|
||||
history: Sequence[AgentMessage] | None = None
|
||||
"""需要传递给该 Agent 的上下文历史记录(可选)"""
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
import inspect
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
import re
|
||||
from typing import Any, cast
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
|
||||
from zhenxun.services.ai.core.messages import AgentMessage
|
||||
from zhenxun.services.ai.core.templates import PromptTemplate
|
||||
from zhenxun.services.ai.run import AgentTask, RunContext
|
||||
from zhenxun.services.ai.flow.agent.agent import Agent, ToolSource
|
||||
from zhenxun.services.ai.flow.agent.models import AgentConfig
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.run import RunContext, RunIntent
|
||||
from zhenxun.services.ai.run.di import DependencyInjector
|
||||
from zhenxun.services.ai.utils.logger import log_team as logger
|
||||
|
||||
from .models import RouteDecision, Transition
|
||||
from .capabilities import TeamRoutingCapability
|
||||
from .models import RouteDecision, TeamRuntimeConfig, Transition
|
||||
|
||||
|
||||
class BaseRouter(ABC):
|
||||
@@ -23,7 +23,7 @@ class BaseRouter(ABC):
|
||||
self,
|
||||
context: RunContext,
|
||||
history: Sequence[AgentMessage],
|
||||
prompt: str | AgentTask | None = None,
|
||||
intent: RunIntent,
|
||||
) -> RouteDecision | None:
|
||||
"""核心路由方法"""
|
||||
pass
|
||||
@@ -32,7 +32,9 @@ class BaseRouter(ABC):
|
||||
class FunctionRouter(BaseRouter):
|
||||
"""基于纯函数的极速路由器"""
|
||||
|
||||
def __init__(self, selector_func: Callable[..., Any], target: str | None = None):
|
||||
def __init__(
|
||||
self, selector_func: Callable[..., str | bool | None], target: str | None = None
|
||||
):
|
||||
"""
|
||||
初始化基于函数的极速路由器。
|
||||
|
||||
@@ -47,27 +49,21 @@ class FunctionRouter(BaseRouter):
|
||||
self,
|
||||
context: RunContext,
|
||||
history: Sequence[AgentMessage],
|
||||
prompt: str | AgentTask | None = None,
|
||||
intent: RunIntent,
|
||||
) -> RouteDecision | None:
|
||||
sig = inspect.signature(self.selector_func)
|
||||
call_kwargs = {"prompt": prompt, "context": context, "history": history}
|
||||
if isinstance(prompt, AgentTask):
|
||||
call_kwargs["agent_task"] = prompt
|
||||
call_kwargs["task"] = prompt
|
||||
|
||||
kwargs_resolved = await DependencyInjector.resolve_all(
|
||||
sig, call_kwargs, context
|
||||
)
|
||||
filtered_kwargs = {
|
||||
k: v for k, v in kwargs_resolved.items() if k in sig.parameters
|
||||
call_kwargs = {
|
||||
"intent": intent,
|
||||
"prompt": intent.original_input,
|
||||
"context": context,
|
||||
"history": history,
|
||||
}
|
||||
if intent.task_obj:
|
||||
call_kwargs["agent_task"] = intent.task_obj
|
||||
call_kwargs["task"] = intent.task_obj
|
||||
|
||||
if is_coroutine_callable(self.selector_func):
|
||||
_async_func = cast(Callable[..., Awaitable[Any]], self.selector_func)
|
||||
selected_target = await _async_func(**filtered_kwargs)
|
||||
else:
|
||||
_sync_func = cast(Callable[..., Any], self.selector_func)
|
||||
selected_target = _sync_func(**filtered_kwargs)
|
||||
selected_target = await DependencyInjector.invoke(
|
||||
self.selector_func, call_kwargs, context
|
||||
)
|
||||
|
||||
if isinstance(selected_target, bool):
|
||||
if selected_target and self.target:
|
||||
@@ -97,13 +93,9 @@ class RegexRouter(BaseRouter):
|
||||
self,
|
||||
context: RunContext,
|
||||
history: Sequence[AgentMessage],
|
||||
prompt: str | AgentTask | None = None,
|
||||
intent: RunIntent,
|
||||
) -> RouteDecision | None:
|
||||
text_to_match = (
|
||||
prompt.description
|
||||
if isinstance(prompt, AgentTask)
|
||||
else (prompt or context.run.user_input or "")
|
||||
)
|
||||
text_to_match = intent.text or context.run.user_input or ""
|
||||
|
||||
if self.pattern.search(text_to_match):
|
||||
logger.debug(f"命中正则极速路由 -> {self.target}")
|
||||
@@ -127,10 +119,10 @@ class ChainRouter(BaseRouter):
|
||||
self,
|
||||
context: RunContext,
|
||||
history: Sequence[AgentMessage],
|
||||
prompt: str | AgentTask | None = None,
|
||||
intent: RunIntent,
|
||||
) -> RouteDecision | None:
|
||||
for router in self.routers:
|
||||
decision = await router.route(context, history, prompt)
|
||||
decision = await router.route(context, history, intent)
|
||||
if decision is not None:
|
||||
return decision
|
||||
return None
|
||||
@@ -142,11 +134,11 @@ class LLMRouter(BaseRouter):
|
||||
def __init__(
|
||||
self,
|
||||
team_name: str,
|
||||
members: list[Any],
|
||||
members: list[BaseRunnable],
|
||||
leader_model: str | None = None,
|
||||
leader_tools: list[Any] | None = None,
|
||||
leader_tools: list[ToolSource] | None = None,
|
||||
state_flow: Mapping[str, Sequence[Transition | str]] | Callable | None = None,
|
||||
runtime_config: Any = None,
|
||||
runtime_config: TeamRuntimeConfig | None = None,
|
||||
custom_prompt: str | None = None,
|
||||
allowed_transitions: list[Transition] | None = None,
|
||||
max_handoffs: int = 3,
|
||||
@@ -179,13 +171,8 @@ class LLMRouter(BaseRouter):
|
||||
self,
|
||||
context: RunContext,
|
||||
history: Sequence[AgentMessage],
|
||||
prompt: str | AgentTask | None = None,
|
||||
intent: RunIntent,
|
||||
) -> RouteDecision | None:
|
||||
from zhenxun.services.ai.flow.agent.agent import Agent
|
||||
from zhenxun.services.ai.flow.agent.models import AgentConfig
|
||||
|
||||
from .capabilities import TeamRoutingCapability
|
||||
|
||||
default_system_prompt = """## 角色与目标
|
||||
你是一个高级任务路由器 (所在团队: {{ team_name }})。
|
||||
请根据用户的输入意图,立刻调用相应的移交工具 (transfer_to_...)
|
||||
@@ -217,13 +204,6 @@ class LLMRouter(BaseRouter):
|
||||
)
|
||||
|
||||
target_model = self.leader_model
|
||||
if not target_model:
|
||||
for m in self.members:
|
||||
if m_model := getattr(m, "model_name", None) or getattr(
|
||||
m, "model", None
|
||||
):
|
||||
target_model = m_model
|
||||
break
|
||||
|
||||
router_agent = Agent(
|
||||
name=f"{self.team_name}_Router",
|
||||
@@ -240,7 +220,7 @@ class LLMRouter(BaseRouter):
|
||||
logger.debug("🤖 [LLMRouter] 启动 LLM 思考路由决策...")
|
||||
|
||||
res = await router_agent.run(
|
||||
prompt=prompt,
|
||||
prompt=intent.original_input,
|
||||
context=sub_context,
|
||||
config=AgentConfig(message_history=history),
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import asyncio
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .team import Team
|
||||
|
||||
from zhenxun.services.ai.core.exceptions import (
|
||||
AbortException,
|
||||
@@ -8,19 +11,19 @@ from zhenxun.services.ai.core.exceptions import (
|
||||
LLMException,
|
||||
)
|
||||
from zhenxun.services.ai.core.messages import UsageInfo
|
||||
from zhenxun.services.ai.core.stream_events import AgentStreamEvent
|
||||
from zhenxun.services.ai.flow.agent.models import AgentConfig
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext
|
||||
from zhenxun.services.ai.run import AgentRunResult, RunContext, RunIntent
|
||||
from zhenxun.services.ai.run.models import AgentRunEnd
|
||||
from zhenxun.services.ai.utils.logger import log_team as logger
|
||||
from zhenxun.utils.pydantic_compat import model_construct
|
||||
|
||||
from .capabilities import TeamRoutingCapability
|
||||
from .models import (
|
||||
CallAction,
|
||||
ConcurrentCallAction,
|
||||
FinishAction,
|
||||
)
|
||||
from .strategy import BaseTeamStrategy, RouteStrategy
|
||||
from .strategy import BaseTeamStrategy
|
||||
|
||||
|
||||
class TeamRunner:
|
||||
@@ -28,10 +31,33 @@ class TeamRunner:
|
||||
多智能体团队核心执行引擎。
|
||||
"""
|
||||
|
||||
def __init__(self, team: Any, strategy: BaseTeamStrategy):
|
||||
def __init__(self, team: "Team", strategy: BaseTeamStrategy):
|
||||
self.team = team
|
||||
self.strategy = strategy
|
||||
|
||||
async def _consume_event_queue(
|
||||
self,
|
||||
queue: asyncio.Queue,
|
||||
tasks: list[asyncio.Task],
|
||||
expected_results: int,
|
||||
results_box: dict[int, tuple[str, AgentRunResult]],
|
||||
) -> AsyncGenerator[AgentStreamEvent, None]:
|
||||
"""辅助方法:统一消费队列中的流事件、异常和结果"""
|
||||
try:
|
||||
while len(results_box) < expected_results:
|
||||
msg_type, *payload = await queue.get()
|
||||
if msg_type == "yield_event":
|
||||
yield payload[0]
|
||||
elif msg_type == "control_flow_error":
|
||||
raise payload[0]
|
||||
elif msg_type == "result":
|
||||
idx, agent_name, agent_res = payload
|
||||
results_box[idx] = (agent_name, agent_res)
|
||||
finally:
|
||||
for task in tasks:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
|
||||
async def _execute_call_action_to_queue(
|
||||
self,
|
||||
index: int,
|
||||
@@ -69,14 +95,9 @@ class TeamRunner:
|
||||
sub_context = context.clone_for_member(target_agent.name)
|
||||
sub_context.capabilities = list(sub_context.capabilities)
|
||||
|
||||
if isinstance(self.strategy, RouteStrategy):
|
||||
routing_cap = TeamRoutingCapability(
|
||||
team_name=self.team.name,
|
||||
members=self.team.members,
|
||||
state_flow=getattr(self.strategy, "state_flow", None),
|
||||
max_handoffs=getattr(self.strategy, "max_handoffs", 3),
|
||||
)
|
||||
sub_context.capabilities.append(routing_cap)
|
||||
sub_context.capabilities.extend(
|
||||
self.strategy.get_member_capabilities(self.team, target_agent)
|
||||
)
|
||||
|
||||
logger.debug(f"🚀 **专员 👨💼`{target_agent.name}`** 开始执行子任务...")
|
||||
|
||||
@@ -125,7 +146,7 @@ class TeamRunner:
|
||||
target_name = agent_res.handoff.target
|
||||
reason = agent_res.handoff.reason
|
||||
|
||||
logger.info(
|
||||
logger.debug(
|
||||
f"🛣️ **路由决策**: 委派给专员 👨💼`{target_name}` (理由: {reason})"
|
||||
)
|
||||
|
||||
@@ -138,15 +159,37 @@ class TeamRunner:
|
||||
|
||||
await queue.put(("result", index, target_agent.name, agent_res))
|
||||
|
||||
async def _dispatch_actions(
|
||||
self,
|
||||
actions: list[CallAction],
|
||||
context: RunContext,
|
||||
session_id: str,
|
||||
results_container: list,
|
||||
) -> AsyncGenerator[AgentStreamEvent, None]:
|
||||
"""统一的任务派发、流事件转译与结果回传调度器"""
|
||||
queue = asyncio.Queue()
|
||||
tasks = [
|
||||
asyncio.create_task(
|
||||
self._execute_call_action_to_queue(i, act, context, session_id, queue)
|
||||
)
|
||||
for i, act in enumerate(actions)
|
||||
]
|
||||
results_box: dict[int, tuple[str, AgentRunResult]] = {}
|
||||
async for event in self._consume_event_queue(
|
||||
queue, tasks, len(actions), results_box
|
||||
):
|
||||
yield event
|
||||
results_container.extend([results_box[i] for i in range(len(actions))])
|
||||
|
||||
async def run_stream(
|
||||
self, prompt: Any, context: RunContext, **kwargs: Any
|
||||
) -> AsyncGenerator[Any, None]:
|
||||
self, intent: RunIntent, context: RunContext, **kwargs: Any
|
||||
) -> AsyncGenerator[AgentStreamEvent, None]:
|
||||
session_id = context.session_id or "default_team_session"
|
||||
task_desc = getattr(prompt, "description", str(prompt))
|
||||
task_desc = intent.text
|
||||
|
||||
logger.info(f"🤝 **团队 [{self.team.name}] 开始协作**: `{task_desc}`")
|
||||
logger.debug(f"🤝 **团队 [{self.team.name}] 开始协作**: `{task_desc}`")
|
||||
|
||||
plan_gen = self.strategy.generate_plan(self.team, prompt, context, **kwargs)
|
||||
plan_gen = self.strategy.generate_plan(self.team, intent, context, **kwargs)
|
||||
|
||||
send_value = None
|
||||
final_result = None
|
||||
@@ -160,61 +203,23 @@ class TeamRunner:
|
||||
break
|
||||
|
||||
if isinstance(action, CallAction):
|
||||
queue = asyncio.Queue()
|
||||
task = asyncio.create_task(
|
||||
self._execute_call_action_to_queue(
|
||||
0, action, context, session_id, queue
|
||||
)
|
||||
)
|
||||
try:
|
||||
while True:
|
||||
msg_type, *payload = await queue.get()
|
||||
if msg_type == "yield_event":
|
||||
yield payload[0]
|
||||
elif msg_type == "control_flow_error":
|
||||
raise payload[0]
|
||||
elif msg_type == "result":
|
||||
idx, agent_name, agent_res = payload
|
||||
send_value = agent_res
|
||||
cumulative_usage += agent_res.usage
|
||||
break
|
||||
finally:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
res_container = []
|
||||
async for event in self._dispatch_actions(
|
||||
[action], context, session_id, res_container
|
||||
):
|
||||
yield event
|
||||
_, send_value = res_container[0]
|
||||
cumulative_usage += send_value.usage
|
||||
|
||||
elif isinstance(action, ConcurrentCallAction):
|
||||
queue = asyncio.Queue()
|
||||
tasks = []
|
||||
for i, act in enumerate(action.actions):
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._execute_call_action_to_queue(
|
||||
i, act, context, session_id, queue
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
results_dict = {}
|
||||
try:
|
||||
while len(results_dict) < len(action.actions):
|
||||
msg_type, *payload = await queue.get()
|
||||
if msg_type == "yield_event":
|
||||
yield payload[0]
|
||||
elif msg_type == "control_flow_error":
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
raise payload[0]
|
||||
elif msg_type == "result":
|
||||
idx, agent_name, agent_res = payload
|
||||
results_dict[idx] = (agent_name, agent_res)
|
||||
cumulative_usage += agent_res.usage
|
||||
send_value = [
|
||||
results_dict[i] for i in range(len(action.actions))
|
||||
]
|
||||
finally:
|
||||
for task in tasks:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
res_container = []
|
||||
async for event in self._dispatch_actions(
|
||||
action.actions, context, session_id, res_container
|
||||
):
|
||||
yield event
|
||||
send_value = res_container
|
||||
for _, res in res_container:
|
||||
cumulative_usage += res.usage
|
||||
|
||||
elif isinstance(action, FinishAction):
|
||||
final_result = action.result
|
||||
@@ -225,7 +230,7 @@ class TeamRunner:
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
logger.info(f"🏁 **团队 [{self.team.name}]** 协作圆满结束!")
|
||||
logger.debug(f"🏁 **团队 [{self.team.name}]** 协作圆满结束!")
|
||||
|
||||
if not isinstance(final_result, AgentRunResult):
|
||||
final_result = model_construct(
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
from abc import ABC
|
||||
from collections.abc import AsyncGenerator, Callable, Mapping, Sequence
|
||||
import json
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from zhenxun.services.ai.capabilities import AbstractCapability
|
||||
from zhenxun.services.ai.core.exceptions import AbortException
|
||||
from zhenxun.services.ai.core.messages import AgentMessage, LLMMessage
|
||||
from zhenxun.services.ai.core.stream_events import ToolStreamChunkEvent
|
||||
from zhenxun.services.ai.core.templates import PromptTemplate
|
||||
from zhenxun.services.ai.flow.agent.agent import Agent, ToolSource
|
||||
from zhenxun.services.ai.flow.agent.models import AgentConfig
|
||||
from zhenxun.services.ai.run import AgentTask, RunContext
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.run import RunContext, RunIntent
|
||||
from zhenxun.services.ai.run.blackboard import BlackboardManager
|
||||
from zhenxun.services.ai.tools.bridges.delegate import DelegateTool
|
||||
from zhenxun.services.ai.tools.providers.builtin.blackboard import BlackboardToolkit
|
||||
@@ -25,7 +29,7 @@ from .models import (
|
||||
TeamAction,
|
||||
Transition,
|
||||
)
|
||||
from .router import BaseRouter
|
||||
from .router import BaseRouter, ChainRouter, FunctionRouter, LLMRouter
|
||||
from .task_tools import TaskPlanningToolkit
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -50,10 +54,16 @@ class BaseTeamStrategy(ABC):
|
||||
template = self.custom_prompt or self.default_system_prompt
|
||||
return PromptTemplate(template).render(**kwargs)
|
||||
|
||||
def get_member_capabilities(
|
||||
self, team: "Team", member: BaseRunnable
|
||||
) -> list[AbstractCapability]:
|
||||
"""获取派发给子成员时需要动态注入的能力组件"""
|
||||
return []
|
||||
|
||||
async def generate_plan(
|
||||
self,
|
||||
team: "Team",
|
||||
prompt: str | AgentTask | None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[TeamAction, Any]:
|
||||
@@ -66,36 +76,39 @@ class BaseTeamStrategy(ABC):
|
||||
2. 使用 `yield FinishAction(...)` 结束团队协作。
|
||||
|
||||
"""
|
||||
yield FinishAction(
|
||||
result="The Strategy has not implemented generate_plan() yet."
|
||||
)
|
||||
yield FinishAction(result="该策略尚未实现 generate_plan() 方法。")
|
||||
|
||||
def _build_leader_agent(
|
||||
self, team: "Team", name: str, instruction: str, tools: list[ToolSource]
|
||||
self,
|
||||
team: "Team",
|
||||
role_name: str,
|
||||
extra_instruction: str = "",
|
||||
extra_tools: list[ToolSource] | None = None,
|
||||
) -> Agent:
|
||||
"""
|
||||
统一的团队 Leader / Planner 装配工厂。
|
||||
自动处理无状态配置以及 HITL 状态继承。
|
||||
统一的团队 Leader / Planner / Broadcaster 装配工厂。
|
||||
自动合并默认指令、追加指令、基类工具和策略专属工具,
|
||||
并处理无状态配置以及 HITL 状态继承。
|
||||
"""
|
||||
instruction = self.get_prompt()
|
||||
if extra_instruction:
|
||||
instruction += f"\n\n{extra_instruction}"
|
||||
|
||||
tools = getattr(self, "leader_tools", []).copy()
|
||||
if extra_tools:
|
||||
tools.extend(extra_tools)
|
||||
|
||||
leader_config = AgentConfig(
|
||||
stateless=team.runtime_config.stateless if team.runtime_config else True,
|
||||
enable_hitl=getattr(team.runtime_config, "leader_enable_hitl", False),
|
||||
)
|
||||
|
||||
target_model = getattr(self, "leader_model", None) or getattr(
|
||||
team, "model", None
|
||||
)
|
||||
if not target_model:
|
||||
for m in team.members:
|
||||
if m_model := getattr(m, "model_name", None) or getattr(
|
||||
m, "model", None
|
||||
):
|
||||
target_model = m_model
|
||||
break
|
||||
target_model = getattr(self, "leader_model", None) or team.default_model
|
||||
|
||||
return Agent(
|
||||
name=name,
|
||||
name=f"{team.name}_{role_name}",
|
||||
instruction=instruction,
|
||||
persona=team.persona,
|
||||
model=target_model,
|
||||
tools=tools,
|
||||
config=leader_config,
|
||||
@@ -107,7 +120,7 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
state_flow: "Mapping[str, Sequence[str | Any]] | Callable | None" = None,
|
||||
state_flow: Mapping[str, Sequence[str | Any]] | Callable | None = None,
|
||||
selector_func: Callable[..., str | None] | None = None,
|
||||
router: BaseRouter | None = None,
|
||||
leader_model: str | None = None,
|
||||
@@ -148,17 +161,79 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
else:
|
||||
self.state_flow = state_flow
|
||||
|
||||
def get_member_capabilities(
|
||||
self, team: "Team", member: BaseRunnable
|
||||
) -> list[AbstractCapability]:
|
||||
from .capabilities import TeamRoutingCapability
|
||||
|
||||
return [
|
||||
TeamRoutingCapability(
|
||||
team_name=team.name,
|
||||
members=team.members,
|
||||
state_flow=self.state_flow,
|
||||
max_handoffs=self.max_handoffs,
|
||||
)
|
||||
]
|
||||
|
||||
def _build_handoff_history(
|
||||
self, handoff_reason: str, context_data: Any
|
||||
) -> list[AgentMessage]:
|
||||
"""将移交数据格式化为系统的引导历史消息"""
|
||||
handoff_history_messages = []
|
||||
upstream_info = []
|
||||
if handoff_reason:
|
||||
upstream_info.append(f"【移交说明】\n{handoff_reason}")
|
||||
if context_data:
|
||||
if isinstance(context_data, dict):
|
||||
formatted_data = json.dumps(context_data, ensure_ascii=False, indent=2)
|
||||
upstream_info.append(
|
||||
f"【结构化上下文载荷】\n```json\n{formatted_data}\n```"
|
||||
)
|
||||
else:
|
||||
upstream_info.append(f"【核心上下文数据】\n{context_data}")
|
||||
|
||||
if combined_info := "\n\n".join(upstream_info):
|
||||
handoff_msg = LLMMessage.system(
|
||||
f"### 🔄 [来自上游节点的移交数据]\n{combined_info}"
|
||||
)
|
||||
handoff_history_messages.append(handoff_msg)
|
||||
return handoff_history_messages
|
||||
|
||||
def _check_fast_route(
|
||||
self, current_target: str, output_str: str
|
||||
) -> tuple[bool, str]:
|
||||
"""检查并执行快速硬路由,返回(是否触发路由, 新目标名称)"""
|
||||
if (
|
||||
not isinstance(self.state_flow, dict)
|
||||
or current_target not in self.state_flow
|
||||
):
|
||||
return False, current_target
|
||||
|
||||
for t in self.state_flow[current_target]:
|
||||
if trigger_regex := getattr(t, "trigger_regex", None):
|
||||
if re.search(trigger_regex, output_str):
|
||||
return True, getattr(t, "target", current_target)
|
||||
|
||||
if trigger_func := getattr(t, "trigger_func", None):
|
||||
try:
|
||||
res = trigger_func(output_str)
|
||||
if res:
|
||||
if isinstance(res, str):
|
||||
return True, res
|
||||
return True, getattr(t, "target", current_target)
|
||||
except Exception:
|
||||
pass
|
||||
return False, current_target
|
||||
|
||||
async def generate_plan(
|
||||
self,
|
||||
team: "Team",
|
||||
prompt: str | AgentTask | None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[TeamAction, Any]:
|
||||
router = self.router
|
||||
if not router:
|
||||
from .router import ChainRouter, FunctionRouter, LLMRouter
|
||||
|
||||
routers = []
|
||||
if self.selector_func:
|
||||
routers.append(FunctionRouter(self.selector_func))
|
||||
@@ -166,7 +241,7 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
LLMRouter(
|
||||
team_name=team.name,
|
||||
members=team.members,
|
||||
leader_model=self.leader_model,
|
||||
leader_model=self.leader_model or team.default_model,
|
||||
leader_tools=self.leader_tools,
|
||||
state_flow=self.state_flow,
|
||||
runtime_config=getattr(team, "runtime_config", None),
|
||||
@@ -182,7 +257,7 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
|
||||
logger.debug(f"🛣️ '{team.name}' 正在获取初始路由决策...")
|
||||
|
||||
decision = await router.route(context, [], prompt)
|
||||
decision = await router.route(context, [], intent)
|
||||
if not decision:
|
||||
logger.warning(f"🚨 Team '{team.name}' 的所有路由策略未能命中目标。")
|
||||
raise AbortException(
|
||||
@@ -210,33 +285,13 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
display="🚨 团队协作陷入死循环,已被系统强制中断。",
|
||||
)
|
||||
|
||||
handoff_history_messages: list[AgentMessage] = []
|
||||
upstream_info = []
|
||||
if handoff_reason:
|
||||
upstream_info.append(f"【移交说明】\n{handoff_reason}")
|
||||
if context_data:
|
||||
if isinstance(context_data, dict):
|
||||
import json
|
||||
|
||||
formatted_data = json.dumps(
|
||||
context_data, ensure_ascii=False, indent=2
|
||||
)
|
||||
upstream_info.append(
|
||||
f"【结构化上下文载荷】\n```json\n{formatted_data}\n```"
|
||||
)
|
||||
else:
|
||||
upstream_info.append(f"【核心上下文数据】\n{context_data}")
|
||||
combined_info = "\n\n".join(upstream_info)
|
||||
|
||||
if combined_info:
|
||||
handoff_msg = LLMMessage.system(
|
||||
f"### 🔄 [来自上游节点的移交数据]\n{combined_info}"
|
||||
)
|
||||
handoff_history_messages.append(handoff_msg)
|
||||
handoff_history_messages = self._build_handoff_history(
|
||||
handoff_reason, context_data
|
||||
)
|
||||
|
||||
run_result = yield CallAction(
|
||||
agent=current_target,
|
||||
task=prompt or "",
|
||||
task=intent.original_input or "",
|
||||
history=handoff_history_messages,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
@@ -249,32 +304,11 @@ class RouteStrategy(BaseTeamStrategy):
|
||||
|
||||
output_str = str(run_result.output)
|
||||
|
||||
fast_routed = False
|
||||
if isinstance(self.state_flow, dict) and current_target in self.state_flow:
|
||||
for t in self.state_flow[current_target]:
|
||||
trigger_regex = getattr(t, "trigger_regex", None)
|
||||
if trigger_regex:
|
||||
import re
|
||||
|
||||
if re.search(trigger_regex, output_str):
|
||||
current_target = getattr(t, "target", current_target)
|
||||
handoff_reason = ""
|
||||
context_data = output_str
|
||||
fast_routed = True
|
||||
break
|
||||
trigger_func = getattr(t, "trigger_func", None)
|
||||
if trigger_func:
|
||||
try:
|
||||
if trigger_func(output_str):
|
||||
current_target = getattr(t, "target", current_target)
|
||||
handoff_reason = ""
|
||||
context_data = output_str
|
||||
fast_routed = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
fast_routed, new_target = self._check_fast_route(current_target, output_str)
|
||||
if fast_routed:
|
||||
current_target = new_target
|
||||
handoff_reason = ""
|
||||
context_data = output_str
|
||||
logger.debug(
|
||||
f"🛣️ **路由决策**: 委派给专员 👨💼`{current_target}`"
|
||||
"(系统拦截:正则/函数状态流发生转移)"
|
||||
@@ -317,16 +351,13 @@ class CoordinateStrategy(BaseTeamStrategy):
|
||||
async def generate_plan(
|
||||
self,
|
||||
team: "Team",
|
||||
prompt: str | AgentTask | None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[TeamAction, Any]:
|
||||
delegation_tools = []
|
||||
for m in team.members:
|
||||
persona = getattr(m, "persona", None)
|
||||
desc = getattr(m, "description", "") or "处理节点"
|
||||
if persona and not isinstance(persona, dict):
|
||||
desc = f"角色:{persona.role},目标:{persona.goal}"
|
||||
desc = m.profile_summary
|
||||
|
||||
delegation_tools.append(
|
||||
DelegateTool(
|
||||
@@ -337,14 +368,10 @@ class CoordinateStrategy(BaseTeamStrategy):
|
||||
)
|
||||
)
|
||||
|
||||
leader_tools = self.leader_tools.copy()
|
||||
leader_tools.extend(delegation_tools)
|
||||
|
||||
leader_agent = self._build_leader_agent(
|
||||
team=team,
|
||||
name=f"{team.name}_Leader",
|
||||
instruction=self.get_prompt(),
|
||||
tools=leader_tools,
|
||||
role_name="Leader",
|
||||
extra_tools=delegation_tools,
|
||||
)
|
||||
|
||||
logger.debug(f"✨ **团队 [{team.name}] Leader** 正在汇总各方报告...")
|
||||
@@ -358,7 +385,9 @@ class CoordinateStrategy(BaseTeamStrategy):
|
||||
|
||||
logger.debug(f"👨💼 [CoordinateStrategy] '{team.name}' 正在启动协调推理循环...")
|
||||
|
||||
leader_res = yield CallAction(agent=leader_agent, task=prompt or "")
|
||||
leader_res = yield CallAction(
|
||||
agent=leader_agent, task=intent.original_input or ""
|
||||
)
|
||||
|
||||
yield FinishAction(result=leader_res)
|
||||
|
||||
@@ -391,13 +420,11 @@ class BroadcastStrategy(BaseTeamStrategy):
|
||||
async def generate_plan(
|
||||
self,
|
||||
team: "Team",
|
||||
prompt: str | AgentTask | None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[TeamAction, Any]:
|
||||
task_desc_str = (
|
||||
prompt.description if isinstance(prompt, AgentTask) else (prompt or "")
|
||||
)
|
||||
task_desc_str = intent.text
|
||||
|
||||
await context.run.emit(
|
||||
ToolStreamChunkEvent(
|
||||
@@ -406,7 +433,10 @@ class BroadcastStrategy(BaseTeamStrategy):
|
||||
)
|
||||
)
|
||||
|
||||
actions = [CallAction(agent=m.name, task=task_desc_str) for m in team.members]
|
||||
actions = [
|
||||
CallAction(agent=m.name, task=intent.original_input or "")
|
||||
for m in team.members
|
||||
]
|
||||
results = yield ConcurrentCallAction(actions=actions)
|
||||
|
||||
await context.run.emit(
|
||||
@@ -430,9 +460,7 @@ class BroadcastStrategy(BaseTeamStrategy):
|
||||
|
||||
leader_agent = self._build_leader_agent(
|
||||
team=team,
|
||||
name=f"{team.name}_Leader",
|
||||
instruction=self.get_prompt(),
|
||||
tools=self.leader_tools,
|
||||
role_name="Leader",
|
||||
)
|
||||
|
||||
leader_res = yield CallAction(agent=leader_agent, task=synthesize_prompt)
|
||||
@@ -497,33 +525,36 @@ class TaskStrategy(BaseTeamStrategy):
|
||||
schema=schema, initial_state=initial_state
|
||||
)
|
||||
self.bb_toolkit = BlackboardToolkit(self.blackboard)
|
||||
self.leader_tools.append(self.bb_toolkit)
|
||||
|
||||
def get_member_capabilities(
|
||||
self, team: "Team", member: BaseRunnable
|
||||
) -> list[AbstractCapability]:
|
||||
caps = super().get_member_capabilities(team, member)
|
||||
if self.bb_toolkit:
|
||||
|
||||
class BlackboardInjectCapability(AbstractCapability):
|
||||
def __init__(self, tk):
|
||||
self.toolkit = tk
|
||||
|
||||
async def get_tools(self, context: RunContext) -> list[Any]:
|
||||
return [self.toolkit]
|
||||
|
||||
caps.append(BlackboardInjectCapability(self.bb_toolkit))
|
||||
return caps
|
||||
|
||||
async def generate_plan(
|
||||
self,
|
||||
team: "Team",
|
||||
prompt: str | AgentTask | None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[TeamAction, Any]:
|
||||
if self.blackboard is not None:
|
||||
context.session.blackboard = self.blackboard
|
||||
|
||||
if self.bb_toolkit:
|
||||
for m in team.members:
|
||||
if not hasattr(m, "tool_definitions"):
|
||||
setattr(m, "tool_definitions", [])
|
||||
|
||||
m_tools = getattr(m, "tool_definitions")
|
||||
if self.bb_toolkit not in m_tools:
|
||||
m_tools.append(self.bb_toolkit)
|
||||
|
||||
member_infos = []
|
||||
for m in team.members:
|
||||
desc = getattr(m, "description", "") or "处理节点"
|
||||
persona = getattr(m, "persona", None)
|
||||
if persona and not isinstance(persona, dict):
|
||||
desc = f"角色:{persona.role},目标:{persona.goal}"
|
||||
desc = m.profile_summary
|
||||
member_infos.append(
|
||||
f'<member id="{m.name}" name="{m.name}">\n'
|
||||
f" Description: {desc}\n"
|
||||
@@ -532,18 +563,11 @@ class TaskStrategy(BaseTeamStrategy):
|
||||
|
||||
members_xml = "<team_members>\n" + "\n".join(member_infos) + "\n</team_members>"
|
||||
|
||||
final_instruction = self.get_prompt() + "\n\n" + members_xml
|
||||
|
||||
task_toolkit = TaskPlanningToolkit(members=team.members)
|
||||
|
||||
leader_tools = self.leader_tools.copy()
|
||||
leader_tools.append(task_toolkit)
|
||||
|
||||
leader_agent = self._build_leader_agent(
|
||||
team=team,
|
||||
name=f"{team.name}_Planner",
|
||||
instruction=final_instruction,
|
||||
tools=leader_tools,
|
||||
role_name="Planner",
|
||||
extra_instruction=members_xml,
|
||||
extra_tools=[TaskPlanningToolkit(members=team.members)],
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
@@ -555,7 +579,7 @@ class TaskStrategy(BaseTeamStrategy):
|
||||
board = cast(TaskBoardState, context.session.shared_state["__task_board__"])
|
||||
|
||||
max_iterations = self.max_iterations
|
||||
planner_prompt = prompt
|
||||
planner_prompt = intent.original_input
|
||||
|
||||
for iteration in range(max_iterations):
|
||||
if board.is_goal_complete:
|
||||
@@ -567,12 +591,7 @@ class TaskStrategy(BaseTeamStrategy):
|
||||
if not available_tasks:
|
||||
if iteration > 0:
|
||||
board_str = board.render_board_to_string()
|
||||
goal_str = getattr(prompt, "description", None) or (
|
||||
str(prompt) if prompt else ""
|
||||
)
|
||||
expected_out = getattr(prompt, "expected_output", None)
|
||||
if expected_out:
|
||||
goal_str += f"\n\n### 🎯 [预期产出要求]\n{expected_out}"
|
||||
goal_str = intent.text
|
||||
|
||||
planner_prompt = f"""### 🎯 用户的终极目标 (Original Goal)
|
||||
{goal_str}
|
||||
@@ -637,8 +656,6 @@ class TaskStrategy(BaseTeamStrategy):
|
||||
)
|
||||
|
||||
if task.metadata:
|
||||
import json
|
||||
|
||||
meta_str = json.dumps(task.metadata, ensure_ascii=False)
|
||||
task_prompt += f"\n\n### ⚙️ 附加系统元数据约束:\n{meta_str}"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Annotated, Any
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from zhenxun.services.ai.flow.base import BaseRunnable
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.tools.core.decorators import tool
|
||||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import asyncio
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
import contextlib
|
||||
from collections.abc import AsyncIterator, Callable, Mapping, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing_extensions import Self
|
||||
@@ -12,24 +10,20 @@ from zhenxun.services.ai.capabilities import (
|
||||
CapabilitySource,
|
||||
DynamicCapability,
|
||||
)
|
||||
from zhenxun.services.ai.core.exceptions import ConcurrencyInterruptException
|
||||
from zhenxun.services.ai.core.messages import PromptInput
|
||||
from zhenxun.services.ai.core.models import CancellationToken
|
||||
from zhenxun.services.ai.core.stream_events import EventBus
|
||||
from zhenxun.services.ai.core.stream_events import AgentStreamEvent, EventBus
|
||||
from zhenxun.services.ai.flow.agent.agent import ToolSource
|
||||
from zhenxun.services.ai.flow.agent.models import Persona
|
||||
from zhenxun.services.ai.flow.base import BaseRunnable, ConcurrencyPolicy
|
||||
from zhenxun.services.ai.flow.concurrency import apply_concurrency_policy
|
||||
from zhenxun.services.ai.flow.core.base import BaseRunnable
|
||||
from zhenxun.services.ai.run import (
|
||||
AgentRunResult,
|
||||
AgentTask,
|
||||
RunContext,
|
||||
StreamedRunResult,
|
||||
RunIntent,
|
||||
)
|
||||
from zhenxun.services.ai.run.models import AgentRunError
|
||||
from zhenxun.services.ai.tools.providers.skills.capabilities import SkillCapability
|
||||
from zhenxun.services.ai.tools.providers.skills.models import Skill, SkillSource
|
||||
from zhenxun.services.ai.utils import ContextUtils
|
||||
from zhenxun.utils.utils import infer_plugin_namespace
|
||||
|
||||
from .models import TeamRuntimeConfig, Transition
|
||||
@@ -52,7 +46,7 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
model: str | Callable[[], str] | None = None,
|
||||
strategy: BaseTeamStrategy | None = None,
|
||||
description: str | None = None,
|
||||
persona: Persona | dict | None = None,
|
||||
persona: Persona | None = None,
|
||||
runtime_config: TeamRuntimeConfig | dict | None = None,
|
||||
capabilities: list[CapabilitySource] | None = None,
|
||||
skills: Sequence[str | Path | Skill | SkillSource] | None = None,
|
||||
@@ -74,15 +68,17 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
self.members = members
|
||||
self.model = model
|
||||
self.strategy = strategy
|
||||
self.description = (
|
||||
description
|
||||
or f"一个名为 {self.name} 的协作团队,包含 {len(self.members)} 个处理节点。"
|
||||
)
|
||||
self.persona = persona
|
||||
|
||||
self.namespace = infer_plugin_namespace() or "unknown"
|
||||
|
||||
self.capabilities: list[Any] = []
|
||||
if description:
|
||||
self.description = description
|
||||
else:
|
||||
self.description = f"一个名为 {self.name} 的协作团队,"
|
||||
f"包含 {len(self.members)} 个处理节点。"
|
||||
|
||||
self.capabilities: list[AbstractCapability] = []
|
||||
if capabilities:
|
||||
for cap in capabilities:
|
||||
if isinstance(cap, AbstractCapability):
|
||||
@@ -107,6 +103,18 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
getattr(strategy, "selector_func", None) if strategy else None
|
||||
)
|
||||
|
||||
@property
|
||||
def default_model(self) -> str | None:
|
||||
"""获取当前团队默认调用的可用大模型。优先取自身配置,其次遍历成员寻找可用模型。"""
|
||||
if getattr(self, "model", None):
|
||||
return str(self.model() if callable(self.model) else self.model)
|
||||
|
||||
for m in self.members:
|
||||
m_model = getattr(m, "model_name", None) or getattr(m, "model", None)
|
||||
if m_model:
|
||||
return str(m_model() if callable(m_model) else m_model)
|
||||
return None
|
||||
|
||||
def with_strategy(self, strategy: BaseTeamStrategy) -> Self:
|
||||
"""
|
||||
挂载自定义的团队协作策略。
|
||||
@@ -122,9 +130,7 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
|
||||
def with_routing(
|
||||
self,
|
||||
state_flow: (
|
||||
Mapping[str, Sequence[Transition | str | Any]] | Callable | None
|
||||
) = None,
|
||||
state_flow: (Mapping[str, Sequence[Transition | str]] | Callable | None) = None,
|
||||
selector_func: Callable[..., str | None] | None = None,
|
||||
router: BaseRouter | None = None,
|
||||
leader_model: str | None = None,
|
||||
@@ -287,20 +293,18 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
prompt=prompt, context=context, capabilities=capabilities, **kwargs
|
||||
)
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def run_stream(
|
||||
async def _execute_stream(
|
||||
self,
|
||||
prompt: PromptInput | AgentTask | None = None,
|
||||
*,
|
||||
context: "RunContext | None" = None,
|
||||
capabilities: list[CapabilitySource] | None = None,
|
||||
skills: Sequence[str | Path | Skill | SkillSource] | None = None,
|
||||
intent: RunIntent,
|
||||
context: RunContext,
|
||||
cancel_token: CancellationToken,
|
||||
event_bus: EventBus,
|
||||
**kwargs: Any,
|
||||
):
|
||||
) -> AsyncIterator[AgentStreamEvent]:
|
||||
self._ensure_strategy()
|
||||
|
||||
if context is None:
|
||||
context = RunContext()
|
||||
capabilities = kwargs.pop("capabilities", None)
|
||||
skills = kwargs.pop("skills", None)
|
||||
|
||||
if not hasattr(context, "capabilities"):
|
||||
context.capabilities = []
|
||||
@@ -310,6 +314,10 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
|
||||
if skills:
|
||||
capabilities = list(capabilities) if capabilities else []
|
||||
from zhenxun.services.ai.tools.providers.skills.capabilities import (
|
||||
SkillCapability,
|
||||
)
|
||||
|
||||
capabilities.append(
|
||||
SkillCapability(skills=skills, namespace=self.namespace)
|
||||
)
|
||||
@@ -323,54 +331,8 @@ class Team(BaseRunnable[AgentRunResult[Any]]):
|
||||
|
||||
from .runner import TeamRunner
|
||||
|
||||
event_bus = EventBus()
|
||||
context.run.event_bus = event_bus
|
||||
assert self.strategy is not None
|
||||
runner = TeamRunner(self, self.strategy)
|
||||
|
||||
policy = getattr(self.runtime_config, "concurrency_policy", None)
|
||||
if policy is None:
|
||||
policy = (
|
||||
ConcurrencyPolicy.ALLOW
|
||||
if getattr(self.runtime_config, "stateless", True)
|
||||
else ConcurrencyPolicy.QUEUE
|
||||
)
|
||||
|
||||
intervention_policy = getattr(self.runtime_config, "intervention_policy", None)
|
||||
|
||||
lock_id = ContextUtils.extract_concurrency_lock_id(
|
||||
context,
|
||||
getattr(self.runtime_config, "concurrency_scope", None),
|
||||
context.session_id or "default_session",
|
||||
)
|
||||
|
||||
async def _execution_task():
|
||||
cancel_token = context.run.cancellation_token or CancellationToken()
|
||||
context.run.cancellation_token = cancel_token
|
||||
|
||||
try:
|
||||
async with apply_concurrency_policy(
|
||||
session_id=context.session_id or "default_session",
|
||||
lock_id=lock_id,
|
||||
policy=policy,
|
||||
cancel_token=cancel_token,
|
||||
intervention_policy=intervention_policy,
|
||||
message=prompt,
|
||||
):
|
||||
async for event in runner.run_stream(prompt, context, **kwargs):
|
||||
await event_bus.emit(event)
|
||||
except BaseException as e:
|
||||
if isinstance(e, asyncio.CancelledError):
|
||||
e = ConcurrencyInterruptException("团队执行已被新请求打断并接管")
|
||||
await event_bus.emit(AgentRunError(error=e))
|
||||
finally:
|
||||
await event_bus.end()
|
||||
|
||||
task = asyncio.create_task(_execution_task())
|
||||
result_obj = StreamedRunResult[Any](event_bus)
|
||||
|
||||
try:
|
||||
yield result_obj
|
||||
finally:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
async for event in runner.run_stream(intent, context, **kwargs):
|
||||
yield event
|
||||
|
||||
Reference in New Issue
Block a user