Files
scaffold/template/.claude/rules/agent-llm-api.md
T
baozaotumao 39fe248f9f feat: 重组为标准 skill 包结构 + 修复 verify.sh bash 3.2 兼容
- 新增 .claude/skills/new-project/SKILL.md(标准 skill 格式)
- install-skill.sh 安装时组装 SKILL.md + verify.sh,单一源无重复
- verify.sh 改用索引数组替代 declare -A,兼容 macOS bash 3.2
- log_pass/log_skip 显式 return 0,避免 set -e 下非 verbose 误退出
- README 改为 skill 优先;补全 copier.yml/scripts/template 入库
2026-06-01 21:19:45 -07:00

3.6 KiB
Raw Blame History

paths
paths
agent/**

Agent LLM 驱动层与 API 桥接

厂商无关的 LLM API 抽象、HTTP SDK 集成机制、超时保护与流式响应处理。

重要:LLMSession 端口是协议,不是机制

  • LLMSession Protocol 只定义"能力"(发消息、收推送流、关闭),不规定底层是哪家 SDK
  • 本文件描述 API SDK 适配器实现模式(OpenAI / Anthropic 等)
  • PTY CLI 路径见 agent-llm-pty.md(当前配置未启用)

LLMSession 协议(domain/ports.py

API SDK 场景使用推送式流AsyncIterator),而非拉取式 stream_until

from typing import Protocol, AsyncIterator

class LLMSession(Protocol):
    async def generate(
        self,
        messages: list[dict],          # [{"role": "user", "content": "..."}]
    ) -> AsyncIterator[str]: ...        # 逐 token 推送
    async def close(self) -> None: ...  # 释放 HTTP 连接池

适配器结构(llm/

文件 职责
domain/ports.py LLMSession 协议(端口,接口定义)
llm/<provider>_api.py SDK 适配器实现(实现 LLMSession
llm/registry.py Settings.llm_provider 选择实现

pty_bridge.py,无 skills/installer.py——API 模式不需要 PTY 或 skill 安装。

适配器实现模式

from typing import AsyncIterator, override
from domain.ports import LLMSession

class OpenAISession(LLMSession):
    def __init__(self, settings) -> None:
        from openai import AsyncOpenAI
        self._client = AsyncOpenAI(
            api_key=settings.llm_api_key,
            base_url=settings.llm_base_url or None,
        )
        self._model = settings.llm_model

    @override
    async def generate(self, messages: list[dict]) -> AsyncIterator[str]:
        stream = await self._client.chat.completions.create(
            model=self._model,
            messages=messages,
            stream=True,
        )
        async for chunk in stream:
            delta = chunk.choices[0].delta.content
            if delta:
                yield delta

    @override
    async def close(self) -> None:
        await self._client.close()

Anthropic 适配器同理,将 AsyncOpenAI 换为 AsyncAnthropic,流式 API 换为 client.messages.stream

DAG 节点中的调用模式

async def phase3_generate(state, send, recv_queue):
    try:
        async with asyncio.timeout(settings.generate_timeout_seconds):
            chunks: list[str] = []
            async for token in llm_session.generate(state.messages):
                chunks.append(token)
                await send({"type": "llm_token", "text": token})
            html = extract_html("".join(chunks))
            if not html:
                raise HtmlExtractionError()
    except TimeoutError:
        raise LLMTimeoutError(phase="generate")

不需要 ANSI 过滤——API 响应是纯文本,无终端转义码。

超时保护

每个 DAG 阶段用 asyncio.timeout() 包裹 LLM 调用:

阶段 配置项
phase1_dialog(单轮对话) dialog_timeout_seconds(默认 60s
phase2_preview3 张预览) preview_timeout_seconds(默认 180s
phase3_generate(完整 HTML generate_timeout_seconds(默认 300s

超时后抛 LLMTimeoutErrordomain/exceptions.py),不静默挂起。

连接管理

LLMSession 实例在 DAG 节点开始时创建,在 runner.pyfinally 块中通过 await llm_session.close() 确定性释放 HTTP 连接池,防止连接泄漏。

禁止在节点内手动调用 close()——由 runner 统一回收。