## Context 本项目是一个全新的 Python 项目(greenfield),目标是构建一个基于 LlamaIndex Workflow 的 Agent,能够动态加载和执行 SKILL。 **当前状态:** - 项目仅有 `src/llama_agent_skills/__init__.py` 中一个空的 `main()` 入口 - 无任何现有依赖(`pyproject.toml` 的 `dependencies = []`) - `.opencode/skills/` 和 `.github/skills/` 下有 4 个 SKILL.md 示例文件,定义了 SKILL 的标准格式 **SKILL 格式(已有约定):** - YAML frontmatter:`name`、`description`、`license`、`metadata`(author、version) - Markdown 正文:自由格式的指令文本,包含 Steps、Guardrails、Input/Output 等段落 **LlamaIndex Workflow 关键能力(调研结论):** - `Workflow` 基类:事件驱动、`@step` 装饰器、`Context` 状态管理 - `AgentWorkflow.from_tools_or_functions()`:接受 `tools` 列表和 `system_prompt` - `FunctionAgent`:支持 `system_prompt`、`tools`、`can_handoff_to` - 工具列表存储为实例变量 `self.tools`,可运行时动态增删 - `Context.set/get` 提供步骤间状态共享 **约束:** - 不引入 Shell/subprocess,纯 Python 运行 - 脚本类 SKILL 功能暂不支持 - 需要支持线上部署场景 ## Goals / Non-Goals **Goals:** - 实现 SKILL.md 文件的解析器,能提取 frontmatter 元数据和正文指令 - 实现 SKILL 注册表,支持从目录扫描、运行时动态注册/注销 - 构建基于 LlamaIndex Workflow 的核心 Agent,将 SKILL 作为 system prompt 的扩展注入 - 提供清晰的 Python API,方便程序化创建和运行 Agent - 保持架构简洁,为后续扩展(MCP 工具、多 Agent 协作)留出空间 **Non-Goals:** - 不实现 Shell/subprocess 执行能力(安全约束) - 不实现 SKILL 内定义的脚本/命令执行功能 - 不实现多 Agent 协作/handoff(后续迭代) - 不实现 SKILL 的远程加载(仅支持本地文件系统) - 不实现 Web UI 或 REST API 层 - 不实现 MCP server/client 集成(后续迭代) ## Decisions ### D1: SKILL 注入方式 — System Prompt 拼接 **决定:** 将 SKILL 的 Markdown 正文作为 system prompt 的一部分注入 Agent。 **备选方案:** - A) 将 SKILL 转换为 LlamaIndex `BaseTool`(每个 SKILL 变成一个可调用工具) - B) 将 SKILL 注入 system prompt(当前选择) - C) 将 SKILL 作为 RAG 文档,通过检索注入 **选择 B 的理由:** - SKILL 本质是「行为指令」而非「可调用函数」,它定义了 Agent 应该如何行动,而不是提供一个新的能力入口 - System prompt 注入最自然,与 LlamaIndex 的 `system_prompt` 参数直接对应 - 方案 A 需要为每个 SKILL 包装一个工具入口,增加不必要的间接层 - 方案 C 引入 RAG 复杂度,对于有限数量的 SKILL 是过度设计 - 后续如果 SKILL 需要携带工具定义,可以在 SKILL 格式中扩展 `tools` 字段,注册为真正的工具 ### D2: SKILL.md 解析策略 — python-frontmatter 库 **决定:** 使用 `python-frontmatter` 库解析 SKILL.md 的 YAML frontmatter + Markdown body。 **备选方案:** - A) 手写 YAML + Markdown 分割解析器 - B) 使用 `python-frontmatter` 库(当前选择) - C) 使用 `pyyaml` + 手动分割 `---` 分隔符 **选择 B 的理由:** - `python-frontmatter` 是成熟的库,专门处理 frontmatter 格式,边界情况覆盖好 - 减少自研代码量和 bug 风险 - 自动处理 YAML 类型转换 ### D3: Agent 架构 — 基于 AgentWorkflow 的单 Agent **决定:** 使用 `AgentWorkflow.from_tools_or_functions()` 构建单 Agent,通过 `system_prompt` 参数注入 SKILL 上下文。 **备选方案:** - A) 自定义 `Workflow` 子类,手动实现所有步骤 - B) 使用 `AgentWorkflow.from_tools_or_functions()`(当前选择) - C) 使用 `FunctionAgent` + 多 Agent 编排 **选择 B 的理由:** - `AgentWorkflow` 提供开箱即用的对话循环、工具调用、流式输出 - 对于第一版,单 Agent 足够验证 SKILL 注入概念 - 如需自定义步骤(如 SKILL 热加载),可以后续继承扩展 - 方案 A 需要重写大量 LlamaIndex 已有逻辑 - 方案 C 在当前阶段是过度设计 ### D4: 模块结构 — 职责分离 **决定:** 按职责划分为以下模块: ``` src/llama_agent_skills/ ├── __init__.py # 包入口 + main() ├── skill.py # Skill 数据模型 + SKILL.md 解析器 ├── registry.py # SkillRegistry(扫描、注册、查询) ├── agent.py # Agent 构建与运行(封装 AgentWorkflow) └── config.py # 配置管理(环境变量、默认值) ``` **理由:** - `skill.py` 专注数据解析,与 LlamaIndex 无关,可独立测试 - `registry.py` 管理 SKILL 生命周期,依赖 `skill.py` 但不依赖 `agent.py` - `agent.py` 是唯一依赖 LlamaIndex 的模块,负责将 registry 中的 SKILL 组装为 Agent - `config.py` 集中管理配置,避免硬编码散落 ### D5: SKILL 上下文组装策略 **决定:** 在 Agent 启动时,将所有已注册 SKILL 的正文按固定格式拼接到 system prompt: ``` {base_system_prompt} --- ## Active Skills ### Skill: {skill.name} {skill.description} {skill.body} --- ### Skill: {skill2.name} ... ``` **理由:** - 结构化的分隔让 LLM 清楚区分不同 SKILL 的指令 - 每个 SKILL 带有名称和描述作为标题,便于 LLM 理解上下文切换 - 用 `---` 分隔避免 SKILL 间指令混淆 ## Risks / Trade-offs - **[System prompt 长度爆炸]** → 当加载多个大型 SKILL 时,system prompt 可能超出 LLM context window。**缓解:** 限制同时加载的 SKILL 数量,后续可引入选择性加载或摘要机制。 - **[SKILL 间指令冲突]** → 多个 SKILL 可能包含矛盾指令。**缓解:** 第一版不解决,通过文档约定 SKILL 应自包含;后续可增加冲突检测。 - **[LlamaIndex 版本锁定]** → 依赖 `llama-index-core` 的特定 API(如 `AgentWorkflow`),升级可能 break。**缓解:** 通过 `agent.py` 隔离 LlamaIndex 依赖,仅在该模块中引用 LlamaIndex API。 - **[无 Shell 限制降低灵活性]** → 部分 SKILL 可能期望执行 CLI 命令(如 `openspec` CLI)。**缓解:** 明确标记为 Non-Goal,后续可通过安全沙箱或 API wrapper 支持。 - **[SKILL.md 格式无 schema 校验]** → frontmatter 字段错误不会被提前发现。**缓解:** 在 `skill.py` 中做基本校验(必填字段检查),后续可引入 JSON Schema 或 Pydantic 校验。