从零构建AI文档协作工作台:Skill Runtime驱动的智能引导系统实战

Posted by iceyao on Monday, March 9, 2026

一、引言

在云原生时代,编写高质量的部署描述文档是开发运维工作中不可或缺的一环。一份完整的 K8s 部署描述涉及镜像配置、资源限制、网络策略、健康检查等 8 大模块,新手很难一次性写全、写好。

AI Co-authoring 交互工作台正是为解决这一痛点而生——通过 DeepSeek 大模型 + Skill 技能机制 + 多轮引导对话,帮助用户将一段粗糙的部署描述逐步结构化为专业完整的 Markdown 文档。

本文从Skill Runtime 运行时原理、架构设计、Co-authoring 引导流程、数据模型、前端交互等维度完整拆解技术实现。

graph LR
    A[用户输入粗糙描述] --> B[AI 分析 + Skill 加载]
    B --> C[多轮引导对话]
    C --> D[结构化文档输出]
    D --> E[导出 .md 文件]

二、系统总体架构

2.1 技术栈

层级 技术选型 职责
后端框架 FastAPI RESTful API + SSE 流式响应
AI 模型 DeepSeek API(OpenAI 兼容) 大模型推理 + Tool Calling
前端 HTML/CSS/JS + Gradio(双模式) 深色科技风交互界面
渲染 markdown-it-py Markdown → HTML
存储 本地 JSON + localStorage 本地优先,数据不出端
运行时 Python 3.12 + uv 现代 Python 环境

2.2 架构全景

graph TB
    subgraph Browser ["浏览器"]
        UI["前端 UI"] --- LS["localStorage 草稿"]
    end
    subgraph Backend ["FastAPI 后端"]
        API["REST / SSE API"]
        CS["CoauthorService 引导服务"]
        AI["DeepSeekClient AI 客户端"]
        SK["SkillLoader 技能加载器"]
        ST["StorageService 存储"]
    end
    subgraph Ext ["外部"]
        DS["DeepSeek API"]
    end
    subgraph FS ["文件系统"]
        Skills["skills/*.md"]
        Data["~/.ai-coauthor/"]
    end
    UI --> API --> CS --> AI --> DS
    AI --> SK --> Skills
    CS --> ST --> Data

2.3 项目结构

ai-spec/
├── src/
│   ├── main.py              # FastAPI 入口
│   ├── config.py            # 配置 + 特性开关
│   ├── models/              # Session / Message / Document
│   ├── services/
│   │   ├── ai_service.py    # DeepSeek 客户端 + Tool Call 处理
│   │   ├── coauthor.py      # Co-authoring 引导逻辑
│   │   ├── skills.py        # ★ Skill Runtime 核心
│   │   ├── storage.py       # 本地 JSON 存储
│   │   └── templates.py     # 部署描述结构化模板
│   ├── ui/                  # Gradio UI + 样式
│   ├── static/              # 纯前端(HTML/CSS/JS)
│   └── utils/markdown.py
├── skills/                  # Skill 定义目录
│   ├── doc-coauthoring/SKILL.md
│   ├── docker/SKILL.md
│   └── kubernetes/SKILL.md
└── specs/                   # 设计文档

三、Skill Runtime 核心原理(重点)

Skill Runtime 是本项目的技术核心,构建了从磁盘文件到大模型上下文的完整链路

3.1 Skill 的本质

先理解 Tool Calling 原理:

大模型的 tool_call 本质上只是返回一个结构化的"调用建议",模型本身不会执行代码。真正的执行发生在本地 Agent Runtime:本地程序注册可用工具并解析模型返回结果,一旦检测到 tool_calls 字段,就根据工具注册表匹配对应函数并执行,再把结果回传给模型继续推理。

Skill 是 Tool Calling 之上的工程化封装

Skill = Prompt(领域知识)+ SOP(工作流程)+ Tool(可执行能力)
要素 说明 来源
领域知识 注入大模型上下文的专业知识 SKILL.md body
工作流程 结构化多步骤引导策略 SKILL.md 中的 Stage 定义
关联资源 脚本、参考文档(可选) 技能目录下 scripts/、references/

3.2 四层架构

graph TB
    subgraph L4 ["第四层:执行层 DeepSeekClient"]
        TC["Tool Call 协议处理 + 消息重组"]
    end
    subgraph L3 ["第三层:注册层"]
        TD["将 Skill 注册为 load_skill Tool"]
    end
    subgraph L2 ["第二层:加载层 SkillLoader"]
        SCAN["目录扫描 → 解析 → 缓存 → 格式化"]
    end
    subgraph L1 ["第一层:存储层"]
        F1["skills/doc-coauthoring/SKILL.md"]
        F2["skills/docker/SKILL.md"]
        F3["skills/kubernetes/SKILL.md"]
    end
    L1 --> L2 --> L3 --> L4

3.3 第一层:SKILL.md 文件规范

每个 Skill 是 skills/ 下一个子目录,核心是 SKILL.md(YAML frontmatter + Markdown body):

---
name: "doc-coauthoring"
description: "Guide users through a structured workflow for co-authoring
documentation. Trigger when user mentions writing docs, drafting specs..."
---

# Doc Co-Authoring Workflow

## Stage 1: Context Gathering
询问文档类型、受众、目的、约束...

## Stage 2: Refinement & Structure
逐节优化,提出 5-20 个要点供用户筛选...

## Stage 3: Reader Testing
用新上下文 AI 验证文档是否清晰...

关键约束:

  • name:kebab-case,无空格无大写
  • description:同时写明做什么何时触发,直接影响 AI 匹配准确率
  • body 会被完整注入到大模型上下文

3.4 第二层:SkillLoader 加载器

SkillLoader 负责 扫描 → 解析 → 缓存 → 格式化 的完整流程:

class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills_dir = skills_dir
        self.skills: dict[str, dict] = {}
        self.load_skills()  # 启动时自动扫描

    def load_skills(self) -> None:
        """扫描 skills/ 下所有子目录"""
        for skill_dir in self.skills_dir.iterdir():
            if not skill_dir.is_dir():
                continue
            skill_md = skill_dir / "SKILL.md"
            if skill_md.exists():
                skill = self.parse_skill_md(skill_md)
                if skill:
                    self.skills[skill["name"]] = skill

SKILL.md 解析——用正则提取 frontmatter,手工解析避免 PyYAML 依赖:

def parse_skill_md(self, path: Path) -> Optional[dict]:
    content = path.read_text(encoding="utf-8")
    match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)$", content, re.DOTALL)
    if not match:
        return None
    frontmatter, body = match.groups()
    metadata = {}
    for line in frontmatter.strip().split("\n"):
        if ":" in line:
            key, value = line.split(":", 1)
            metadata[key.strip()] = value.strip().strip("\"'")
    return {
        "name": metadata["name"],
        "description": metadata["description"],
        "body": body.strip(),
        "dir": path.parent,  # 保存目录引用,用于探测关联资源
    }

Prompt 格式化——将技能内容包装为可注入 prompt 的结构,同时自动探测关联资源:

def format_skill_for_prompt(self, name: str) -> str:
    content = self.get_skill_content(name)  # body + 关联资源列表
    return f"""<skill-loaded name="{name}">
{content}
</skill-loaded>

请按照上述技能说明完成用户的任务。"""

def get_skill_content(self, name: str) -> str:
    skill = self.skills[name]
    content = f"# 技能: {skill['name']}\n\n{skill['body']}"
    # 自动探测 scripts/ references/ assets/ 下的文件
    for folder, label in [("scripts", "脚本"), ("references", "参考文档")]:
        folder_path = skill["dir"] / folder
        if folder_path.exists():
            files = list(folder_path.glob("*"))
            if files:
                content += f"\n- {label}: {', '.join(f.name for f in files)}"
    return content

全局单例 + 热重载

_skills_loader: Optional[SkillLoader] = None

def get_skills_loader() -> SkillLoader:
    global _skills_loader
    if _skills_loader is None:
        _skills_loader = SkillLoader(Path("skills"))
    return _skills_loader

def reload_skills() -> SkillLoader:
    """运行时热重载,配合 POST /api/skills/reload"""
    global _skills_loader
    _skills_loader = None
    return get_skills_loader()

3.5 第三层:注册为 Tool

DeepSeekClient 将所有 Skill 聚合为一个 load_skill 工具,注册到 Function Calling 协议:

def get_skill_tool(self) -> dict:
    skill_descriptions = self._skills.get_descriptions()
    # 输出示例:
    # - **doc-coauthoring**: Guide users through...
    # - **docker**: Docker 容器化专家...
    # - **kubernetes**: K8s 部署配置专家...
    return {
        "type": "function",
        "function": {
            "name": "load_skill",
            "description": f"加载技能获取领域专业知识。\n"
                           f"可用技能:\n{skill_descriptions}",
            "parameters": {
                "type": "object",
                "properties": {
                    "skill_name": {"type": "string", "description": "技能名称"}
                },
                "required": ["skill_name"]
            }
        }
    }

大模型根据 description 中的技能列表自主判断是否需要以及加载哪个 Skill。

3.6 第四层:Tool Call 协议闭环(最核心)

这是 Skill Runtime 的灵魂——Tool Call 的完整处理闭环

完整时序

sequenceDiagram
    participant U as 用户
    participant CS as CoauthorService
    participant AI as DeepSeekClient
    participant DS as DeepSeek API
    participant SL as SkillLoader

    U->>CS: 输入部署描述
    CS->>AI: chat(messages, use_tools=True)
    AI->>DS: 请求(messages + tools=[load_skill])
    DS-->>AI: finish_reason="tool_calls"<br/>load_skill("doc-coauthoring")
    Note over AI: 本地执行 Tool
    AI->>SL: format_skill_for_prompt("doc-coauthoring")
    SL-->>AI: 返回格式化 Skill 内容
    Note over AI: 消息重组
    AI->>DS: 原始messages + assistant_tool_call<br/>+ tool_result
    DS-->>AI: finish_reason="stop"<br/>基于 Skill 知识生成引导回复
    AI-->>CS: (content, reasoning)
    CS-->>U: AI 引导回复

核心代码——chat 入口

def chat(self, messages, temperature=0.7, max_tokens=2000, use_tools=False):
    # 有技能时才注入 Tool 定义
    tools = [self.get_skill_tool()] if use_tools and self._skills.list_skills() else None
    
    response = self.sync_client.chat.completions.create(
        model=self.model, messages=messages, tools=tools, ...
    )
    message = response.choices[0].message
    finish_reason = response.choices[0].finish_reason
    
    # 关键分支:模型要求调用 Tool
    if finish_reason == "tool_calls" and message.tool_calls:
        return self._handle_tool_calls_sync(messages, message, ...)
    return message.content or "", None

核心代码——Tool Call 消息重组

def _handle_tool_calls_sync(self, messages, assistant_message, ...):
    # 1. 将 assistant 的 tool_call 消息加入历史
    new_messages = messages + [assistant_message]
    
    # 2. 逐个执行 tool_call
    for tool_call in assistant_message.tool_calls:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)
        
        if func_name == "load_skill":
            result = self.execute_skill_tool(func_args["skill_name"])
        else:
            result = f"未知工具: {func_name}"
        
        # 3. 以 tool role 追加结果(必须关联 tool_call_id)
        new_messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": result,
        })
    
    # 4. 带完整消息链重新请求(use_tools=False 防无限循环)
    return self.chat(new_messages, ..., use_tools=False)

两个关键设计点

  1. tool_call_id 关联:每个 tool result 必须通过 ID 与原始 tool_call 一一对应,这是 OpenAI 协议强制要求
  2. use_tools=False 防递归:二次请求禁用 tools,避免模型再次触发 tool_call 导致无限循环

3.7 全流程总结

graph TB
    A["应用启动"] --> B["SkillLoader 扫描 skills/"]
    B --> C["解析 SKILL.md frontmatter + body"]
    C --> D["缓存到内存字典"]
    E["用户请求"] --> F["构建消息上下文"]
    F --> G["生成 load_skill Tool 定义"]
    G --> H["发送到 DeepSeek API"]
    H --> I{finish_reason?}
    I -->|tool_calls| J["本地执行: 加载 Skill 内容"]
    J --> K["组装 tool result 消息"]
    K --> L["重新请求(禁用 tools)"]
    L --> M["返回 AI 回复"]
    I -->|stop| M

3.8 内置 Skill

Skill 描述 核心内容
doc-coauthoring 文档协作技能(核心) 三阶段:Context Gathering → Refinement → Reader Testing
docker Docker 容器化专家 Dockerfile 最佳实践、多阶段构建、镜像优化、安全加固
kubernetes K8s 部署专家 资源配置、健康检查、滚动更新、常见问题排查

四、Co-authoring 引导服务

4.1 三阶段引导模型

根据会话轮次自动切换阶段:

def get_stage(self, session: Session) -> str:
    round_num = session.current_round
    max_rounds = session.max_rounds  # 默认 10
    if round_num <= max_rounds * 0.3:   return "context"      # 1-3 轮
    elif round_num <= max_rounds * 0.8: return "refinement"   # 4-8 轮
    else:                                return "testing"      # 9-10 轮
graph LR
    S1["Stage 1: Context Gathering<br/>上下文收集 (30%)"] --> S2["Stage 2: Refinement<br/>优化结构化 (50%)"] --> S3["Stage 3: Reader Testing<br/>可读性验证 (20%)"]

每个阶段有专属的 prompt 引导策略:

阶段 目标 AI 行为
Context 了解文档类型、受众、约束 询问背景、收集需求
Refinement 逐节完善内容 提出 5-20 个要点供用户筛选
Testing 验证文档对读者清晰 提出读者可能的问题、检查歧义

4.2 消息构建策略

每次 AI 调用前精心构建上下文:

def build_messages(self, session, messages, document, user_input):
    ai_messages = [
        {"role": "system", "content": SYSTEM_PROMPT},  # 含 Skill 内容
    ]
    # 注入当前状态(阶段、轮次、完成度、文档全文)
    ai_messages.append({"role": "user", "content": f"""
**轮次**: {session.current_round}/{session.max_rounds}
**文档完成度**: {int(analysis["completeness"] * 100)}%
### 当前文档内容
```markdown
{document.content}
```"""})
    # 最近 10 条历史(滑动窗口)
    for msg in messages[-10:]:
        ai_messages.append({"role": msg.role.value, "content": msg.content})
    # 用户最新输入
    ai_messages.append({"role": "user", "content": user_input})
    return ai_messages

4.3 文档完成度分析

def analyze_content(self, content: str) -> dict:
    sections = [...]  # 检测 h1/h2/h3 章节
    has_placeholders = any(p in content for p in ["待补充", "tbd", "..."])
    completeness = 0.0
    if len(content) > 500:  completeness = 0.5
    if len(content) > 1000 and not has_placeholders: completeness = 0.7
    if len(content) > 2000 and len(sections) >= 3:   completeness = 0.9
    return {"sections": sections, "completeness": completeness, ...}

五、数据模型

5.1 实体关系

erDiagram
    Session ||--o{ Message : contains
    Session ||--|| Document : has
    Session {
        string id "UUID"
        enum status "draft / in_progress / completed"
        int current_round
        int max_rounds
    }
    Message {
        string id "UUID"
        enum role "user / assistant"
        string content
        string reasoning
        int round
    }
    Document {
        string id "UUID"
        string content "Markdown"
        int version
        object structure "8 模块完成度"
    }

5.2 部署描述 8 模块

Document 内嵌 DeploymentStructure,追踪每个模块完成度:

class DeploymentStructure(BaseModel):
    basic_info: ModuleStatus      # 服务名称、版本、描述
    image_config: ModuleStatus    # 镜像地址、标签、拉取策略
    resource_config: ModuleStatus # CPU/内存 requests/limits
    env_vars: ModuleStatus        # 环境变量、密钥引用
    network_config: ModuleStatus  # 端口、Service 类型、Ingress
    storage_config: ModuleStatus  # 挂载卷、持久化
    health_check: ModuleStatus    # 存活/就绪探针
    deploy_strategy: ModuleStatus # 副本数、滚动更新

5.3 本地存储

~/.ai-coauthor/
├── sessions/{id}.json    # 完整会话(session + messages + document)
├── index.json            # 会话索引
└── config.json           # 用户配置

六、API 与流式响应

6.1 核心端点

方法 端点 说明
POST /api/sessions 创建会话 + 初始引导
POST /api/sessions/stream 流式创建(SSE)
POST /api/sessions/{id}/chat 对话引导
POST /api/sessions/{id}/chat/stream 流式对话(SSE)
POST /api/sessions/{id}/complete 完成引导
GET /api/sessions/{id}/export 导出 .md 文件
GET /api/skills 技能列表
POST /api/skills/reload 热重载技能

6.2 SSE 流式实现

@api.post("/api/sessions/stream")
async def create_session_stream(request):
    async def generate():
        async for chunk in coauthor.generate_guidance_stream(...):
            if chunk["type"] == "content":
                yield f"data: {json.dumps(chunk)}\n\n".encode("utf-8")
                await asyncio.sleep(0)  # 强制 flush
            elif chunk["type"] == "done":
                storage.save_session(session, messages, document)
                yield f"data: {json.dumps(done_data)}\n\n".encode("utf-8")
    
    return StreamingResponse(generate(), media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})

七、前端设计

7.1 深色科技风

采用深色底 + 霓虹渐变的未来感设计:

  • 背景 #0a0a0f,霓虹渐变 #00f0ff → #bf5af2 → #ff2d55
  • 圆角 16px,字体 Space Grotesk + Noto Sans SC
  • 动效:标题呼吸光效、按钮光扫、消息滑入、脉冲加载点

7.2 三栏布局

前端采用输入-对话-预览三栏布局,配合流程指示器(输入文档 → 迭代校验 → 最终输出),核心交互 2-3 步完成:

  1. 左栏:Markdown 编辑器,支持文件上传和示例加载
  2. 中栏:AI 对话区,含快捷操作(确认/跳过/完成)
  3. 右栏:实时预览 + 源码模式切换 + 导出

7.3 特性开关

通过环境变量控制各功能的启用/禁用,离线时自动降级为纯编辑模式:

FEATURE_AI_GUIDANCE=true      # AI 引导
FEATURE_SHOW_REASONING=true   # 推理过程展示
FEATURE_LIVE_PREVIEW=true     # 实时预览
FEATURE_EXPORT=true           # 导出功能

八、总结

本文完整拆解了 AI Co-authoring 交互工作台的技术实现,核心创新在于 Skill Runtime 机制:

  1. 存储层:SKILL.md 标准化格式,frontmatter 定义元数据,body 承载领域知识
  2. 加载层:SkillLoader 启动时扫描、运行时热重载,自动探测关联资源
  3. 注册层:将 Skill 聚合为 load_skill Tool,大模型自主决定是否/何时加载
  4. 执行层:Tool Call 协议闭环——检测 finish_reason=tool_calls → 本地执行 → 消息重组 → 二次请求

这套机制让 AI 应用具备了可插拔的领域知识扩展能力:新增一个 skills/xxx/SKILL.md 文件,无需改任何代码,AI 就能在恰当时机自动加载并运用新知识。

Skill = 带上下文能力的 Tool + Prompt + 执行流程,是 Tool Calling 的工程化封装。

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付