# `from_config()` 编译器需求规格文档 > YAML Agent Configuration → Lambda Term Compiler > > 版本: 2.0 | 状态: 需求定稿 | 上游理论: LDS ≡ Lambda Calculus --- ## 1. 概述 ### 1.1 一句话定义 `from_config(path)` 是一个**编译器**,输入 YAML 格式的 Agent 配置文件,输出一个可执行的 `lambdagent.Term`(Lambda 项)。 ### 1.2 核心等式 ``` from_config("agent-config.yml") → Term → term(input) → result ↓ ↓ ↓ YAML 解析 Lambda 项 β-规约 ``` 等价于手写: ```python Memory( Loop( think >> Route(tools) >> observe, max_steps=20 ), store=redis(20, 7200) ) ``` ### 1.3 设计原则 | 原则 | 说明 | |---|---| | **每个 YAML 字段有且仅有一个 Lambda 语义** | 不存在"无法翻译"的配置项 | | **编译结果可追踪** | 每一步执行 = 一次 β-规约,记录在 Context.trace | | **编译结果可静态分析** | lint 工具能从 Term 结构推断配置问题 | | **编译是确定性的** | 同一 YAML 总产出同一 Term 结构 | | **编译器不执行 Agent** | `from_config` 只构建 Term,不调用 LLM | --- ## 2. 输入规格: YAML Schema ### 2.1 完整 YAML Schema ```yaml # ═══ 必填字段 ═══ agentId: string # Agent 唯一标识符 name: string # Agent 可读名称 type: enum # "react" | "chain" | "simple" | "parallel" | "router" systemPrompt: string # Lambda body(λ 抽象的函数体) # ═══ 模型配置 ═══ model: provider: string # "anthropic" | "openai" | "dashscope" | "ollama" # | "claude-code" | "deepseek" | "moonshot" | "zhipu" | "custom" name: string # 具体模型名 (e.g., "claude-sonnet-4-20250514") temperature: float # [0.0, 2.0],默认 0.3 maxTokens: int # 最大输出 token 数,默认 4096 topP: float # [0.0, 1.0],可选 stopSequences: [string] # 停止序列,可选 baseUrl: string # 自定义 API 端点,可选 conversation: bool # 是否启用 ConversationLam 对话管理,默认 true maxHistoryTokens: int # 对话历史最大 token 数,默认 min(contextWindow/2, 80000) contextWindow: int # 模型上下文窗口大小,默认 200000 timeout: int # 单次 LLM 调用超时(秒),默认 600 # ═══ ReAct 配置(type=react 时必填)═══ react: maxSteps: int # Y 组合子最大展开次数,默认 10 observationEnabled: bool # 是否将工具结果追加到上下文,默认 true toolTimeout: int # 单个工具调用超时(秒),默认 30 verbose: bool # 是否打印中间步骤,默认 false earlyStop: string # 提前终止条件表达式,可选 thinkPrompt: string # 覆盖 think 步骤的 prompt,可选 # ═══ Chain 配置(type=chain 时必填)═══ chain: steps: # 有序步骤列表 - name: string # 步骤名称 prompt: string # 该步骤的 prompt model: object # 可选覆盖模型配置 outputParser: string # "json" | "text" | "number" | "boolean" guard: # 可选输出验证 validator: string # Python 表达式 (e.g., "len(x) > 100") retry: int # 验证失败重试次数 fallback: string # 兜底值 # ═══ Router 配置(type=router 时必填)═══ router: classifier: # 分类器配置 prompt: string # 分类 prompt model: object # 可选模型覆盖 categories: [string] # 类别列表 routes: # 类别 → Agent 映射 : # 每个 category 指向一个子 agent 配置 type: string systemPrompt: string # ... 子 agent 的完整配置 default: object # 无法分类时的默认 Agent,可选 # ═══ Parallel 配置(type=parallel 时必填)═══ parallel: agents: # 并行 Agent 列表 - name: string type: string systemPrompt: string # ... 子 agent 完整配置 merge: string # 结果合并策略: "tuple" | "concat" | "custom" mergePrompt: string # merge=custom 时的合并 prompt # ═══ MCP 工具配置 ═══ mcp: onlineTool: # 在线 MCP 工具 : # MCP 服务器名 - string # 工具名列表 localTools: [string] # 本地工具("terminate" 是特殊的 base case) policy: mode: string # "auto" | "force" | "intelligence" | "disable" maxConcurrent: int # 最大并行工具调用数,默认 1 retryOnFail: int # 工具调用失败重试次数,默认 0 # ═══ Memory 配置 ═══ memory: enabled: bool # 是否启用 strategy: string # "local" | "redis" | "sqlite" | "custom" size: int # 记忆容量(最多保留多少条) ttl: int # 过期时间(秒),0 = 永不过期 scope: string # "session" | "global" | "user" customStore: string # strategy=custom 时的 Python 类路径 # ═══ Runtime 配置(可选)═══ runtime: engine: string # "recursive" | "cek" | "adaptive",默认 "recursive" # 执行引擎选择。详见 docs/yaml-config.md §4 # ═══ Guard 配置(可选,包裹整个 Agent)═══ guard: validator: string # Python 表达式 retry: int # 重试次数 fallback: string # 兜底策略: "error" | "empty" | "last" # ═══ RAG 配置(可选)═══ rag: enabled: bool source: string # 知识库路径或 URL topK: int # 检索数量,默认 3 chunkSize: int # 分块大小 backend: string # "simple" | "chroma",默认 "simple" minScore: float # 最低相似度阈值,默认 0.0 format: string # "numbered" | "plain" | "json",默认 "numbered" agentic: bool # true = AgenticRAG(Agent 自行决定是否检索) persistDirectory: string # backend=chroma 时的持久化目录 # ═══ 外部服务配置 ═══ app: mcp: custom: nodes: : url: string # MCP 服务端点 endpoint: string # 路径 headers: Authorization: string timeout: int # 超时(秒) ``` ### 2.2 YAML 字段分级 | 级别 | 字段 | Lambda 语义 | 编译行为 | |---|---|---|---| | **必须** | `type`, `systemPrompt` | Agent 结构 + λ body | 缺失则报编译错误 | | **核心** | `model`, `react`, `mcp` | 计算单元 + Y 参数 + Oracle | 缺失则用默认值 | | **增强** | `memory`, `guard`, `chain`, `router`, `parallel` | 环境扩展 + 类型约束 + 组合 | 缺失则不包裹 | | **元数据** | `agentId`, `name`, `description` | 不影响 Lambda 语义 | 只用于标识和追踪 | | **运行时** | `runtime`, `rag`, `app` | 引擎选择 + 外部资源 | `runtime.engine`: 执行引擎选择 (recursive \| cek \| adaptive), 默认 recursive. 详见 docs/yaml-config.md §4 | --- ## 3. 输出规格: Term 结构 ### 3.1 编译输出的类型签名 ```python def from_config(path: str, **overrides) -> Term: """ 编译 YAML 配置为 Lambda 项。 参数: path: YAML 文件路径 **overrides: 运行时覆盖项 - model: 覆盖模型名称 - temperature: 覆盖温度 - max_steps: 覆盖最大步数 - tools: Dict[str, Callable] 注入自定义工具实现 - memory_store: Dict 注入初始记忆 返回: Term — 可执行的 Lambda 项 异常: ConfigError — YAML 解析失败 SchemaError — 必填字段缺失 CompileError — 语义不合法 (e.g., router 无 routes) """ ``` ### 3.2 五种 type 的编译目标 #### `type: simple` — 单个 λ 抽象 ``` 输入 YAML: type: simple systemPrompt: "Summarize text." model: {name: claude-sonnet} 编译目标: Lam("agent", prompt="Summarize text.", model="claude-sonnet") Lambda: λx. LLM_{θ,p}(x) ``` #### `type: react` — Y 组合子 ``` 输入 YAML: type: react react: {maxSteps: 20} mcp: onlineTool: {server: [search, calc]} localTools: [terminate] 编译目标: Memory( Loop( Tool("react_step", fn=react_step_fn), condition=lambda r, s: s >= 19, max_steps=20 ), store={...} ) 其中 react_step_fn 内部结构: think(state) ← Lam: β-规约 → Route(thought, { ← 广义 Church 布尔 "search": Tool("search", mcp), "calc": Tool("calc", mcp), "terminate": Tool("terminate", λx.x) ← base case }) → observation ← 工具结果 → state ⊕ observation ← 状态拼接 Lambda: Memory( Y₂₀(λself.λstate. let t = think(state) in CASE t [ (search, Tool_MCP(t)), (calc, Tool_MCP(t)), (terminate, λx.x) ← base case ] >> IF is_terminate THEN t ELSE self(state ⊕ obs) ), Γ ∪ store ) ``` #### `type: chain` — 函数组合链 ``` 输入 YAML: type: chain chain: steps: - name: extract prompt: "Extract key facts." - name: analyze prompt: "Analyze the facts." guard: {validator: "len(x) > 50", retry: 2} - name: draft prompt: "Write a draft." 编译目标: Lam("extract", "Extract key facts.") >> Guard( Lam("analyze", "Analyze the facts."), validator=lambda x: len(x) > 50, retry=2 ) >> Lam("draft", "Write a draft.") Lambda: λx. draft(Guard(analyze, P)(extract(x))) where P(r) = len(r) > 50 ``` #### `type: router` — 广义 Church 布尔 (CASE) ``` 输入 YAML: type: router router: classifier: prompt: "Classify: code/math/general" categories: [code, math, general] routes: code: {type: simple, systemPrompt: "You are a coder."} math: {type: simple, systemPrompt: "You are a mathematician."} default: {type: simple, systemPrompt: "You are a helpful assistant."} 编译目标: Route( classifier=Lam("classifier", "Classify: code/math/general"), routes={ "code": Lam("code_agent", "You are a coder."), "math": Lam("math_agent", "You are a mathematician."), }, default=Lam("default", "You are a helpful assistant.") ) Lambda: λx. CASE (classifier x) [ (code, code_agent x), (math, math_agent x), (_, default_agent x) ] ``` #### `type: parallel` — Church 对 (PAIR / PAR) ``` 输入 YAML: type: parallel parallel: agents: - {name: researcher, systemPrompt: "Research the topic."} - {name: critic, systemPrompt: "Critique the topic."} merge: custom mergePrompt: "Synthesize the research and critique." 编译目标: Par( Lam("researcher", "Research the topic."), Lam("critic", "Critique the topic.") ) >> Lam("merge", "Synthesize the research and critique.") Lambda: λx. merge(PAIR (researcher x) (critic x)) ``` --- ## 4. 编译规则(完整翻译表) ### 4.1 一级字段编译规则 | YAML 字段 | 编译目标 | Lambda 语义 | 编译函数 | |---|---|---|---| | `systemPrompt` + `model` | `ConversationLam(name, provider, prompt)` | `λx. provider(history + x)` | `_compile_lam()` → `_create_provider()` | | `type: react` | `Loop(body, cond, maxSteps)` | `Y_n(λself.λs. ...)` | `_compile_react()` | | `type: chain` | `Compose(s1, s2, ..., sn)` | `λx. sn(...s2(s1(x)))` | `_compile_chain()` | | `type: router` | `Route(classifier, routes)` | `CASE` | `_compile_router()` | | `type: parallel` | `Par(a1, a2, ...) >> merge` | `PAIR >> merge` | `_compile_parallel()` | | `mcp.onlineTool` | `Tool(name, mcp_caller)` | `Tool_MCP` (Oracle) | `_compile_tools()` | | `mcp.localTools: [terminate]` | `Tool("terminate", λx.x)` | `λx.x` (identity = base case) | `_compile_tools()` | | `memory: {enabled: true}` | `Memory(agent, store)` | `Γ' = Γ ∪ store` | `_compile_memory()` | | `guard` | `Guard(agent, validator, retry)` | `{x:T \| P(x)}` | `_compile_guard()` | | `rag: {enabled: true}` | `Tool("rag_retrieve", fn)` 注入 | 外部 Oracle | `_compile_rag()` | ### 4.2 嵌套/递归编译 子 Agent 配置(router 的 routes、parallel 的 agents)递归调用 `build_agent()`: ```python def _compile_router(cfg): classifier = _compile_lam(cfg["router"]["classifier"]) routes = {} for category, sub_cfg in cfg["router"]["routes"].items(): routes[category] = build_agent(sub_cfg) # 递归编译 default = build_agent(cfg["router"]["default"]) if "default" in cfg["router"] else None return Route(classifier, routes, default) ``` ### 4.3 包装顺序(从内到外) ``` 1. 核心: Lam / Compose / Loop / Route / Par ← 最内层 2. Guard: 包裹核心 Agent ← 输出验证 3. Memory: 最外层包裹 ← 环境扩展 编译伪代码: agent = compile_core(cfg) # Step 1 if cfg.guard: agent = Guard(agent, ...) # Step 2 if cfg.memory.enabled: agent = Memory(agent, store) # Step 3 return agent ``` --- ## 5. ReAct 编译器详细规格 ReAct 是最复杂的编译目标,单独详述。 ### 5.1 ReAct 的 Lambda 语义 ``` react_agent = Memory( Y_n(λself. λstate. let thought = think(state) in ← β-规约 let tool = route(thought, tools) in ← CASE let obs = tool(thought) in ← β-规约 IF (tool = terminate) THEN thought ← base case ELSE self(state ⊕ format(thought, obs)) ← 递归 ), Γ ∪ store ) ``` ### 5.2 react_step 函数的内部结构 ```python def _compile_react(cfg) -> Term: think = _compile_lam(cfg) tools = _compile_tools(cfg) max_steps = cfg["react"]["maxSteps"] tool_timeout = cfg["react"].get("toolTimeout", 30) observation_enabled = cfg["react"].get("observationEnabled", True) def react_step(state: str) -> str: # Phase 1: Think (β-规约) thought = think(state) # Phase 2: Tool Selection (Route / CASE) selected_tool = _extract_tool_call(thought, tools) # Phase 3: Base Case Check if selected_tool is None or selected_tool._name == "terminate": return thought # λx.x — 递归终止 # Phase 4: Tool Execution (β-规约) try: observation = _timeout_call(selected_tool, thought, tool_timeout) except TimeoutError: observation = f"[TIMEOUT after {tool_timeout}s]" # Phase 5: State Update (递归准备) if observation_enabled: return _format_state(state, thought, selected_tool._name, observation) else: return thought body = Tool(f"{cfg['name']}.react_step", react_step) return Loop( body=body, condition=lambda r, s: s >= max_steps - 1, max_steps=max_steps, ) ``` ### 5.3 工具调用提取 (`_extract_tool_call`) ```python def _extract_tool_call(thought: str, tools: Dict[str, Tool]) -> Tool | None: """ 从 LLM 输出中提取工具调用意图。 支持三种格式: 1. 结构化 JSON: {"tool": "search", "args": "query"} 2. XML 标签: searchquery 3. 关键词匹配: thought 中包含工具名 优先级: JSON > XML > 关键词 返回: 匹配的 Tool 实例,或 None(表示不调用工具 → 隐式 terminate) """ ``` ### 5.4 状态格式化 (`_format_state`) ```python def _format_state(state, thought, tool_name, observation) -> str: """ 构建下一轮 ReAct 的输入状态。 格式: [Previous Context] {state} [Step N] Thought: {thought} Action: {tool_name} Observation: {observation} 这个拼接操作对应 Lambda 演算中的 state ⊕ obs。 """ ``` ### 5.5 Provider 创建与 ConversationLam 编译管道 `_compile_lam()` 不再直接构造 `Lam`。它首先调用 `_create_provider()` 创建一个 `LLMProvider` 实例,然后将其包装在 `ConversationLam` 中。`ConversationLam` 负责对话历史管理,`LLMProvider` 负责底层 LLM 调用。 ```python def _compile_lam(cfg, name_suffix="", overrides=None) -> Term: """ 编译管道 (v2): 1. _create_provider(model_cfg) -> (LLMProvider, use_conversation) 2. if use_conversation: ConversationLam(name, provider, system_prompt, max_history_tokens) else: Lam(name, prompt, model) # legacy fallback Lambda 语义: 旧: λx. LLM_{θ,p}(x) — 无状态 新: λx. provider(history ++ [x]) — 有状态 (对话感知) """ def _create_provider(model_cfg: Dict) -> (LLMProvider, bool): """ Provider 工厂: 根据 model.provider 字段创建对应的 LLMProvider。 返回: (provider_instance, use_conversation_flag) 路由逻辑: "claude-code" → ClaudeCodeProvider (session persistence via --resume) "anthropic" → AnthropicProvider (Messages API) "openai" → OpenAICompatProvider (Chat Completions API) "ollama" → OpenAICompatProvider (localhost:11434) "dashscope" → OpenAICompatProvider (DashScope endpoint) "deepseek" → OpenAICompatProvider (DeepSeek endpoint) "moonshot" → OpenAICompatProvider (Moonshot endpoint) "zhipu" → OpenAICompatProvider (Zhipu endpoint) """ ``` #### Provider 与 ConversationLam 的关系 ``` model YAML │ ▼ _create_provider() │ ├── LLMProvider (transport layer) │ chat(messages: list[dict]) -> str │ └── use_conversation flag │ ▼ ConversationLam (conversation layer) │ ├── manages messages: List[dict] ├── system message always first ├── appends user/assistant messages on each apply() ├── context window management (sliding window) └── calls provider.chat(managed_messages) ``` #### `model.conversation` 字段 ```yaml model: provider: anthropic name: claude-sonnet-4-20250514 conversation: true # 默认 true: 使用 ConversationLam maxHistoryTokens: 80000 # 对话历史最大 token 数 ``` 当 `conversation: true` 时: - `_compile_lam()` 返回 `ConversationLam` - 每次 `apply()` 将输入追加为 user message,调用 provider,记录 assistant response - 上下文管理: 超过 `maxHistoryTokens` 时,旧消息被压缩为摘要 当 `conversation: false` 时: - `_compile_lam()` 返回传统无状态 `Lam` - 每次 `apply()` 独立调用 LLM,不保留历史 ### 5.6 react_step 的会话模式与无状态模式 ReAct 循环中的 `think` 步骤根据底层 provider 类型自动选择两种模式: #### 会话模式 (Session Mode) — ClaudeCodeProvider 适用于具有原生会话持久化的 provider (如 `claude-code`)。 ``` Step 0: think.apply(full_input + tool_docs + step_info) └─ ClaudeCodeProvider: 创建新 session, 捕获 session_id └─ ConversationLam: 记录到 messages Step 1: think.apply(observation_only) └─ ClaudeCodeProvider: --resume (只发新内容) └─ 底层 Claude 保留完整上下文记忆 Step N: think.apply(observation_only) └─ 同上, session 贯穿整个 ReAct 循环 ``` 关键优化: 后续步骤只发送最新的工具观察结果 (observation),不重复发送完整状态。 Provider 端 (Claude Code CLI) 通过 `--resume` 自动保持完整上下文。 #### 无状态模式 (Stateless Mode) — Ollama / OpenAI / Anthropic API 等 适用于 HTTP API 类型的 provider,无原生会话概念。 ``` Step 0: think.apply(full_input) └─ ConversationLam: messages = [system, user(full_input)] └─ Provider: chat([system, user(full_input)]) -> response Step 1: think.apply(observation) └─ ConversationLam: messages = [system, user(full_input), assistant(r0), user(obs1)] └─ Provider: chat(full_messages_array) -> response Step N: think.apply(observation) └─ ConversationLam: messages 持续增长, 受 maxHistoryTokens 约束 └─ Provider: chat(managed_messages) -> response ``` ConversationLam 的上下文管理器在每次调用前检查 token 预算: - 始终保留 system message - 始终保留最近 N 轮 (keep_recent_turns = 20) - 超出预算时,将旧消息压缩为 "[对话历史摘要]" #### 工具输入序列化 工具参数统一序列化为 JSON 字符串传递给工具函数 (P0 fix): ```python tool_input = action.input if isinstance(action.input, str) else str(action.input) ``` #### 工具参数文档自动生成 `_generate_tool_schema_docs()` 从 MCP 工具的 JSON Schema 自动生成参数说明, 注入到 systemPrompt 尾部,帮助 LLM 正确构造工具调用参数。 --- ## 6. MCP 工具编译规格 ### 6.1 MCP 调用器的构造 ```python def _compile_mcp_caller(server_name: str, tool_name: str, app_cfg: dict) -> Callable: """ 构造一个 MCP 工具调用函数。 输入: server 配置 (url, endpoint, headers) 输出: Callable[[str], str] HTTP 调用流程: POST {url}{endpoint} Headers: Authorization: {token}, Content-Type: application/json Body: {"tool": tool_name, "input": input_text} Response: {"output": "..."} 错误处理: - 网络超时 → 返回 "[MCP_TIMEOUT: {server}/{tool}]" - HTTP 4xx/5xx → 返回 "[MCP_ERROR: {status}]" - JSON 解析失败 → 返回原始 response text 重试策略(由 mcp.policy.retryOnFail 控制): - 指数退避: delay = min(2^attempt * 1s, 30s) - 最大重试次数: retryOnFail(默认 0,即不重试) """ ``` ### 6.2 terminate 的特殊处理 ```python # terminate 永远编译为恒等函数,不管 YAML 怎么写 if tool_name == "terminate": return Tool("terminate", fn=lambda x: x) # Lambda 语义: λx.x # 这是 Y 组合子的 base case # react_step 检测到 terminate 被选中时,停止递归 ``` --- ## 7. Memory 编译规格 ### 7.1 Memory Store 接口 ```python class MemoryStore(ABC): """Memory 的存储后端接口""" @abstractmethod def get(self, key: str) -> Any | None: ... @abstractmethod def put(self, key: str, value: Any, ttl: int = 0) -> None: ... @abstractmethod def list_recent(self, n: int) -> List[Tuple[str, Any]]: ... @abstractmethod def clear(self) -> None: ... ``` ### 7.2 四种 strategy 的实现 | strategy | 实现 | 持久性 | 并发安全 | |---|---|---|---| | `local` | Python dict + LRU | 进程内 | 否 | | `redis` | Redis hash | 跨进程 | 是 | | `sqlite` | SQLite 表 | 磁盘 | 是(单写) | | `custom` | 用户提供类路径 | 取决于实现 | 取决于实现 | ### 7.3 Memory 注入方式 ```python def _compile_memory(agent: Term, memory_cfg: dict) -> Term: """ Memory 编译 = 环境扩展 Γ' = Γ ∪ store 注入方式: 将记忆内容序列化后追加到每次 Agent 调用的输入前面 格式: [Memory Context] - key1: value1 (2min ago) - key2: value2 (15min ago) ... (最近 {size} 条) [Current Input] {original_input} 记忆自动管理: - 每次 Agent 输出后,自动摘要存入 memory - 超过 size 的旧记忆 LRU 淘汰 - 超过 ttl 的记忆自动删除 """ ``` --- ## 8. 静态分析 (lint) 规格 ### 8.1 lint 规则表(完整版) | ID | 级别 | 条件 | Lambda 含义 | 消息 | |---|---|---|---|---| | L001 | ERROR | `systemPrompt` 为空 | `λx.⊥` — 函数体未定义 | "Agent 无行为定义" | | L002 | ERROR | `model` 未配置 | 无 LLM 计算单元 | "无法执行 β-规约" | | L003 | ERROR | `react.maxSteps = 0` | `Y₀(g) = ⊥` | "Agent 不会执行" | | L004 | ERROR | `type: react` 且无 `terminate` | Y 无 base case | "可能无限循环" | | L005 | ERROR | `type: router` 且 `routes` 为空 | CASE 无分支 | "路由无目标" | | L006 | ERROR | `type: chain` 且 `steps` 为空 | 空组合链 | "Pipeline 无步骤" | | L007 | WARN | `temperature > 1.5` | 高熵 `⊕_p` | "输出可能不稳定" | | L008 | WARN | `memory.ttl = 0` 且 `enabled` | Γ' 绑定永不回收 | "内存可能无限增长" | | L009 | WARN | `memory.size = 0` 且 `enabled` | `Γ' = Γ ∪ ∅ = Γ` | "Memory 无效果" | | L010 | WARN | `react.maxSteps > 50` | `Y₅₀+` | "长运行高成本" | | L011 | WARN | ReAct 除 terminate 外无工具 | 纯推理循环 | "Agent 只能思考" | | L012 | WARN | `guard.retry > 5` | 过多重试 | "可能死循环" | | L013 | WARN | `router` 无 `default` | CASE 不完全 | "未分类输入将报错" | | L014 | INFO | `temperature = 0` | 确定性 Lambda | "确定性模式" | | L015 | INFO | Memory 已启用 | `Γ' = Γ ∪ store` | "有状态 Agent" | | L016 | INFO | RAG 已启用 | 外部知识 Oracle | "可访问知识库" | ### 8.2 lint 输出格式 ``` lambdagent lint: agent-config.yml ============================================================ ✗ [ERROR] mcp.localTools: 没有 terminate 工具 Lambda: Y 组合子无 base case (λx.x) → 无穷递归 ⚠ [WARN] react.maxSteps: maxSteps=50 较大 Lambda: Y₅₀(g) → 最多 50 步 β-规约 ℹ [INFO] memory: 启用记忆 strategy=redis, size=20, ttl=7200s Lambda: 环境扩展 Γ' = Γ ∪ store(redis) ℹ [INFO] summary: Agent 'SeeCoderManus' 的 Lambda 结构: Memory(Loop(think >> act >> observe, max_steps=20), redis) ──────────────────────────────────────────────────────── 1 error(s), 1 warning(s), 2 info(s) ``` --- ## 9. 编译器架构 ### 9.1 模块分解 ``` from_config.py ├── from_config(path, **overrides) → Term # 入口 ├── build_agent(cfg) → Term # 递归编译核心 │ ├── _compile_lam(cfg) → ConversationLam|Lam # λ 抽象 (via _create_provider) │ ├── _compile_react(cfg) → Loop # Y 组合子 │ ├── _compile_chain(cfg) → Compose # 函数组合 │ ├── _compile_router(cfg) → Route # CASE │ ├── _compile_parallel(cfg) → Par # PAIR │ ├── _compile_tools(cfg) → Dict[str, Tool] # Oracle 集 │ ├── _compile_memory(agent, cfg) → Memory # 环境扩展 │ └── _compile_guard(agent, cfg) → Guard # 类型约束 ├── _create_provider(model_cfg) → (LLMProvider, bool) # Provider 工厂 ├── _resolve_model(model_cfg) → str # 模型名解析 (legacy fallback) ├── _compile_mcp_caller(server, tool, app) → Callable # MCP HTTP ├── _extract_tool_call(thought, tools) → Tool | None # 工具匹配 ├── _format_state(state, thought, tool, obs) → str # 状态拼接 ├── describe_config(path) → str # Lambda 结构可视化 └── to_lambda_expr(path) → str # 导出纯 Lambda 表达式字符串 ``` ### 9.2 编译流程图 ``` YAML 文件 │ ┌────▼────┐ │ 解析 YAML │ └────┬────┘ │ ┌────▼────┐ │ 校验 Schema│ ── 缺必填字段 ──→ SchemaError └────┬────┘ │ ┌────▼────┐ │ 识别 type │ └────┬────┘ │ ┌────────┬───┼────────┬──────────┐ ▼ ▼ ▼ ▼ ▼ simple react chain router parallel │ │ │ │ │ _lam() _react() _chain() _router() _parallel() │ │ │ │ │ └────────┴───┼────────┴──────────┘ │ ┌────▼────┐ │ guard? │── yes ──→ Guard(agent, P) └────┬────┘ │ ┌────▼────┐ │ memory? │── yes ──→ Memory(agent, store) └────┬────┘ │ ┌────▼────┐ │ 输出 Term │ └─────────┘ ``` --- ## 10. 错误处理规格 ### 10.1 异常层次 ```python class CompileError(LambdagentError): """编译阶段错误(YAML → Term)""" pass class SchemaError(CompileError): """YAML schema 不合法""" field: str # 出错的字段路径 expected: str # 期望的类型/值 actual: Any # 实际值 class SemanticError(CompileError): """语义不合法(schema 合法但逻辑矛盾)""" rule: str # 违反的 lint 规则 ID ``` ### 10.2 编译时 vs 运行时错误 | 阶段 | 示例 | 异常类型 | 应对 | |---|---|---|---| | 解析 | YAML 语法错误 | `yaml.YAMLError` | 报位置 | | Schema | `type` 字段缺失 | `SchemaError` | 报字段路径 | | 语义 | router 无 routes | `SemanticError` | 报 lint 规则 | | 运行时 | LLM API 超时 | `LambdagentError` | 记入 trace | | 运行时 | Guard 验证失败 | `ValidationError` | 重试或 fallback | | 运行时 | Route 无匹配 | `RouteError` | 走 default 或报错 | --- ## 11. 扩展点 ### 11.1 自定义 Provider ```python from_config("agent.yml", providers={ "dashscope": DashScopeProvider(api_key="sk-..."), "openai": OpenAIProvider(api_key="sk-..."), }) ``` ### 11.2 自定义工具注入 ```python from_config("agent.yml", tools={ "web_search": lambda q: requests.get(f"https://api.search.com?q={q}").json(), "run_code": lambda code: exec_sandbox(code), }) ``` ### 11.3 自定义 Memory Store ```python from_config("agent.yml", memory_store=MyRedisStore(host="localhost")) ``` ### 11.4 编译后 Hook ```python agent = from_config("agent.yml") agent = agent >> Tool("postprocess", my_cleanup_fn) # 在编译结果后追加步骤 ``` --- ## 12. 测试验收标准 ### 12.1 编译正确性 | 测试 | 输入 | 期望输出 Term 类型 | 验证方式 | |---|---|---|---| | T01 | `type: simple` | `Lam` | `isinstance(term, Lam)` | | T02 | `type: react, maxSteps: 20` | `Loop(max_steps=20)` 或 `Memory(Loop(...))` | 检查 Loop.max_steps | | T03 | `type: chain, steps: [a, b, c]` | `Compose(3 stages)` | `len(term.stages) == 3` | | T04 | `type: router, routes: {x, y}` | `Route(2 routes)` | `len(term.routes) == 2` | | T05 | `type: parallel, agents: [a, b]` | `Par(2 agents) >> merge` | 检查 Par + Compose | | T06 | `memory.enabled: true` | 最外层是 `Memory` | `isinstance(term, Memory)` | | T07 | `guard.validator 存在` | 内含 `Guard` | 递归查找 Guard | | T08 | `terminate in localTools` | 包含 `Tool("terminate", λx.x)` | 检查 fn 是恒等函数 | ### 12.2 端到端测试 | 测试 | 输入 YAML | 执行输入 | 验证 | |---|---|---|---| | E01 | 现有 `agent-cofig.yml` | "1+1等于几" | 返回包含 "2" 的字符串 | | E02 | simple agent | "Hello" | 返回非空字符串 | | E03 | chain agent (3 steps) | 一段文本 | Context.trace 有 3 条 | | E04 | router agent | 代码问题 | 路由到 code agent | | E05 | react agent (有 terminate) | 简单问题 | 在 maxSteps 内终止 | ### 12.3 lint 测试 | 测试 | 输入配置 | 期望 lint 结果 | |---|---|---| | L_T01 | 无 systemPrompt | ERROR L001 | | L_T02 | react 无 terminate | ERROR L004 | | L_T03 | maxSteps=0 | ERROR L003 | | L_T04 | router 无 routes | ERROR L005 | | L_T05 | 正常配置 | 0 errors | | L_T06 | temperature=2.0 | WARN L007 | --- ## 13. 与现有代码的差距分析 ### 13.1 当前实现 (v1) vs 本规格 (v2) | 能力 | v1 (当前) | v2 (本规格) | 工作量 | |---|---|---|---| | `type: simple` | ✅ | ✅ | 无 | | ConversationLam + Provider | ❌ (Lam only) | ✅ (_create_provider pipeline) | 已完成 | | `provider: claude-code` | ❌ | ✅ (session persistence) | 已完成 | | `model.conversation` 字段 | ❌ | ✅ (conversation: true/false) | 已完成 | | Tool input JSON serialization | ❌ (broken) | ✅ (P0 fix) | 已完成 | | Tool schema auto-gen docs | ❌ | ✅ (_generate_tool_schema_docs) | 已完成 | | `type: react` | ✅ (基础) | ✅ (完整工具提取) | 中 | | `type: chain` | ❌ | ✅ | 新增 | | `type: router` | ❌ | ✅ | 新增 | | `type: parallel` | ❌ | ✅ | 新增 | | MCP HTTP 调用 | ❌ (占位) | ✅ (真实 HTTP) | 中 | | 工具调用提取 | 关键词匹配 | JSON + XML + 关键词 | 中 | | Memory store backend | dict only | local/redis/sqlite/custom | 大 | | Guard 编译 | ❌ | ✅ | 小 | | 嵌套子 Agent | ❌ | ✅ (递归 build_agent) | 中 | | overrides 参数 | ❌ | ✅ | 小 | | Schema 校验 | ❌ | ✅ | 中 | | describe_config | ✅ | ✅ (增强) | 小 | | to_lambda_expr | ❌ | ✅ (导出纯 Lambda) | 中 | ### 13.2 优先级排序 ``` P0 (必须): chain + router + parallel 编译 P1 (重要): MCP 真实 HTTP + 工具调用提取增强 P2 (增强): Schema 校验 + 嵌套子 Agent + overrides P3 (生态): Memory 多后端 + to_lambda_expr + 自定义 Provider ``` --- ## 14. Lambda 对应速查卡 给后续开发者的速查表,编码时随时参考: ``` YAML Python (lambdagent) Lambda 演算 ───────────────────────── ───────────────────────── ────────────────── systemPrompt + model ConversationLam(name, prov, p) λx. prov(history ++ [x]) (conversation: false) Lam("name", prompt, model) λx. LLM_{θ,p}(x) model.provider: claude-code ClaudeCodeProvider(config) session-persistent LLM model.provider: anthropic AnthropicProvider(config) HTTP API LLM model.provider: ollama/... OpenAICompatProvider(config) OpenAI-compat LLM agent(input) term("hello") (f x) → β-规约 type: chain f >> g >> h λx. h(g(f(x))) type: react + maxSteps Loop(body, cond, N) Y_N(λself.λx...) type: router Route(cls, {k: agent}) CASE type: parallel Par(a, b) >> merge PAIR >> merge mcp.onlineTool Tool("name", http_fn) Oracle / primitive terminate Tool("terminate", λx.x) λx.x (identity) memory Memory(agent, store) Γ' = Γ ∪ store guard Guard(agent, P, retry) {x:T | P(x)} temperature Lam(..., temperature=T) ⊕_T (概率参数) maxSteps Loop(..., max_steps=N) Y_N (有界 Y) ``` --- ## 15. CLI 交互入口规格 ### 15.1 设计哲学 CLI 是 lambdagent 的**第一公民交互界面**,不是附属品。理由: 1. **Agent 的本质是函数**——函数最自然的交互方式是 `f(x)`,CLI 就是 `lambdagent run config.yml "input"` 2. **Lambda 演算是 REPL 友好的**——每次输入一个表达式,求值,输出结果,循环 3. **运维需要 CLI**——部署前 lint、运行时 trace、生产排查全在终端完成 4. **MCP/工具生态是 CLI 驱动的**——MCP 服务器本身就是进程间通信 ### 15.2 命令总览 ``` lambdagent [options] [arguments] Commands: compile 编译 YAML → Lambda 项(不执行) run 编译 + 执行 repl 交互式 REPL(持续对话) lint 静态分析 trace 查看/回放 β-规约追踪 lambda 导出纯 Lambda 表达式 serve 启动 HTTP/MCP 服务 tools 管理工具(MCP 发现/测试) version 版本信息 ``` ### 15.3 各命令详细规格 #### `lambdagent compile` — 编译(不执行) ```bash # 编译并打印 Lambda 结构 lambdagent compile agent-config.yml # 输出: # SeeCoderManus = # Memory( # Y₂₀(λself.λstate. # let t = think(state) in # CASE t [(sum, Tool_MCP), (improve, Tool_MCP), (terminate, λx.x)] # >> IF is_terminate THEN t ELSE self(state ⊕ obs) # ), # redis(size=20, ttl=7200) # ) # # Constructs used: Lam, Loop, Route, Tool(×3), Memory # β-reduction bound: max 20 steps # Base case: terminate = λx.x # 选项: # --format text|json|python 输出格式 # --validate 同时运行 lint # --dry-run 验证所有工具可达(ping MCP 端点) ``` **Lambda 语义**:`compile` = 解析 + 翻译,不触发 β-规约。等价于"写出 Lambda 表达式但不求值"。 #### `lambdagent run` — 编译 + 执行 ```bash # 单次执行 lambdagent run agent-config.yml "帮我写一个快速排序" # 从 stdin 读取输入 echo "帮我写一个快速排序" | lambdagent run agent-config.yml - # 从文件读取输入 lambdagent run agent-config.yml --input task.txt # 选项: # --trace 打印每步 β-规约 # --trace-file trace.json β-规约追踪保存到文件 # --max-steps N 覆盖 YAML 的 maxSteps # --temperature T 覆盖 YAML 的 temperature # --timeout S 总超时(秒) # --model MODEL 覆盖模型 # --tool NAME=CMD 注入 CLI 工具(见 15.4) # --env KEY=VALUE 注入环境变量到 Memory # --output FILE 结果输出到文件 # --format text|json 输出格式 # --quiet 只输出最终结果,不打印过程 # --verbose 打印所有中间步骤 ``` **Lambda 语义**:`run` = compile + β-规约。`run config.yml "input"` 等价于 `(from_config("config.yml")) ("input")`。 **`--trace` 输出格式**: ``` β[0] think (2.8s) "帮我写快速排序" → "需要调用代码工具..." β[1] route (0.0s) → selected: sum (Tool_MCP) β[2] Tool:sum (1.2s) → "def quicksort(arr): ..." β[3] think (3.1s) "代码结果..." → "代码已完成,调用 terminate" β[4] route (0.0s) → selected: terminate (λx.x) β[5] terminate (0.0s) → (identity: base case reached) ──────────────────────────────────────── Total: 5 β-reductions, 7.1s, ~2400 tokens ``` #### `lambdagent repl` — 交互式 REPL ```bash # 启动 REPL lambdagent repl agent-config.yml # 输出: # lambdagent REPL v2.0 # Agent: SeeCoderManus (react, maxSteps=20) # Lambda: Memory(Y₂₀(think >> route >> observe), redis) # Type :help for commands, :quit to exit # # λ> 帮我写一个快速排序 # [β[0] think 2.8s] 需要调用代码工具... # [β[1] Tool:sum 1.2s] def quicksort(arr): ... # [β[2] think 3.1s] 代码完成,调用 terminate # [β[3] terminate 0.0s] (base case) # # Result: def quicksort(arr): ... # (4 β-reductions, 7.1s) # # λ> 能不能加上注释? # [Memory: injecting 1 previous exchange] # [β[0] think 2.5s] 用户想要注释版... # ... # REPL 内置命令: # :help 帮助 # :quit / :q 退出 # :trace 显示上次执行的完整 β-规约链 # :trace N 显示第 N 步的详细信息 # :memory 显示当前 Memory 内容 # :memory clear 清空 Memory # :lambda 显示当前 Agent 的 Lambda 表达式 # :lint 重新 lint 配置 # :reload 重新加载 YAML 配置(热更新) # :model MODEL 切换模型 # :temp T 切换温度 # :tools 列出可用工具 # :tool NAME "input" 手动调用单个工具 # :export FILE 导出完整对话 + trace 到文件 # :stats 显示会话统计(总 β-规约数、总 token、总时间) ``` **Lambda 语义**:REPL = 持续的 β-规约环境。每次输入是一个新的函数应用 `(agent input_n)`。Memory 使得 `Γ` 在多次调用间累积——这正是 REPL 与单次 `run` 的区别。 **REPL 的 Lambda 本质**: ``` REPL session = let Γ₀ = {} in let (r₁, Γ₁) = agent(input₁) [Γ₀] in let (r₂, Γ₂) = agent(input₂) [Γ₁] in let (r₃, Γ₃) = agent(input₃) [Γ₂] in ... 每一轮: 1. 读取用户输入 xₙ 2. β-规约: (agent xₙ) [Γₙ₋₁] → (rₙ, Γₙ) 3. 打印 rₙ 4. 更新 Γₙ₋₁ → Γₙ (Memory 写入) 5. 循环 这就是 Y 组合子在 REPL 级别的又一次体现: REPL = Y(λself.λΓ. let x = read() in let (r, Γ') = agent(x)[Γ] in print(r); self(Γ')) ``` #### `lambdagent lint` — 静态分析 ```bash # Lint 单个文件 lambdagent lint agent-config.yml # Lint 目录下所有配置 lambdagent lint configs/ # 选项: # --level error|warn|info 最低报告级别 # --format text|json 输出格式 # --fix 自动修复可修复的问题 # --rules L001,L004 只运行指定规则 # --ignore L014 忽略指定规则 # 退出码: # 0 = 无 error # 1 = 有 error # 2 = 配置文件不存在 ``` #### `lambdagent trace` — 追踪查看/回放 ```bash # 回放上次执行的 trace lambdagent trace trace.json # 选项: # --step N 只看第 N 步 # --slow 逐步回放(按回车继续) # --filter TERM 只看特定 Term 的 β-规约 # --timeline 时间线视图 # --flamegraph 火焰图输出(SVG) ``` **`--timeline` 输出**: ``` Time ──────────────────────────────────────────→ 0s 2.8s 4.0s 7.1s 7.1s │ │ │ │ │ ├─think───┤ │ │ │ ├─search──┤ │ │ ├─think───┤ │ ├─terminate ``` #### `lambdagent lambda` — 导出 Lambda 表达式 ```bash # 导出为纯 Lambda 表达式 lambdagent lambda agent-config.yml # 输出: # Memory( # Y₂₀(λself. λstate. # let t = (λx. LLM_{qwen3-max, "你是SeeCoderManus..."}(x)) state in # CASE (classify t) [ # ("sum", λx. MCP("example-mcp-server", "everything_get_sum", x)), # ("improve", λx. MCP("example-mcp-server", "chat_improve_prompt", x)), # ("terminate", λx. x) # ] >> λobs. # IF (obs = t) THEN t ELSE self(state ⊕ format(t, obs)) # ), # Γ ∪ redis{size=20, ttl=7200} # ) # 选项: # --format human|formal|json 输出格式 # --desugar 展开所有语法糖到纯 λ/app/var ``` #### `lambdagent serve` — 启动服务 ```bash # 启动 HTTP 服务 lambdagent serve agent-config.yml --port 8080 # 启动 MCP 服务器(让其他 Agent 作为工具调用本 Agent) lambdagent serve agent-config.yml --mcp # 启动 WebSocket 服务(流式输出) lambdagent serve agent-config.yml --ws --port 8080 ``` **Lambda 语义**:`serve` = 将 Agent 包装为一个持久运行的函数,等待外部应用(application)。 ``` serve(agent, port) = Y(λself.λΓ. let (x, conn) = accept(port) in ← 等待请求 let (r, Γ') = agent(x) [Γ] in ← β-规约 send(conn, r); ← 返回结果 self(Γ') ← 循环(保持 Memory) ) ``` #### `lambdagent tools` — 工具管理 ```bash # 列出配置中的所有工具 lambdagent tools agent-config.yml # 输出: # Tools for SeeCoderManus: # [MCP] everything_get_sum example-mcp-server https://ai-paas... # [MCP] chat_improve_prompt example-mcp-server https://ai-paas... # [Local] terminate (λx.x) base case # # MCP endpoints: # example-mcp-server: https://your-mcp-endpoint.example.com/mcp/airouting # Status: ✓ reachable (238ms) # 测试单个工具 lambdagent tools agent-config.yml --test everything_get_sum "测试输入" # 发现可用的 MCP 工具 lambdagent tools agent-config.yml --discover example-mcp-server ``` ### 15.4 CLI 工具注入 (`--tool`) CLI 工具注入是 CLI 作为交互入口的**核心差异化能力**——它把任意 shell 命令变成 Lambda 项中的 Tool。 ```bash # 注入 shell 命令作为工具 lambdagent run agent.yml "分析日志" \ --tool grep="grep -c ERROR /var/log/app.log" \ --tool wc="wc -l /var/log/app.log" \ --tool curl="curl -s https://api.example.com/status" # 注入管道命令 lambdagent run agent.yml "统计代码行数" \ --tool loc="find . -name '*.py' | xargs wc -l" # 注入交互式命令(stdin 传参) lambdagent run agent.yml "搜索文件" \ --tool search="grep -r {} src/" ``` **Lambda 语义**: ``` --tool grep="grep -c ERROR /var/log/app.log" 编译为: Tool("grep", fn=lambda x: subprocess.run( "grep -c ERROR /var/log/app.log", shell=True, capture_output=True ).stdout.decode()) 注入到 Agent 的工具集: tools["grep"] = Tool("grep", shell_fn) ``` 当命令中包含 `{}` 占位符时,Agent 的输出会被替换进去: ``` --tool search="grep -r {} src/" 编译为: Tool("search", fn=lambda x: subprocess.run( f"grep -r {shlex.quote(x)} src/", shell=True, capture_output=True ).stdout.decode()) ``` **安全约束**: - `--tool` 注入的命令只在**用户明确指定时**执行 - 命令参数经过 `shlex.quote()` 转义 - 默认超时 30 秒(可通过 `--tool-timeout` 覆盖) - 不允许注入 `rm -rf`、`dd`、`mkfs` 等危险命令(黑名单) ### 15.5 管道组合 — Unix 哲学 lambdagent CLI 遵循 Unix 管道哲学:每个命令读 stdin、写 stdout、错误写 stderr。 ```bash # 管道链: 编译检查 → 执行 → 后处理 lambdagent lint config.yml && \ lambdagent run config.yml "input" | jq '.result' # Agent 链: 一个 Agent 的输出喂给另一个 lambdagent run summarizer.yml "长文本..." | \ lambdagent run translator.yml - # 批量处理 cat inputs.txt | while read line; do lambdagent run agent.yml "$line" --quiet done > outputs.txt # 与系统工具组合 curl -s https://api.example.com/data | \ lambdagent run analyzer.yml - | \ mail -s "分析报告" team@example.com ``` **Lambda 语义**:Unix 管道 `|` = 函数组合 `>>`。 ``` cmd1 | cmd2 | cmd3 = cmd3(cmd2(cmd1(stdin))) = (cmd1 >> cmd2 >> cmd3)(stdin) = Compose(cmd1, cmd2, cmd3).apply(stdin) ``` 这不是类比——这就是同一个数学结构。Unix shell 的管道和 lambdagent 的 `>>` 都是函数组合。CLI 是 Lambda 演算的自然栖息地。 ### 15.6 环境变量接口 ```bash # API Key export ANTHROPIC_API_KEY=sk-... export OPENAI_API_KEY=sk-... export DASHSCOPE_API_KEY=sk-... # 模型覆盖 export LAMBDAGENT_MODEL=claude-sonnet-4-20250514 export LAMBDAGENT_TEMPERATURE=0.0 export LAMBDAGENT_MAX_TOKENS=4096 # 追踪 export LAMBDAGENT_TRACE=1 # 默认开启 trace export LAMBDAGENT_TRACE_FILE=trace.json # trace 输出路径 # MCP export LAMBDAGENT_MCP_TIMEOUT=30 # MCP 调用超时 export LAMBDAGENT_MCP_RETRY=2 # MCP 重试次数 # Memory export LAMBDAGENT_REDIS_URL=redis://localhost:6379 ``` ### 15.7 退出码规范 | 码 | 含义 | 场景 | |---|---|---| | 0 | 成功 | Agent 正常完成,lint 无 error | | 1 | Agent 执行失败 | LLM API 错误、工具超时、Guard 失败 | | 2 | 编译失败 | YAML 语法错误、Schema 不合法 | | 3 | Lint 有 ERROR | lint 发现结构性问题 | | 4 | 配置文件不存在 | 文件路径错误 | | 130 | 用户中断 | Ctrl+C | ### 15.8 输出格式 所有命令支持 `--format` 选项: **`--format text`**(默认,人类可读): ``` Result: def quicksort(arr): ... (4 β-reductions, 7.1s, ~2400 tokens) ``` **`--format json`**(机器可读,管道友好): ```json { "result": "def quicksort(arr): ...", "trace": [ {"step": 0, "term": "think", "duration_ms": 2800, "input": "...", "output": "..."}, {"step": 1, "term": "Tool:sum", "duration_ms": 1200, "input": "...", "output": "..."} ], "stats": { "total_steps": 4, "total_time_ms": 7100, "total_tokens": 2400 } } ``` --- ## 16. CLI 实现架构 ### 16.1 模块分解 ``` lambdagent/ ├── __main__.py # python -m lambdagent 入口 ├── cli/ │ ├── __init__.py │ ├── main.py # argparse 路由 │ ├── compile_cmd.py # lambdagent compile │ ├── run_cmd.py # lambdagent run │ ├── repl_cmd.py # lambdagent repl │ ├── lint_cmd.py # lambdagent lint │ ├── trace_cmd.py # lambdagent trace │ ├── lambda_cmd.py # lambdagent lambda │ ├── serve_cmd.py # lambdagent serve │ ├── tools_cmd.py # lambdagent tools │ └── shell_tool.py # --tool 的 shell 命令包装 ``` ### 16.2 `__main__.py` 入口 ```python """ python -m lambdagent [args] 等价于: lambdagent [args](通过 setup.py entry_points 注册) """ from lambdagent.cli.main import main if __name__ == "__main__": main() ``` ### 16.3 shell_tool.py — CLI 工具注入的核心 ```python """ 将 shell 命令包装为 lambdagent Tool。 --tool grep="grep -c ERROR /var/log/app.log" → Tool("grep", fn=ShellTool("grep -c ERROR /var/log/app.log")) --tool search="grep -r {} src/" → Tool("search", fn=ShellTool("grep -r {} src/", parameterized=True)) """ DANGEROUS_COMMANDS = {"rm -rf", "dd ", "mkfs", "format ", "> /dev/sd"} class ShellTool: def __init__(self, command: str, timeout: int = 30): self.command = command self.timeout = timeout self.parameterized = "{}" in command self._safety_check() def _safety_check(self): for dangerous in DANGEROUS_COMMANDS: if dangerous in self.command: raise ValueError(f"Dangerous command blocked: {self.command}") def __call__(self, input_text: str) -> str: if self.parameterized: cmd = self.command.replace("{}", shlex.quote(str(input_text))) else: cmd = self.command result = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=self.timeout ) if result.returncode != 0 and result.stderr: return f"[ERROR] {result.stderr.strip()}" return result.stdout.strip() ``` ### 16.4 与现有代码的集成点 ``` CLI 命令 调用的核心 API ───────── ────────────── compile from_config(path) → term; describe_config(path) run from_config(path) → term; term(input, ctx) repl from_config(path) → term; while True: term(input, ctx) lint lint_config(path) → issues trace ctx.print_trace() / ctx.trace → json lambda to_lambda_expr(path) → str [新增] serve from_config(path) → term; http.serve(term) tools _build_tools(cfg) → dict; ping endpoints ``` --- ## 17. Agent 间通信: CLI 作为交互协议 ### 17.1 设计哲学 Agent 之间的通信不应只依赖 MCP。**CLI(stdin/stdout)是最通用的进程间通信协议**: - 任何语言写的程序都能读写 stdin/stdout - Unix 管道天然支持 Agent 链式调用 - 不需要 HTTP 服务器、不需要 SDK、不需要注册发现 - **Lambda 语义完美对齐**:管道 `|` = 函数组合 `>>` ### 17.2 三种 Agent 间通信方式 ``` 方式 协议 Lambda 语义 适用场景 ───────── ───────── ───────────── ───────── CLI Pipe stdin/stdout f >> g (Compose) 本机 Agent 链 MCP HTTP/SSE Tool(name, mcp_fn) 远程服务调用 CLI Tool 注入 subprocess Tool(name, shell_fn) 混合生态集成 ``` ### 17.3 YAML 中的 CLI 通信配置 ```yaml # ═══ 新增: CLI 通信配置 ═══ cli: # 本 Agent 作为 CLI 服务(被其他 Agent 调用) server: enabled: true mode: pipe # "pipe" (stdin/stdout) | "socket" (Unix socket) | "tcp" format: json # "text" | "json" | "jsonl" (一行一条) socketPath: /tmp/lambdagent-myagent.sock # mode=socket 时 port: 0 # mode=tcp 时,0=随机端口 # 调用其他 CLI Agent agents: summarizer: command: "python -m lambdagent run summarizer.yml -" format: json timeout: 60 retry: 2 translator: command: "lambdagent run translator.yml --quiet -" format: text timeout: 30 legacy_tool: command: "/usr/local/bin/legacy-analyzer" # 非 lambdagent 程序也行 format: text timeout: 10 # 远程 Agent (通过 SSH) remote_researcher: command: "ssh gpu-server 'lambdagent run researcher.yml --quiet -'" format: json timeout: 120 # 管道链预设(快捷定义常用的 Agent 组合) pipelines: research_and_translate: steps: [summarizer, translator] # 等价于 summarizer >> translator analyze_with_review: steps: [legacy_tool, summarizer] parallel: false # 串行执行 ``` ### 17.4 CLI Agent 编译规则 ```python # cli.agents 中的每个 Agent 编译为 Tool # 但不是普通 Tool——是 "CLI Tool",通过 subprocess 调用 # YAML: # cli: # agents: # summarizer: # command: "lambdagent run summarizer.yml --quiet -" # 编译为: Tool("summarizer", fn=CLIAgent( command="lambdagent run summarizer.yml --quiet -", format="json", timeout=60, )) # Lambda 语义: # summarizer = λx. exec("lambdagent run summarizer.yml --quiet -", x) # 等价于: summarizer = λx. (from_config("summarizer.yml"))(x) # 即: CLI 调用 = β-规约的跨进程版本 ``` ### 17.5 CLIAgent 实现 ```python class CLIAgent: """ 通过 CLI 调用另一个 Agent = 跨进程 β-规约 Lambda 语义: CLIAgent(cmd) = λx. decode(exec(cmd, encode(x))) 通信协议: 1. 将 input 写入子进程的 stdin 2. 从子进程的 stdout 读取 output 3. stderr 用于日志/错误(不影响结果) 这就是 Unix 管道的语义——也是 Lambda 演算应用的语义: (f x) 在 Lambda 中 = 把 x 喂给 f,拿到结果 cmd < input 在 Unix 中 = 把 input 喂给 cmd,拿到结果 """ def __init__(self, command: str, format: str = "text", timeout: int = 60): self.command = command self.format = format self.timeout = timeout def __call__(self, input_text: str) -> str: import subprocess proc = subprocess.run( self.command, shell=True, input=input_text, capture_output=True, text=True, timeout=self.timeout, ) if proc.returncode != 0: return f"[CLI_ERROR:{proc.returncode}] {proc.stderr.strip()}" output = proc.stdout.strip() if self.format == "json": import json data = json.loads(output) return data.get("result", output) return output ``` ### 17.6 使用场景 #### 场景 A: Agent 链式调用(管道) ```bash # 方法 1: Unix 管道(最简单) echo "长文本..." | \ lambdagent run summarizer.yml --quiet - | \ lambdagent run translator.yml --quiet - # 方法 2: YAML 配置(可复用) # config: # cli.agents: {summarizer: ..., translator: ...} # type: chain # chain.steps 引用 cli.agents lambdagent run pipeline.yml "长文本..." # Lambda 语义完全相同: # 方法 1: (translator ∘ summarizer)("长文本...") # 方法 2: (translator >> summarizer)("长文本...") ``` #### 场景 B: Agent 作为工具(ReAct 中调用其他 Agent) ```yaml # master-agent.yml type: react react: {maxSteps: 10} mcp: localTools: [terminate] cli: agents: code_expert: command: "lambdagent run code-agent.yml --quiet -" math_expert: command: "lambdagent run math-agent.yml --quiet -" ``` ``` ReAct 执行: β[0] think → "这个问题需要代码专家" β[1] route → selected: code_expert (CLI Agent) β[2] cli:code_expert → subprocess: lambdagent run code-agent.yml ↳ 子进程内部: β[0] think → β[1] ... → result β[3] think → "代码结果看起来不错,terminate" β[4] terminate → (base case) ``` #### 场景 C: 与非 lambdagent 系统集成 ```yaml cli: agents: # Python 脚本 data_fetcher: command: "python scripts/fetch_data.py" # Go 程序 fast_parser: command: "./bin/parser --format json" # Node.js 服务 renderer: command: "node render.js" # 任意 REST API (通过 curl) api_call: command: "curl -s -X POST https://api.example.com/process -d {}" ``` **关键洞察**:CLI 通信让 lambdagent 可以编排**任何能读写 stdin/stdout 的程序**——不限语言、不限框架、不限部署位置(SSH 透传)。这是 MCP 做不到的通用性。 ### 17.7 CLI vs MCP 对比 ``` 维度 CLI Pipe MCP ────── ────────── ────── 协议 stdin/stdout HTTP/SSE 发现 手动配置 command 服务注册 延迟 低 (fork) 中 (HTTP) 流式 天然支持 (pipe) SSE 跨语言 任何程序 需 MCP SDK 跨机器 SSH 透传 HTTP 有状态 每次 fork 新进程 持久连接 调试 stderr + 退出码 HTTP 状态码 Lambda 语义 f >> g (Compose) Tool(name, fn) ``` ### 17.8 编译器更新 `from_config()` 需要新增对 `cli` 字段的编译: ```python def _compile_cli_agents(cfg: dict) -> dict[str, Tool]: """编译 cli.agents 为 Tool 集合""" cli_cfg = cfg.get("cli", {}) agents_cfg = cli_cfg.get("agents", {}) tools = {} for name, agent_cfg in agents_cfg.items(): tools[name] = Tool( name=f"cli:{name}", fn=CLIAgent( command=agent_cfg["command"], format=agent_cfg.get("format", "text"), timeout=agent_cfg.get("timeout", 60), ), ) return tools ``` CLI Agent 和 MCP Tool 合并到同一个工具集中供 ReAct/Route 使用: ```python all_tools = { **_compile_tools(cfg), # MCP 工具 **_compile_cli_agents(cfg), # CLI Agent 工具 } ``` --- ## 18. CLI 测试验收 | 测试 | 命令 | 期望 | |---|---|---| | C00 | `lambdagent version` | 打印版本信息 | | C01 | `lambdagent compile agent-cofig.yml` | 打印 Lambda 结构,退出码 0 | | C02 | `lambdagent lint agent-cofig.yml` | 输出 lint 结果,退出码 0(无 error) | | C03 | `lambdagent lint bad-config.yml` | 退出码 3(有 error) | | C04 | `lambdagent run simple.yml "Hello"` | 输出非空结果,退出码 0 | | C05 | `lambdagent run simple.yml "Hello" --trace` | 输出含 β[0] 的 trace | | C06 | `lambdagent run simple.yml "Hello" --format json` | 输出合法 JSON | | C07 | `echo "Hello" \| lambdagent run simple.yml -` | stdin 输入有效 | | C08 | `lambdagent run a.yml "x" --tool wc="wc -l f.txt"` | 工具注入有效 | | C09 | `lambdagent lambda agent-cofig.yml` | 输出 Lambda 表达式 | | C10 | `lambdagent tools agent-cofig.yml` | 列出工具 + 状态 | | C11 | `lambdagent repl simple.yml` 后输入 `:quit` | 正常退出 | | C12 | `lambdagent run a.yml "x" --tool rm="rm -rf /"` | 被安全拦截 | | C13 | `echo "Hello" \| lambdagent run a.yml --quiet - \| lambdagent run b.yml -` | Agent 链管道 | | C14 | CLI Agent 配置 `cli.agents.sub` | 编译为 `Tool("cli:sub", CLIAgent(...))` | | C15 | `lambdagent tools config.yml` 含 CLI Agent | 列出 MCP + CLI 工具 | --- ## 19. 新增 YAML 配置字段(v2 扩展) 以下字段对应 lambdagent v2 新增的模块。它们是可选的顶级配置块。 ### 19.1 GroupChat 配置 ```yaml # ═══ GroupChat 配置(type=groupchat 时使用)═══ groupchat: agents: # 参与对话的 Agent 列表 - name: string systemPrompt: string model: object # 可选模型覆盖 maxRounds: int # 最大轮数(Y 组合子展开上界),默认 10 scheduler: string # "round_robin" | "random" | "llm" schedulerPrompt: string # scheduler=llm 时的分类 prompt termination: # 终止条件 keywords: [string] # 包含这些关键词时终止,默认 ["CONSENSUS", "DONE"] custom: string # Python 表达式 (state, round) → bool summaryAgent: # 可选的总结 Agent systemPrompt: string model: object ``` 编译目标: ``` GroupChat( agents=[Lam(a1), Lam(a2), ...], max_rounds=10, scheduler="round_robin" | Term(schedulerPrompt), termination=fn, summary_agent=Lam(summaryPrompt) ) ``` ### 19.2 SharedMemory 配置 ```yaml # ═══ SharedMemory 配置(多 Agent 共享状态)═══ sharedMemory: enabled: bool # 是否启用 store: # 初始共享数据 key1: value1 key2: value2 appendOnly: bool # true = 类型安全模式(Σ' ⊇ Σ) ``` 编译目标: ``` SharedMemory(store={"key1": "value1"}, append_only=True) → sm.wrap(agent) # 将 Agent 绑定到共享记忆 ``` ### 19.3 Channel 配置 ```yaml # ═══ Channel 配置(Agent 间通信)═══ channels: - name: string # 通道名称 capacity: int # 缓冲区大小,0=无缓冲(同步),默认 0 ``` 编译目标: ``` channels = {name: Channel(name, capacity) for ch in config.channels} ``` ### 19.4 MCP Server 配置(v2 扩展) ```yaml # ═══ MCP Server 配置(v2: 结构化 MCP 连接)═══ mcpServers: - name: string # 服务器名称 transport: string # "http" | "stdio" url: string # transport=http 时的 URL command: string # transport=stdio 时的命令 args: [string] # transport=stdio 时的参数 env: # transport=stdio 时的环境变量 KEY: VALUE headers: # transport=http 时的 HTTP 头 Authorization: string timeout: float # 超时秒数,默认 30 tools: [string] # 只暴露这些工具(空=全部),可选 ``` 编译目标: ```python server = MCPServer.http(url, headers) | MCPServer.stdio(command, args, env) tools = server.to_tools() # 或 [server.to_tool(name) for name in config.tools] ``` ### 19.5 Handoff 配置 ```yaml # ═══ Handoff 配置(动态委派)═══ handoff: selector: # 选择器 prompt: string # LLM 分类器 prompt model: object # 可选模型覆盖 registry: # Agent 注册表 : # 目标 Agent 名称 type: string systemPrompt: string # ... 子 agent 完整配置 fallback: # 兜底 Agent(可选) systemPrompt: string ``` 编译目标: ``` Handoff( selector=Lam(selectorPrompt), registry={name: compile(sub_agent) for ...}, fallback=Lam(fallbackPrompt) ) ``` ### 19.6 Skill 配置 ```yaml # ═══ Skill 配置(可复用技能)═══ skills: - name: string # 技能名称 description: string # 自然语言描述 tags: [string] # 标签 inputType: string # 输入类型描述,默认 "Str" outputType: string # 输出类型描述,默认 "Str" agent: # 技能实现 type: string systemPrompt: string # ... 完整 agent 配置 examples: # 使用示例 - input: string output: string ``` 编译目标: ```python Skill( name=name, term=compile(agent_config), description=description, signature=SkillSignature(inputType, outputType), tags=tags, examples=[(inp, out), ...] ) ``` ### 19.7 Checkpoint 配置 ```yaml # ═══ Checkpoint 配置(状态持久化)═══ checkpoint: enabled: bool # 是否启用自动 checkpoint directory: string # checkpoint 目录路径 maxCheckpoints: int # 最多保留多少个,默认 10 autoSaveSteps: int # 每 N 步 β-规约自动保存,0=不自动保存 ``` 编译目标:在 Runtime 中注入 `CheckpointManager(directory, max_checkpoints)`。 ### 19.8 A2A 配置 ```yaml # ═══ A2A Protocol 配置(Agent 间协议)═══ a2a: server: # 发布为 A2A 服务器(可选) port: int # 监听端口,默认 8000 host: string # 监听地址,默认 "0.0.0.0" clients: # 远程 A2A Agent(可选) - name: string # 本地名称 url: string # 远程 A2A endpoint URL timeout: float # 超时秒数,默认 60 ``` 编译目标: ```python # 远程 Agent 封装为本地 Term remote_agents = {c.name: A2AClient(c.url, timeout=c.timeout) for c in config.a2a.clients} # 可注入 Handoff / Route 的 registry ``` ### 19.9 字段分级更新 | 级别 | 字段 | Lambda 语义 | 编译行为 | |---|---|---|---| | **必须** | `type`, `systemPrompt` | Agent 结构 + λ body | 缺失则报编译错误 | | **核心** | `model`, `react`, `mcp` | 计算单元 + Y 参数 + Oracle | 缺失则用默认值 | | **增强** | `memory`, `guard`, `chain`, `router`, `parallel` | 环境扩展 + 类型约束 + 组合 | 缺失则不包裹 | | **多智能体** | `groupchat`, `sharedMemory`, `channels`, `handoff` | π-演算扩展 | 缺失则不启用 | | **协议** | `mcpServers`, `a2a`, `skills` | MCP + A2A + Skill 系统 | 缺失则不启用 | | **持久化** | `checkpoint`, `rag` | 状态保存 + 检索增强 | 缺失则不启用 | | **安全** | `sandbox` | 进程隔离 + 资源限制 | 缺失则不启用 | | **元数据** | `agentId`, `name`, `description` | 不影响 Lambda 语义 | 只用于标识和追踪 | --- ## 20. Sandbox 配置字段 ### 20.1 YAML Schema ```yaml sandbox: enabled: true # 是否启用沙箱(默认 false) timeout: 30 # CPU 超时(秒),对应 RLIMIT_CPU memory_mb: 256 # 内存上限(MB),对应 RLIMIT_AS network: false # 是否允许网络访问 allow_subprocess: false # 是否允许创建子进程(RLIMIT_NPROC) max_output_bytes: 65536 # 最大输出字节数 max_fds: 64 # 最大文件描述符数(RLIMIT_NOFILE) policy: default # 预设策略: strict | default | permissive ``` ### 20.2 预设策略 当指定 `policy` 时,其余字段作为覆盖值(override)。未指定的字段使用预设默认值。 | 字段 | `strict` | `default` | `permissive` | |------|----------|-----------|--------------| | `timeout` | 5 | 30 | 300 | | `memory_mb` | 64 | 256 | 2048 | | `network` | false | false | true | | `allow_subprocess` | false | false | true | | `max_output_bytes` | 4096 | 65536 | 1048576 | | `max_fds` | 8 | 64 | 256 | ### 20.3 编译目标 ```python # sandbox.enabled = true 时: from lambdagent import SandboxPolicy, SecureExecutor if config.sandbox.policy: policy = getattr(SandboxPolicy, config.sandbox.policy)() else: policy = SandboxPolicy( timeout=config.sandbox.timeout, memory_mb=config.sandbox.memory_mb, network=config.sandbox.network, allow_subprocess=config.sandbox.allow_subprocess, max_output_bytes=config.sandbox.max_output_bytes, max_fds=config.sandbox.max_fds, ) # 字段级覆盖(policy 为基础,YAML 字段覆盖) if config.sandbox.timeout is not None: policy.timeout = config.sandbox.timeout # ... executor = SecureExecutor(policy=policy) term = executor.sandbox_all_tools(term) # 递归包裹所有 Tool ``` ### 20.4 Lambda 语义 Sandbox 不改变 Lambda 语义。编译后的 Term 树类型不变: ``` ⟦sandbox: {enabled: true, policy: P}⟧ = map(λt. SandboxedTool(t.name, t.fn, P), Tools(term)) ``` 所有 `Tool` 节点被替换为 `SandboxedTool`,但 `SandboxedTool <: Tool`,Term 树结构不变。