Skip to content

03 · Agent 管线 ​

架构剖析第三篇:Agent 生命周期、ConversationSession 工具循环、DialogueManager RAG 组合、Transferer 跨 Agent 派发、工具系统与客户端抽象。 对应 ARCHITECTURE.md §4。


§4.1 为什么不用现成 Agent 框架 ​

框架不选原因
LangChain抽象层太厚,无法精细控制单 agent 生命周期、工具审批时机、记忆写入点
AutoGen多 agent 协作模式与 Familiars 的三层架构(Manager → Assistant → Dialogue)不匹配
CrewAI本地小模型(decider/rewriter/memorizer-writer)需要深度耦合主循环

自研管线把「规划 → 工具调用 → 记忆 → 报告」作一等公民,每个阶段都有明确的拦截点。

§4.2 Agent 类(src/models/assistant.py) ​

4.2.1 Assistant 构造 ​

python
class Assistant:
    def __init__(self, id: int, mode='normal', description: str = '',
                 name: str = '', notification_type: str = 'SYSTEM',
                 path_override: Optional[str] = None):
        self.id = id
        self.workspace_route = _resolve_workspace_root()
        
        # 路径解析
        if path_override:
            # Program worker: 绝对路径,如 <repo>/runs/programs/<pid>/agents/<role>
            self.path = path_override
            _agent_dir = os.path.abspath(path_override)
            _agents_parent = os.path.dirname(_agent_dir)
            _memorizer_root = os.path.dirname(_agents_parent)
            _agent_key = os.path.basename(_agent_dir)
            self.memorizer = Memorizer(_memorizer_root, agent_id=_agent_key)
        else:
            self.path = f'{self.workspace_route}/workspace/agents/agent_{self.id}'
            self.memorizer = Memorizer(
                os.path.join(self.workspace_route, "workspace"),
                agent_id=f"agent_{self.id}"
            )
        
        # 核心组件
        self.vision_processor = VisionProcessor()  # MultimodalTools
        self.dialogue_manager = DialogueManager(
            self.memorizer,
            agent_path=self.path,
            top_k=int(os.getenv("TOP_K") or 5),
            workspace_route=self.workspace_route
        )
        
        # 通知类型自动检测
        if self.notification_type == 'SYSTEM':
            if self.info['os'] == 'Darwin':
                self.notification_type = 'MacOS'
            elif self.info['os'] == 'Windows':
                self.notification_type = 'Windows'
            else:
                self.notification_type = 'DESKTOP'

4.2.2 AssistantManager ​

python
class AssistantManager:
    """N 个 Assistant 的编排器。
    
    职责:
    - Agent 生命周期(创建/删除/列表)
    - APScheduler cron 驱动报告聚合
    - MANAGER_MODEL 独立模型用于执行摘要 + 人设演化
    """

4.2.3 信号评估(Decider 集成) ​

python
def evaluate_signal(
    self,
    *,
    trigger_type: str,           # "new_message" / "external_info" / ...
    trigger_summary: str,         # 触发摘要
    trigger_details: Optional[dict] = None,
    memory: Optional[list[str]] = None,
    user_state: Optional[dict] = None,
    recent_user_messages: Optional[list[str]] = None,
):
    """Ask the decider whether this agent should act on a signal.
    
    Returns a DecisionOutcome (fire: bool, action_category: str, act_score: float).
    Always returns — never raises. Worst case: fire=False with source indicating why.
    """
    from src.decider import DeciderService
    svc = DeciderService.shared()
    return svc.evaluate(
        agent_id=self.id,
        trigger_type=trigger_type,
        trigger_summary=trigger_summary,
        ...
    )

4.2.4 主动消息 ​

python
def send_proactive_message(
    self,
    *,
    content: str,
    signal_summary: str,
    action_category: str,
    act_score: Optional[float] = None,
    decision_id: Optional[str] = None,
    dialogue_id: int = 0,
    title: Optional[str] = None,
    fired_at: Optional[float] = None,
    extra: Optional[dict] = None,
) -> Optional[int]:
    """Attach an agent-initiated A-node to dialogue_id and persist.
    
    origin='proactive' + metadata → frontend rating UI can POST feedback.
    Returns the new node id, or None if no dialogue exists.
    """

4.2.5 通知通道 ​

通道实现说明
Windowswin10toast.ToastNotifier原生 toast;失败回退 desktop_notify
macOSosascript -e 'display notification ...'Notification Center
Desktopsrc.services.desktop_notify.notify_desktopElectron renderer → OS toast
Telegramrequests.post(api.telegram.org/bot{token}/sendMessage)远程推送

§4.3 ConversationSession(src/models/conversation.py) ​

4.3.1 构造参数 ​

python
class ConversationSession:
    def __init__(
        self,
        model: Optional[str] = None,        # 默认 select_model(fallback_env="DEFAULT_MODEL")
        timeout: Optional[int] = None,       # 默认 TIMEOUT=300s
        tools: Optional[list[dict]] = None,  # OpenAI function schemas
        system_prompt: str = "",
        base_url: Optional[str] = None,
        api_key: Optional[str] = None,
        dispatcher: Optional[ToolDispatcher] = None,
    ):

4.3.2 CircuitBreaker(熔断器) ​

python
class CircuitBreaker:
    """Per-URL 熔断器,三态:CLOSED → OPEN → HALF_OPEN。
    
    CLOSED: 请求正常流动,计数失败。
    OPEN: 所有请求立即拒绝 recovery_seconds。
    HALF_OPEN: 允许一个探测请求;成功→CLOSED,失败→OPEN。
    """
    
    def __init__(
        self,
        failure_threshold: int = 5,         # CB_FAILURE_THRESHOLD
        recovery_seconds: float = 30.0,     # CB_RECOVERY_SECONDS
        half_open_max: int = 1,
    ):

全局共享:_breakers: dict[str, CircuitBreaker] 按 base URL 索引,所有 ConversationSession 实例共享同一个熔断器。

4.3.3 重试逻辑 ​

python
_RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504}

# 永久性内容策略拒绝(不可重试)
_PERMANENT_CONTENT_ERROR_CODES = frozenset({
    "local:sensitive_words",           # api3.wlai.vip 中文内容过滤
    "content_filter",                  # azure openai
    "content_policy_violation",        # openai
    "safety",                          # gemini / vertex
    "prohibited_content",              # gemini
})

4.3.4 流式决策 ​

python
_REASONING_MODEL_PREFIXES = ("gpt-5", "gpt-o", "o1-", "o3-", "o4-")
_REASONING_MODEL_EXACT = frozenset({"o1", "o3", "o4"})

def _should_stream(model: str) -> bool:
    """推理模型(gpt-5.x, gpt-o1, gpt-o3)返回 False。
    
    原因:推理模型的 SSE delta 协议与现有 assembler 不兼容 —— 
    HTTP 200 但 prompt_tokens=0, completion_tokens=0,延迟 100-200s。
    
    环境变量覆盖:
    FAMILIARS_STREAM_LLM=true  → 强制开启(调试)
    FAMILIARS_STREAM_LLM=false → 强制关闭
    """

4.3.5 工具循环(contact() 方法) ​

python
async def contact(self, history: list[dict], **kwargs) -> dict:
    """Run the tool-call loop on top of chat().
    
    每轮 LLM 可发 ≥1 工具调用 → dispatch → tool_result → 继续循环。
    直到 max_turns 或 LLM 停止发工具调用。
    
    Turn budget 升级:预算耗尽时询问 manager LLM 是否延长。
    """

4.3.6 文档自动刷新 ​

python
def _build_auto_refresh_messages(path_norm: str, write_tool: str,
                                  progress_footer: str = "") -> list[dict]:
    """在 write 操作后合成 (assistant tool_call, tool result) 对。
    
    下次 mutation 同一文件时,_mark_stale_docx_reads 会折叠旧内容。
    返回两条消息准备追加到 history。
    """

§4.4 DialogueManager ​

python
class DialogueManager:
    """管理 N 个 Dialogue(对话树 + 历史)。
    
    职责:
    - _load_existing_dialogues() 从 disk 读取
    - store_dialogue(dialogue_id) 持久化
    - build_rag_context(query) 组装 RAG 上下文
    - release_memory() 释放常驻内存(JIT 按需装载)
    """
    
    def __init__(self, memorizer, agent_path, top_k=5, workspace_route=""):
        self.memorizer = memorizer
        self.agent_path = agent_path
        self.top_k = top_k
        self.dialogue: list[Dialogue] = []

§4.5 Transferer(跨 Agent 派发) ​

src/models/transferer.py 是所有跨 agent 调用的唯一咽喉点。

python
def dispose(request_model: str, request: str, **kwargs) -> Any:
    """将 destination 字符串解析为可调用对象。
    
    优先级:
    1. 工具客户端别名 — "BrowseTool_Client", "browser_tools" → 实例化 ToolClient
    2. 工具方法路径 — "BaseTools.open_app" → 解析到 src.utils.* 的方法
    3. 模型名 — 直接发 chat completion(无工具 schema)
    4. 空 / "Writer" / 未知 → 回退到 DEFAULT_MODEL
    """

客户端注册表 ​

python
_CLIENT_REGISTRY: dict[str, str] = {
    "basetoolclient":     "src.models.clients.base_client:BaseToolClient",
    "browsertoolclient":  "src.models.clients.browser_client:BrowserToolClient",
    "webdive":            "src.models.clients.browser_client:WebDiveToolClient",
    "multimodaltoolclient": "src.models.clients.multimodal_client:MultimodalToolClient",
    "inputtoolclient":    "src.models.clients.input_client:InputToolClient",
    "doctoolclient":      "src.models.clients.doc_client:DocToolClient",
    "dailytoolclient":    "src.models.clients.daily_client:DailyToolClient",
    "systemtoolclient":   "src.models.clients.system_client:SystemToolClient",
    # ...
}

§4.6 工具系统架构 ​

4.6.1 ToolDispatcher ​

python
class ToolDispatcher:
    """工具名 → 可调用的注册表。
    
    15 个默认工具实例:
    """
    
    _DEFAULT_INSTANCES = [
        ("BaseTools",           BaseTools),
        ("BaseTools.InputController", BaseTools.InputController),
        ("BrowserTools",        BrowserTools),
        ("MultimodalTools",     MultimodalTools),
        ("DailyTools",          DailyTools),
        ("DailyMonitorTools",   DailyMonitorTools),
        ("DocTools",            DocTools),
        ("MemoryTools",         MemoryTools),
        ("SystemTools",         SystemTools),
        ("UserTools",           UserTools),
        ("UserSecretTools",     UserSecretTools),
        ("PhoneTools",          PhoneTools),
        ("SkillTools",          SkillTools),
        ("SkillWatcherTools",   SkillWatcherTools),
        ("BrokerTools",         BrokerTools),
    ]

4.6.2 execute_skill 伪工具 ​

python
def execute_skill_schema() -> dict:
    """OpenAI function schema for execute_skill pseudo-tool.
    
    框架级 affordance,注册在每个 ToolDispatcher 上。
    Recipe/flow unit 通过 tools.whitelist 暴露此 schema。
    """
    return {
        "type": "function",
        "function": {
            "name": "execute_skill",
            "parameters": {
                "type": "object",
                "properties": {
                    "skill_id": {"type": "string"},
                    "args": {"type": "object"},
                },
                "required": ["skill_id"],
            },
        },
    }

4.6.3 工具活动映射 ​

python
_INSTANCE_ACTIVITY_KEY: dict[str, str] = {
    "BaseTools":         "base",
    "BrowserTools":      "browser",
    "MultimodalTools":   "multimodal",
    "DailyTools":        "daily",
    "DailyMonitorTools": "daily",
    "DocTools":          "doc",
    "SystemTools":       "system",
    "InputController":   "input",
}

驱动前端 robot-mascot 状态切换。

4.6.4 客户端工具白名单 ​

每个 ToolClient 子类只暴露它能用的工具:

客户端工具子集
BaseToolClient基础工具(read/write/execute/find/grep/download)
BrowserToolClient浏览器工具 + explore_page
MultimodalToolClient视觉 + 操作工具
DocToolClient文档工具(Word/Excel/PPT/PDF)
DailyToolClient日程/待办/提醒
SystemToolClient系统工具

Schema 来自 workspace/tools.json + filtered_schemas() 按客户端过滤。

§4.7 Turn Budget 升级器 ​

python
# max_turns 不再是硬顶
# 预算耗尽时询问 manager LLM:
#   - 延长(+N turns)
#   - 中止

# 实现:_turn_budget_escalator 调用 MANAGER_MODEL 判断

下一篇:04-RAG 与记忆 →

Familiars · 多 Agent AI 桌面应用