一、引言
在云原生时代,编写高质量的部署描述文档是开发运维工作中不可或缺的一环。一份完整的 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)
两个关键设计点:
tool_call_id关联:每个 tool result 必须通过 ID 与原始 tool_call 一一对应,这是 OpenAI 协议强制要求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 步完成:
- 左栏:Markdown 编辑器,支持文件上传和示例加载
- 中栏:AI 对话区,含快捷操作(确认/跳过/完成)
- 右栏:实时预览 + 源码模式切换 + 导出
7.3 特性开关
通过环境变量控制各功能的启用/禁用,离线时自动降级为纯编辑模式:
FEATURE_AI_GUIDANCE=true # AI 引导
FEATURE_SHOW_REASONING=true # 推理过程展示
FEATURE_LIVE_PREVIEW=true # 实时预览
FEATURE_EXPORT=true # 导出功能
八、总结
本文完整拆解了 AI Co-authoring 交互工作台的技术实现,核心创新在于 Skill Runtime 机制:
- 存储层:SKILL.md 标准化格式,frontmatter 定义元数据,body 承载领域知识
- 加载层:SkillLoader 启动时扫描、运行时热重载,自动探测关联资源
- 注册层:将 Skill 聚合为
load_skillTool,大模型自主决定是否/何时加载 - 执行层:Tool Call 协议闭环——检测
finish_reason=tool_calls→ 本地执行 → 消息重组 → 二次请求
这套机制让 AI 应用具备了可插拔的领域知识扩展能力:新增一个 skills/xxx/SKILL.md 文件,无需改任何代码,AI 就能在恰当时机自动加载并运用新知识。
Skill = 带上下文能力的 Tool + Prompt + 执行流程,是 Tool Calling 的工程化封装。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付