<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Edward Chen</title><description>构建事物的笔记</description><link>https://heyedwardchen.com/</link><image><url>https://heyedwardchen.com/favicon.svg</url><title>Edward Chen</title><link>https://heyedwardchen.com/</link><width>32</width><height>32</height></image><atom:link href="https://heyedwardchen.com/api/rss.xml" rel="self" type="application/rss+xml" xmlns:atom="http://www.w3.org/2005/Atom"/><follow_challenge><feedId>1210651946751229952</feedId><userId>67422381549442048</userId></follow_challenge><item><title>OpenCLAW vs Hermes：多智能体框架深度对比</title><link>https://heyedwardchen.com/blog/openclaw-vs-hermes/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/openclaw-vs-hermes/</guid><description>从设计哲学、源码架构到实战场景：两个多智能体框架的系统化对比与选型指南</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;OpenCLAW vs Hermes：多智能体框架深度对比&lt;/h1&gt;
&lt;h2&gt;引言：为什么需要对比？&lt;/h2&gt;
&lt;h3&gt;1.1 多智能体框架的兴起&lt;/h3&gt;
&lt;p&gt;2024-2026 年，LLM 应用从单 Agent 向多 Agent 协作演进。早期应用（如 Chatbot）只需一个 LLM 处理用户请求，但复杂场景（如数据分析、工作流自动化）需要多个 Agent 分工协作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要框架？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免重复造轮子&lt;/strong&gt;：Agent 协作的通用模式（任务分解、状态管理、工具调用）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降低开发成本&lt;/strong&gt;：框架提供基础设施，开发者专注业务逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最佳实践沉淀&lt;/strong&gt;：框架封装了多 Agent 协作的经验和教训&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 对比的动机&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：我使用数个月，熟悉其能力边界。它是一个&lt;strong&gt;个人 AI 助手&lt;/strong&gt;，擅长即时交互、工具调用、知识管理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：新兴的多 Agent 框架，设计哲学与 Hermes 截然不同。它强调&lt;strong&gt;自主 Agent 协作&lt;/strong&gt;，适合复杂任务分解和企业级工作流。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对比价值&lt;/strong&gt;：帮助读者根据场景选择合适框架，避免&quot;用锤子找钉子&quot;。&lt;/p&gt;
&lt;h3&gt;1.3 对比维度&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;设计哲学&lt;/strong&gt;：以人为本 vs Agent 优先&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源码架构&lt;/strong&gt;：核心类、设计模式、关键实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能力边界&lt;/strong&gt;：擅长什么、不擅长什么&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：什么时候选哪个&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Hermes 框架深度解析&lt;/h2&gt;
&lt;h3&gt;2.1 设计哲学&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;以人为本&lt;/strong&gt;：Hermes 是 AI 助手，而非自主 Agent。人类始终在控制回路中，AI 辅助而非替代。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具优先&lt;/strong&gt;：内置丰富工具集（终端、文件、Web 搜索、MCP），Agent 是工具的使用者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持久化&lt;/strong&gt;：记忆（memory）、技能（skills）、定时任务（cron），支持长期协作。&lt;/p&gt;
&lt;h3&gt;2.2 核心架构&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;会话驱动&lt;/strong&gt;：基于对话的交互模式，用户输入 → Agent 处理 → 工具调用 → 返回结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技能系统&lt;/strong&gt;：SKILL.md 格式的可插拔能力，YAML frontmatter + Markdown 正文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;记忆机制&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;短期：会话历史（context window）&lt;/li&gt;
&lt;li&gt;长期：memory（事实）+ skills（流程）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工具集&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;终端：执行 shell 命令&lt;/li&gt;
&lt;li&gt;文件：读写、搜索、编辑&lt;/li&gt;
&lt;li&gt;Web：搜索、访问 API&lt;/li&gt;
&lt;li&gt;MCP：Model Context Protocol，扩展工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 能力边界&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;擅长&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;个人助理（日程、笔记、邮件）&lt;/li&gt;
&lt;li&gt;知识管理（Obsidian、Git、文档）&lt;/li&gt;
&lt;li&gt;自动化脚本（定时任务、监控告警）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;局限&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多 Agent 协作（仅支持 delegate_task，功能有限）&lt;/li&gt;
&lt;li&gt;复杂状态管理（无状态机，依赖会话历史）&lt;/li&gt;
&lt;li&gt;长期任务（无任务队列，依赖 cron）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 使用场景&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;个人知识管理&lt;/strong&gt;：Obsidian 同步、博客写作、知识检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自动化运维&lt;/strong&gt;：定时任务（cron）、监控告警、脚本执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术写作&lt;/strong&gt;：选题推荐、草稿生成、内容同步。&lt;/p&gt;
&lt;h3&gt;2.5 Hermes 源码解构&lt;/h3&gt;
&lt;h4&gt;2.5.1 核心类分析&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Agent&lt;/code&gt;&lt;/strong&gt;：主循环，处理用户输入和工具调用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 简化版 Hermes Agent 核心逻辑
class Agent:
    async def run(self, user_input: str) -&gt; str:
        # 1. 加载上下文（技能 + 记忆）
        context = self.load_context()
        
        # 2. 调用 LLM
        response = await self.llm.chat(
            messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: user_input}],
            tools=self.available_tools,
            context=context
        )
        
        # 3. 解析工具调用
        tool_calls = self.parse_tool_calls(response)
        
        # 4. 执行工具（支持并行）
        results = await self.execute_tools(tool_calls)
        
        # 5. 将结果回喂给 LLM
        final_response = await self.llm.chat(
            messages=[
                {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: user_input},
                {&quot;role&quot;: &quot;assistant&quot;, &quot;tool_calls&quot;: tool_calls},
                {&quot;role&quot;: &quot;tool&quot;, &quot;content&quot;: results}
            ]
        )
        
        return final_response.content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tool&lt;/code&gt;&lt;/strong&gt;：工具抽象，统一接口。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from abc import ABC, abstractmethod

class Tool(ABC):
    name: str
    description: str
    parameters: Dict  # JSON Schema
    
    @abstractmethod
    async def execute(self, **kwargs) -&gt; str:
        &quot;&quot;&quot;执行工具，返回结果&quot;&quot;&quot;
        pass

# 示例：终端工具
class TerminalTool(Tool):
    name = &quot;terminal&quot;
    description = &quot;执行 shell 命令&quot;
    
    async def execute(self, command: str, timeout: int = 180) -&gt; str:
        import subprocess
        result = subprocess.run(
            command, shell=True, capture_output=True, 
            text=True, timeout=timeout
        )
        return f&quot;stdout: {result.stdout}\nstderr: {result.stderr}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/strong&gt;：记忆管理。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Memory:
    def __init__(self):
        self.user_memory = []  # 用户偏好
        self.system_memory = []  # 系统笔记
    
    def add(self, target: str, content: str):
        &quot;&quot;&quot;添加记忆&quot;&quot;&quot;
        if target == &quot;user&quot;:
            self.user_memory.append(content)
        else:
            self.system_memory.append(content)
    
    def load_context(self) -&gt; str:
        &quot;&quot;&quot;加载上下文&quot;&quot;&quot;
        return f&quot;User Memory:\n{&apos;\n&apos;.join(self.user_memory)}\n\nSystem Memory:\n{&apos;\n&apos;.join(self.system_memory)}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;SkillLoader&lt;/code&gt;&lt;/strong&gt;：技能加载器。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import yaml
from pathlib import Path

class SkillLoader:
    def load_skill(self, name: str) -&gt; Dict:
        skill_path = Path(f&quot;~/.hermes/skills/{name}/SKILL.md&quot;).expanduser()
        content = skill_path.read_text()
        
        # 解析 frontmatter
        if content.startswith(&quot;---&quot;):
            end = content.find(&quot;---&quot;, 3)
            frontmatter = yaml.safe_load(content[3:end])
            body = content[end+3:].strip()
            return {**frontmatter, &quot;content&quot;: body}
        
        return {&quot;name&quot;: name, &quot;content&quot;: content}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.5.2 关键设计模式&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;命令模式&lt;/strong&gt;：工具调用作为命令对象，便于日志记录和重试。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ToolCommand:
    def __init__(self, tool: Tool, args: Dict):
        self.tool = tool
        self.args = args
    
    async def execute(self) -&gt; str:
        return await self.tool.execute(**self.args)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;策略模式&lt;/strong&gt;：不同的模型提供者（OpenAI/Anthropic/Custom）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ModelProvider(ABC):
    @abstractmethod
    async def chat(self, messages: List[Dict], tools: List[Dict]) -&gt; str:
        pass

class OpenAIProvider(ModelProvider):
    async def chat(self, messages, tools):
        # OpenAI API 实现
        pass

class AnthropicProvider(ModelProvider):
    async def chat(self, messages, tools):
        # Anthropic API 实现
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;观察者模式&lt;/strong&gt;：工具调用的事件通知。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ToolEventBus:
    def __init__(self):
        self.listeners = []
    
    def on_tool_call(self, listener):
        self.listeners.append(listener)
    
    async def notify(self, tool_name: str, args: Dict, result: str):
        for listener in self.listeners:
            await listener(tool_name, args, result)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;OpenCLAW 框架深度解析&lt;/h2&gt;
&lt;h3&gt;3.1 设计哲学&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Agent 优先&lt;/strong&gt;：自主 Agent 协作，人类只需定义目标，Agent 自动分解和执行任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;状态机驱动&lt;/strong&gt;：明确的状态流转（待处理→执行中→完成），支持复杂任务管理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;：插件化设计，支持自定义 Agent 类型和协作策略。&lt;/p&gt;
&lt;h3&gt;3.2 核心架构&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;多 Agent 协作&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主管 - 工人模式（Orchestrator-Worker）&lt;/li&gt;
&lt;li&gt;投票共识（Voting）&lt;/li&gt;
&lt;li&gt;流水线协作（Pipeline）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;状态管理&lt;/strong&gt;：基于状态机的任务流转，支持断点续传。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具调用&lt;/strong&gt;：类似 LangChain 的工具抽象，需自行实现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持久化&lt;/strong&gt;：任务状态、执行历史，支持恢复和审计。&lt;/p&gt;
&lt;h3&gt;3.3 能力边界&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;擅长&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂任务分解（自动拆分为子任务）&lt;/li&gt;
&lt;li&gt;多 Agent 协作（主管 - 工人、投票）&lt;/li&gt;
&lt;li&gt;长期任务（任务队列、状态持久化）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;局限&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;个人助理场景（缺乏即时交互）&lt;/li&gt;
&lt;li&gt;工具丰富度（需自行实现）&lt;/li&gt;
&lt;li&gt;学习曲线（需理解状态机、多 Agent 模式）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4 使用场景&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;企业级工作流自动化&lt;/strong&gt;：需要多角色协作的复杂流程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;复杂数据分析任务&lt;/strong&gt;：多步骤分析，需要中间状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要多角色协作的场景&lt;/strong&gt;：如代码审查（作者 + 审查者 + 合并者）。&lt;/p&gt;
&lt;h3&gt;3.5 OpenCLAW 源码解构&lt;/h3&gt;
&lt;h4&gt;3.5.1 核心类分析&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Agent&lt;/code&gt; 基类&lt;/strong&gt;：定义 Agent 的接口。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 简化版 OpenCLAW Agent 基类
from abc import ABC, abstractmethod
from typing import List, Dict

class Agent(ABC):
    name: str
    role: str
    tools: List[Tool]
    
    @abstractmethod
    def plan(self, task: str) -&gt; Plan:
        &quot;&quot;&quot;制定计划&quot;&quot;&quot;
        pass
    
    @abstractmethod
    def execute(self, plan: Plan) -&gt; Result:
        &quot;&quot;&quot;执行计划&quot;&quot;&quot;
        pass
    
    @abstractmethod
    def communicate(self, message: str, from_agent: str) -&gt; str:
        &quot;&quot;&quot;与其他 Agent 通信&quot;&quot;&quot;
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Orchestrator&lt;/code&gt;&lt;/strong&gt;：主管 Agent，负责任务分解和分配。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Orchestrator(Agent):
    def __init__(self, workers: List[Agent]):
        super().__init__(name=&quot;orchestrator&quot;, role=&quot;task_manager&quot;)
        self.workers = workers
    
    def decompose(self, task: str) -&gt; List[SubTask]:
        &quot;&quot;&quot;将大任务分解为子任务&quot;&quot;&quot;
        # 使用 LLM 分解任务
        plan_prompt = f&quot;&quot;&quot;
        任务：{task}
        
        请将任务分解为多个子任务，每个子任务应：
        1. 可独立执行
        2. 有明确的输入输出
        3. 可分配给不同 Agent
        
        输出格式：JSON 数组
        &quot;&quot;&quot;
        subtasks = self.llm.generate(plan_prompt)
        return [SubTask(**st) for st in subtasks]
    
    def assign(self, tasks: List[SubTask]):
        &quot;&quot;&quot;将子任务分配给工人 Agent&quot;&quot;&quot;
        for task in tasks:
            # 根据任务类型选择最合适的 Agent
            worker = self.select_worker(task)
            worker.execute_task(task)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Worker&lt;/code&gt;&lt;/strong&gt;：工人 Agent，负责执行具体子任务。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Worker(Agent):
    def __init__(self, name: str, specialty: str):
        super().__init__(name=name, role=specialty)
        self.specialty = specialty
    
    def can_handle(self, task: SubTask) -&gt; bool:
        &quot;&quot;&quot;判断是否能处理该任务&quot;&quot;&quot;
        return task.type == self.specialty
    
    def execute_task(self, task: SubTask) -&gt; Result:
        &quot;&quot;&quot;执行任务&quot;&quot;&quot;
        # 1. 制定计划
        plan = self.plan(task.description)
        
        # 2. 执行工具调用
        results = []
        for step in plan.steps:
            if step.requires_tool:
                result = self.execute_tool(step.tool, step.args)
                results.append(result)
        
        # 3. 返回结果
        return Result(
            task_id=task.id,
            status=&quot;completed&quot;,
            output=&quot;\n&quot;.join(results)
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;State&lt;/code&gt;&lt;/strong&gt;：状态管理。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from enum import Enum

class TaskStatus(Enum):
    PENDING = &quot;pending&quot;
    IN_PROGRESS = &quot;in_progress&quot;
    COMPLETED = &quot;completed&quot;
    FAILED = &quot;failed&quot;

class TaskState:
    def __init__(self):
        self.tasks: Dict[str, Dict] = {}
    
    def create_task(self, task_id: str, description: str) -&gt; Dict:
        &quot;&quot;&quot;创建任务&quot;&quot;&quot;
        task = {
            &quot;id&quot;: task_id,
            &quot;description&quot;: description,
            &quot;status&quot;: TaskStatus.PENDING,
            &quot;subtasks&quot;: [],
            &quot;history&quot;: []
        }
        self.tasks[task_id] = task
        return task
    
    def update_status(self, task_id: str, status: TaskStatus):
        &quot;&quot;&quot;更新任务状态&quot;&quot;&quot;
        task = self.tasks[task_id]
        task[&quot;status&quot;] = status
        task[&quot;history&quot;].append({
            &quot;timestamp&quot;: datetime.now(),
            &quot;status&quot;: status.value
        })
    
    def get_task(self, task_id: str) -&gt; Dict:
        &quot;&quot;&quot;获取任务状态&quot;&quot;&quot;
        return self.tasks.get(task_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.5.2 关键设计模式&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;工厂模式&lt;/strong&gt;：创建不同类型的 Agent。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class AgentFactory:
    @staticmethod
    def create_agent(agent_type: str, **kwargs) -&gt; Agent:
        if agent_type == &quot;orchestrator&quot;:
            return Orchestrator(**kwargs)
        elif agent_type == &quot;worker&quot;:
            return Worker(**kwargs)
        elif agent_type == &quot;voter&quot;:
            return VoterAgent(**kwargs)
        else:
            raise ValueError(f&quot;Unknown agent type: {agent_type}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;观察者模式&lt;/strong&gt;：Agent 间的事件通知。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class AgentEventBus:
    def __init__(self):
        self.subscribers: Dict[str, List[Callable]] = {}
    
    def subscribe(self, event_type: str, callback: Callable):
        &quot;&quot;&quot;订阅事件&quot;&quot;&quot;
        if event_type not in self.subscribers:
            self.subscribers[event_type] = []
        self.subscribers[event_type].append(callback)
    
    async def publish(self, event_type: str, data: Dict):
        &quot;&quot;&quot;发布事件&quot;&quot;&quot;
        for callback in self.subscribers.get(event_type, []):
            await callback(data)

# 使用示例
event_bus = AgentEventBus()

async def on_task_completed(data):
    print(f&quot;Task {data[&apos;task_id&apos;]} completed!&quot;)

event_bus.subscribe(&quot;task.completed&quot;, on_task_completed)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;策略模式&lt;/strong&gt;：不同的协作策略。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class CollaborationStrategy(ABC):
    @abstractmethod
    async def collaborate(self, agents: List[Agent], task: str) -&gt; Result:
        pass

class OrchestratorStrategy(CollaborationStrategy):
    async def collaborate(self, agents, task):
        &quot;&quot;&quot;主管 - 工人模式&quot;&quot;&quot;
        orchestrator = agents[0]
        workers = agents[1:]
        return await orchestrator.execute(task, workers)

class VotingStrategy(CollaborationStrategy):
    async def collaborate(self, agents, task):
        &quot;&quot;&quot;投票共识模式&quot;&quot;&quot;
        results = await asyncio.gather(*[
            agent.execute(task) for agent in agents
        ])
        return self.majority_vote(results)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;底层逻辑对比&lt;/h2&gt;
&lt;h3&gt;4.1 核心抽象差异&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;code&gt;User ↔ Agent ↔ Tools&lt;/code&gt;（单 Agent 多工具）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个 Agent，多个工具&lt;/li&gt;
&lt;li&gt;人类在控制回路中&lt;/li&gt;
&lt;li&gt;即时交互，对话式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;code&gt;Orchestrator ↔ Workers ↔ Tools&lt;/code&gt;（多 Agent 协作）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个 Agent，分工协作&lt;/li&gt;
&lt;li&gt;人类只需定义目标&lt;/li&gt;
&lt;li&gt;任务队列，异步执行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 状态管理对比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会话状态：消息历史（context window）&lt;/li&gt;
&lt;li&gt;持久记忆：memory（事实）+ skills（流程）&lt;/li&gt;
&lt;li&gt;无状态机，依赖 LLM 理解上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务状态：状态机（PENDING → IN_PROGRESS → COMPLETED）&lt;/li&gt;
&lt;li&gt;执行历史：任务日志、子任务状态&lt;/li&gt;
&lt;li&gt;支持断点续传、任务恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3 工具系统对比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内置工具丰富：terminal/file/web_search/MCP&lt;/li&gt;
&lt;li&gt;工具即服务：无需自行实现&lt;/li&gt;
&lt;li&gt;扩展方式：MCP 协议、自定义工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;工具抽象类似 LangChain&lt;/li&gt;
&lt;li&gt;需自行实现工具逻辑&lt;/li&gt;
&lt;li&gt;扩展方式：继承 Tool 基类&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4 扩展机制对比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：SKILL.md（Markdown + YAML frontmatter）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
name: git-automation
title: Git 自动化
description: 自动化 Git 工作流
---

# Git 自动化技能

## 能力范围
- 提交代码
- 推送远程
- 创建分支

## 工具调用
- `git_commit(message)`
- `git_push(remote, branch)`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：Python 类继承（需编码）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class GitAutomationAgent(Worker):
    def __init__(self):
        super().__init__(name=&quot;git-automation&quot;, specialty=&quot;git&quot;)
    
    def execute_task(self, task: SubTask) -&gt; Result:
        if task.type == &quot;commit&quot;:
            return self.git_commit(task.message)
        elif task.type == &quot;push&quot;:
            return self.git_push(task.remote, task.branch)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.5 设计哲学差异&lt;/h3&gt;
&lt;p&gt;| 维度 | Hermes | OpenCLAW |
|------|--------|----------|
| &lt;strong&gt;核心理念&lt;/strong&gt; | Human-in-the-loop | Autonomous |
| &lt;strong&gt;交互模式&lt;/strong&gt; | 对话式（即时） | 任务队列（异步） |
| &lt;strong&gt;状态管理&lt;/strong&gt; | 会话历史 | 状态机 |
| &lt;strong&gt;工具系统&lt;/strong&gt; | 内置丰富 | 需自行实现 |
| &lt;strong&gt;扩展方式&lt;/strong&gt; | SKILL.md（无代码） | Python 类（需编码） |
| &lt;strong&gt;适用场景&lt;/strong&gt; | 个人助理、即时交互 | 企业工作流、复杂任务 |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;能力对比矩阵&lt;/h2&gt;
&lt;h3&gt;5.1 功能对比表&lt;/h3&gt;
&lt;p&gt;| 能力 | Hermes | OpenCLAW |
|------|--------|----------|
| &lt;strong&gt;人机交互&lt;/strong&gt; | ✅ 优秀（对话式） | ⚠️ 有限（任务队列） |
| &lt;strong&gt;多 Agent 协作&lt;/strong&gt; | ⚠️ 有限（delegate_task） | ✅ 优秀（主管 - 工人/投票） |
| &lt;strong&gt;状态管理&lt;/strong&gt; | ⚠️ 简单（会话历史） | ✅ 复杂（状态机） |
| &lt;strong&gt;工具丰富度&lt;/strong&gt; | ✅ 丰富（内置 + MCP） | ⚠️ 中等（需自行实现） |
| &lt;strong&gt;持久化&lt;/strong&gt; | ✅ 完善（memory/skills） | ✅ 完善（任务状态） |
| &lt;strong&gt;自动化&lt;/strong&gt; | ✅ 定时任务（cron） | ⚠️ 需外部触发 |
| &lt;strong&gt;学习曲线&lt;/strong&gt; | 上手快，精通需时间 | 上手慢，精通后灵活 |
| &lt;strong&gt;扩展性&lt;/strong&gt; | SKILL.md（无代码） | Python 类（需编码） |&lt;/p&gt;
&lt;h3&gt;5.2 性能对比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;响应速度&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hermes：即时（对话式，单次 LLM 调用）&lt;/li&gt;
&lt;li&gt;OpenCLAW：较慢（任务队列，多次 LLM 调用）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;资源消耗&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hermes：轻量（单 Agent，少状态）&lt;/li&gt;
&lt;li&gt;OpenCLAW：较重（多 Agent，状态持久化）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;扩展性&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hermes：技能系统（无代码，易上手）&lt;/li&gt;
&lt;li&gt;OpenCLAW：插件系统（需编码，灵活）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 学习曲线&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上手快：对话式交互，无需编程&lt;/li&gt;
&lt;li&gt;精通需时间：理解技能系统、记忆机制、工具调用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上手慢：需理解状态机、多 Agent 模式、Python 编程&lt;/li&gt;
&lt;li&gt;精通后灵活：可自定义 Agent 类型、协作策略、工具系统&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;实战场景对比&lt;/h2&gt;
&lt;h3&gt;6.1 场景 1：个人知识管理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：Obsidian 同步、博客写作、知识检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 即时交互：随时询问、检索、同步&lt;/li&gt;
&lt;li&gt;✅ 工具丰富：文件读写、Git 操作、Web 搜索&lt;/li&gt;
&lt;li&gt;✅ 记忆持久：记住用户偏好、写作风格&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ 不适合：需要持续的人机交互，而非任务队列&lt;/li&gt;
&lt;li&gt;❌ 工具有限：需自行实现文件、Git 工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;推荐&lt;/strong&gt;：Hermes&lt;/p&gt;
&lt;h3&gt;6.2 场景 2：自动化运维&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：定时任务、监控告警、脚本执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 定时任务：cron 支持，自动执行&lt;/li&gt;
&lt;li&gt;✅ 工具丰富：terminal/file/web_search&lt;/li&gt;
&lt;li&gt;✅ 即时反馈：执行结果即时返回&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚠️ 需外部触发：无内置定时任务&lt;/li&gt;
&lt;li&gt;✅ 复杂流程：多步骤任务分解&lt;/li&gt;
&lt;li&gt;⚠️ 异步执行：任务队列，非即时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;推荐&lt;/strong&gt;：简单用 Hermes，复杂用 OpenCLAW&lt;/p&gt;
&lt;h3&gt;6.3 场景 3：数据分析&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：多步骤分析，需要中间状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 单次分析：即时查询、简单统计&lt;/li&gt;
&lt;li&gt;❌ 多步骤：依赖会话历史，易丢失状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 多步骤：状态机管理，支持中间状态&lt;/li&gt;
&lt;li&gt;✅ 任务分解：自动拆分为子任务（数据清洗→分析→可视化）&lt;/li&gt;
&lt;li&gt;✅ 持久化：任务状态可恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;推荐&lt;/strong&gt;：OpenCLAW&lt;/p&gt;
&lt;h3&gt;6.4 场景 4：企业工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：多角色协作（如代码审查：作者→审查者→合并者）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ 不适合：缺乏多 Agent 协作能力&lt;/li&gt;
&lt;li&gt;❌ 无状态机：无法管理复杂流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 多 Agent：主管 - 工人模式&lt;/li&gt;
&lt;li&gt;✅ 状态管理：明确的状态流转&lt;/li&gt;
&lt;li&gt;✅ 可扩展：自定义 Agent 类型（作者/审查者/合并者）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;推荐&lt;/strong&gt;：OpenCLAW&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;选型决策树&lt;/h2&gt;
&lt;h3&gt;7.1 决策流程图&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;需要多 Agent 协作？
├─ 是 → OpenCLAW
└─ 否 → 需要即时交互？
    ├─ 是 → Hermes
    └─ 否 → 需要复杂状态管理？
        ├─ 是 → OpenCLAW
        └─ 否 → Hermes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 关键问题清单&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;是否需要多 Agent 协作？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是 → OpenCLAW&lt;/li&gt;
&lt;li&gt;否 → 继续&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;是否需要即时交互（而非任务队列）？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是 → Hermes&lt;/li&gt;
&lt;li&gt;否 → 继续&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;是否需要复杂状态管理（状态机）？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是 → OpenCLAW&lt;/li&gt;
&lt;li&gt;否 → 继续&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;是否需要丰富的内置工具？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是 → Hermes&lt;/li&gt;
&lt;li&gt;否 → 继续&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;是否需要定时任务/自动化？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是 → Hermes&lt;/li&gt;
&lt;li&gt;否 → 两者皆可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;7.3 混合方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes + OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hermes 做日常助理（即时交互、工具调用）&lt;/li&gt;
&lt;li&gt;OpenCLAW 处理复杂任务（多 Agent 协作、状态管理）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Hermes + LangGraph&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hermes 做交互（对话式、工具调用）&lt;/li&gt;
&lt;li&gt;LangGraph 做状态管理（状态机、任务流转）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;总结与建议&lt;/h2&gt;
&lt;h3&gt;8.1 核心结论&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 个人助理、即时交互、工具丰富&lt;/li&gt;
&lt;li&gt;✅ 上手快、无代码扩展、定时任务&lt;/li&gt;
&lt;li&gt;❌ 多 Agent 协作有限、状态管理简单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 多 Agent 协作、状态管理、企业级&lt;/li&gt;
&lt;li&gt;✅ 灵活扩展、任务持久化、复杂流程&lt;/li&gt;
&lt;li&gt;❌ 上手慢、需编程、工具需自行实现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.2 学习建议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;初学者&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 Hermes 开始，理解 Agent 基础&lt;/li&gt;
&lt;li&gt;熟悉工具调用、技能系统、记忆机制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;进阶者&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学习 OpenCLAW，掌握多 Agent 协作&lt;/li&gt;
&lt;li&gt;理解状态机、任务分解、协作策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;专家&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据场景选择，甚至混合使用&lt;/li&gt;
&lt;li&gt;自定义框架，结合两者优势&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 未来展望&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可能增强多 Agent 协作能力（delegate_task 升级）&lt;/li&gt;
&lt;li&gt;可能引入状态机（复杂任务管理）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可能增强人机交互体验（对话式接口）&lt;/li&gt;
&lt;li&gt;可能内置更多工具（减少自行实现）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;融合趋势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两者的设计思想可能互相借鉴&lt;/li&gt;
&lt;li&gt;未来可能出现&quot;对话式多 Agent&quot;框架&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;资源与延伸阅读&lt;/h2&gt;
&lt;h3&gt;9.1 官方文档&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：https://hermes-agent.nousresearch.com/docs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：https://github.com/open-claw/open-claw&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.2 相关博文&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/lightweight-agent-skill-system/&quot;&gt;轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/langgraph-state-machine-patterns/&quot;&gt;LangGraph 复杂状态机设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/llm-evaluation-system/&quot;&gt;LLM 应用评估体系设计：指标、工具与自动化&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.3 社区与讨论&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hermes&lt;/strong&gt;：Discord 社区、GitHub Issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenCLAW&lt;/strong&gt;：GitHub Discussions、技术论坛&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;相关文章&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/lightweight-agent-skill-system/&quot;&gt;轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/langgraph-state-machine-patterns/&quot;&gt;LangGraph 复杂状态机设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/llm-evaluation-system/&quot;&gt;LLM 应用评估体系设计：指标、工具与自动化&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>OpenCLAW</category><category>Hermes</category><category>多智能体</category><category>框架对比</category><category>Agent</category><category>技术选型</category></item><item><title>LLM 应用评估体系设计：指标、工具与自动化</title><link>https://heyedwardchen.com/blog/llm-evaluation-system/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/llm-evaluation-system/</guid><description>从 Prompt 到 RAG 到 Agent：分层级、可量化、自动化的 LLM 应用验收标准与测试框架</description><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;LLM 应用评估体系设计：指标、工具与自动化&lt;/h1&gt;
&lt;h2&gt;引言：为什么需要专门的评估体系&lt;/h2&gt;
&lt;h3&gt;1.1 LLM 应用与传统软件的根本差异&lt;/h3&gt;
&lt;p&gt;传统软件测试有一个基本假设：&lt;strong&gt;相同输入必然得到相同输出&lt;/strong&gt;。但 LLM 应用打破了这个假设：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非确定性输出&lt;/strong&gt;：即使输入完全相同，LLM 也可能给出不同回答。温度参数（temperature）控制随机性，但即使 temperature=0，不同模型、不同版本、甚至同一模型的不同调用，输出也可能有细微差异。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;概率性错误&lt;/strong&gt;：传统软件是&quot;对/错&quot;二元判断，LLM 是&quot;好/更好/最好&quot;的连续谱。一个回答可能&quot;基本正确但有细节错误&quot;，或者&quot;方向正确但表述不清&quot;。如何量化这种模糊性？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上下文依赖&lt;/strong&gt;：LLM 的效果受历史对话、系统提示、few-shot 示例等多重因素影响。单独测试某个 Prompt 可能效果很好，但放到完整对话流程中就可能失效。&lt;/p&gt;
&lt;h3&gt;1.2 缺少验收标准的后果&lt;/h3&gt;
&lt;p&gt;我们团队在 AskBI 项目中踩过这些坑：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上线即翻车&lt;/strong&gt;：测试环境用 50 个精心设计的问答对，准确率 95%。上线后真实用户提问，准确率掉到 70%。原因：测试数据没有覆盖真实场景的多样性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无法量化改进&lt;/strong&gt;：优化 Prompt 后，产品说&quot;感觉更好了&quot;，但无法证明。A/B 测试需要 2 周才能看到统计显著性，迭代太慢。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;责任边界模糊&lt;/strong&gt;：效果差时，模型团队说是数据问题，数据团队说是工程问题，工程团队说是模型问题。没有分层指标，无法定位问题层级。&lt;/p&gt;
&lt;h3&gt;1.3 评估体系的核心目标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;可量化&lt;/strong&gt;：每个层级都有明确指标，不是&quot;感觉好&quot;而是&quot;准确率达到 92%&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可复现&lt;/strong&gt;：测试结果可以重复验证，不是&quot;今天好明天差&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可自动化&lt;/strong&gt;：减少人工评估成本，支持持续集成和快速迭代。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;评估体系设计原则&lt;/h2&gt;
&lt;h3&gt;2.1 分层评估&lt;/h3&gt;
&lt;p&gt;LLM 应用通常有 3 个层级，每层关注点不同：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prompt 层&lt;/strong&gt;：单个交互的质量。关注回答的准确性、相关性、完整性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RAG 层&lt;/strong&gt;：检索与生成的协同。关注检索结果的召回率、生成内容的忠实度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent 层&lt;/strong&gt;：任务完成与工具调用。关注任务成功率、工具选择准确率、状态管理。&lt;/p&gt;
&lt;h3&gt;2.2 指标设计原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;可测量&lt;/strong&gt;：必须有明确的计算方法，不能是主观判断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有意义&lt;/strong&gt;：与业务目标对齐，不是&quot;为了测而测&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可行动&lt;/strong&gt;：指标异常时知道如何改进，不是&quot;只能看不能改&quot;。&lt;/p&gt;
&lt;h3&gt;2.3 测试数据设计&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;基准数据集&lt;/strong&gt;：覆盖典型场景的固定测试集，用于版本对比。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;边界案例&lt;/strong&gt;：极端输入、模糊需求、对抗样本，用于压力测试。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真实数据&lt;/strong&gt;：生产环境采样，持续更新，用于发现长尾问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Prompt 层级的评估指标&lt;/h2&gt;
&lt;h3&gt;3.1 核心指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;准确率（Accuracy）&lt;/strong&gt;：回答正确的比例。适合有明确答案的场景（如数学题、事实查询）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关性（Relevance）&lt;/strong&gt;：回答与问题的相关程度，通常用 1-5 分人工评分。适合开放性问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整性（Completeness）&lt;/strong&gt;：是否覆盖问题的所有子问题。适合复杂问题（如&quot;分析 A 并给出 B 建议&quot;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一致性（Consistency）&lt;/strong&gt;：多次调用的输出稳定性。相同问题调用 5 次，输出语义一致的比例。&lt;/p&gt;
&lt;h3&gt;3.2 测试方法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;黄金测试集&lt;/strong&gt;：准备 50-100 个标准问答对，覆盖典型场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;人工评分&lt;/strong&gt;：3 人独立评分，取平均值，减少个人偏差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自动化评估&lt;/strong&gt;：用另一个 LLM 作为评判模型（LLM-as-a-Judge），但需要定期人工抽检校准。&lt;/p&gt;
&lt;h3&gt;3.3 合格标准示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;准确率 ≥ 90%
相关性 ≥ 4.0/5.0
完整性 ≥ 85%
一致性 ≥ 80%（相同问题 5 次调用，输出语义一致的比例）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 常见问题与调优方向&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;准确率低&lt;/strong&gt; → 优化系统提示、增加 Few-shot 示例、调整模型温度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关性低&lt;/strong&gt; → 调整温度参数、优化问题重写、增加上下文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整性低&lt;/strong&gt; → 在 Prompt 中明确要求&quot;分点回答&quot;、&quot;覆盖所有子问题&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一致性低&lt;/strong&gt; → 降低温度（如 0.1）、增加随机种子控制、简化输出格式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;RAG 层级的评估指标&lt;/h2&gt;
&lt;h3&gt;4.1 检索质量指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;召回率（Recall@K）&lt;/strong&gt;：正确答案是否在前 K 个检索结果中。K 通常取 5 或 10。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;精确率（Precision@K）&lt;/strong&gt;：前 K 个结果中有多少是相关的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MRR（Mean Reciprocal Rank）&lt;/strong&gt;：正确答案的排名倒数平均值。排名越靠前，分数越高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NDCG（Normalized DCG）&lt;/strong&gt;：考虑排名位置的加权评分，排名越靠前权重越大。&lt;/p&gt;
&lt;h3&gt;4.2 生成质量指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;忠实度（Faithfulness）&lt;/strong&gt;：生成内容是否基于检索到的上下文，不编造信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;答案相关性（Answer Relevance）&lt;/strong&gt;：最终答案与问题的相关程度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上下文利用率（Context Utilization）&lt;/strong&gt;：检索内容被引用的比例，避免检索了大量但只用了一部分。&lt;/p&gt;
&lt;h3&gt;4.3 端到端指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;答案准确率&lt;/strong&gt;：最终答案是否正确，综合检索和生成的效果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拒绝率&lt;/strong&gt;：无法回答时的拒绝比例。应控制在合理范围（如 5-10%），太高说明检索能力不足，太低可能产生幻觉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;幻觉率&lt;/strong&gt;：生成内容中事实错误的比例，应尽可能低（&amp;#x3C; 5%）。&lt;/p&gt;
&lt;h3&gt;4.4 测试方法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;检索测试&lt;/strong&gt;：固定问题集，评估检索结果质量，不关心生成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;生成测试&lt;/strong&gt;：固定检索结果，评估生成质量，不关心检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;端到端测试&lt;/strong&gt;：完整流程，评估最终答案，反映真实效果。&lt;/p&gt;
&lt;h3&gt;4.5 合格标准示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Recall@5 ≥ 95%
Precision@3 ≥ 80%
MRR ≥ 0.85
忠实度 ≥ 90%
幻觉率 ≤ 5%
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.6 常见问题与调优方向&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;召回率低&lt;/strong&gt; → 优化分块策略（chunk size）、增加嵌入维度、调整相似度阈值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;精确率低&lt;/strong&gt; → 优化重排序（Re-ranking）、增加元数据过滤、调整 Top-K。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;忠实度低&lt;/strong&gt; → 在 Prompt 中强调&quot;仅基于上下文回答&quot;、&quot;不知道就说不知道&quot;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;幻觉率高 → 增加引用要求（&quot;请标注信息来源&quot;）、设置置信度阈值、过滤低分结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Agent 层级的评估指标&lt;/h2&gt;
&lt;h3&gt;5.1 任务完成指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;任务成功率（Task Success Rate）&lt;/strong&gt;：任务最终完成的比例。Agent 的核心指标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤准确率（Step Accuracy）&lt;/strong&gt;：每一步工具调用的正确率。定位哪一步容易出错。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平均步骤数（Average Steps）&lt;/strong&gt;：完成任务所需的平均工具调用次数。越少越好，但不应以牺牲成功率为代价。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;超时率（Timeout Rate）&lt;/strong&gt;：超过最大步骤数仍未完成的比例。反映任务复杂度或 Agent 规划能力。&lt;/p&gt;
&lt;h3&gt;5.2 工具调用指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;工具选择准确率&lt;/strong&gt;：选择正确工具的比例。Agent 需要理解&quot;用什么工具做什么事&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数准确率&lt;/strong&gt;：工具参数的正确率。选对工具但参数错误，任务仍会失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具执行成功率&lt;/strong&gt;：工具调用成功（无异常）的比例。反映工具稳定性和参数格式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;冗余调用率&lt;/strong&gt;：不必要的工具调用比例。如重复查询、无效尝试。&lt;/p&gt;
&lt;h3&gt;5.3 状态管理指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;状态一致性&lt;/strong&gt;：多轮对话中状态维护的正确性。如&quot;记住用户之前选的选项&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;错误恢复率&lt;/strong&gt;：遇到错误后成功恢复的比例。反映容错能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;人工介入率&lt;/strong&gt;：需要人类协助的比例。越低越好，但关键任务需要人工确认。&lt;/p&gt;
&lt;h3&gt;5.4 测试方法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景测试&lt;/strong&gt;：设计典型任务场景（如&quot;创建环境并部署&quot;），覆盖完整流程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;压力测试&lt;/strong&gt;：复杂任务、多步骤、边界条件，测试极限能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对抗测试&lt;/strong&gt;：故意提供错误信息、模糊需求，测试鲁棒性。&lt;/p&gt;
&lt;h3&gt;5.5 合格标准示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;任务成功率 ≥ 85%
步骤准确率 ≥ 90%
平均步骤数 ≤ 预期步骤数 × 1.5
超时率 ≤ 10%
工具选择准确率 ≥ 95%
人工介入率 ≤ 15%
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.6 常见问题与调优方向&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;任务成功率低&lt;/strong&gt; → 优化技能设计（更清晰的工具描述）、增加错误处理、降低任务复杂度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤准确率低&lt;/strong&gt; → 改进工具描述（更详细的参数说明）、增加 Few-shot 示例、优化系统提示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;冗余调用高&lt;/strong&gt; → 优化系统提示（&quot;避免重复调用&quot;）、增加状态记忆、合并相似工具。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;人工介入率高&lt;/strong&gt; → 明确任务边界（&quot;哪些能做哪些不能&quot;）、增加确认环节（关键操作前让用户确认）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;自动化测试框架设计&lt;/h2&gt;
&lt;h3&gt;6.1 框架架构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;测试数据层（JSON/CSV）
    ↓
测试执行层（并发调用、结果收集）
    ↓
评估层（指标计算、阈值判断）
    ↓
报告层（可视化、趋势分析）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 核心组件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;测试数据管理&lt;/strong&gt;：版本化（Git）、分类（Prompt/RAG/Agent）、标签（场景/难度）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试执行器&lt;/strong&gt;：支持并发（提高速度）、限流（避免超限）、重试（处理临时失败）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;评估引擎&lt;/strong&gt;：指标计算（准确率/召回率等）、阈值判断（是否合格）、异常告警。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;报告生成器&lt;/strong&gt;：HTML/PDF 报告、图表可视化（趋势图/分布图）、邮件通知。&lt;/p&gt;
&lt;h3&gt;6.3 实现示例（Python）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from dataclasses import dataclass
from typing import List, Dict
import json

@dataclass
class TestCase:
    id: str
    input: str
    expected: str  # 期望输出或评估标准
    layer: str  # prompt/rag/agent
    tags: List[str]

class LLMTestSuite:
    def __init__(self, test_data_path: str, model_config: Dict):
        self.test_cases = self.load_test_cases(test_data_path)
        self.model = LLMClient(model_config)
        
    def load_test_cases(self, path: str) -&gt; List[TestCase]:
        with open(path) as f:
            data = json.load(f)
        return [TestCase(**case) for case in data]
    
    def run_prompt_tests(self) -&gt; Dict:
        results = []
        for case in self.filter_by_layer(&apos;prompt&apos;):
            output = self.model.generate(case.input)
            score = self.evaluate_prompt_output(output, case.expected)
            results.append({
                &apos;case_id&apos;: case.id,
                &apos;score&apos;: score,
                &apos;output&apos;: output
            })
        return self.calculate_metrics(results)
    
    def evaluate_prompt_output(self, output: str, expected: str) -&gt; float:
        # 可以用另一个 LLM 作为评判模型
        judge_prompt = f&quot;&quot;&quot;
        问题：{expected[&apos;question&apos;]}
        期望答案：{expected[&apos;answer&apos;]}
        实际输出：{output}
        
        请评分（0-1 分）：
        &quot;&quot;&quot;
        judge_output = self.judge_model.generate(judge_prompt)
        return self.extract_score(judge_output)
    
    def calculate_metrics(self, results: List[Dict]) -&gt; Dict:
        scores = [r[&apos;score&apos;] for r in results]
        return {
            &apos;accuracy&apos;: sum(scores) / len(scores),
            &apos;pass_rate&apos;: sum(1 for s in scores if s &gt;= 0.8) / len(scores),
            &apos;total_cases&apos;: len(results)
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.4 持续集成&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Git Hook&lt;/strong&gt;：提交前运行快速测试（如 10 个核心用例），防止明显回归。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CI Pipeline&lt;/strong&gt;：每次 PR 运行完整测试（如 50-100 个用例），生成报告附在 PR 中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定时任务&lt;/strong&gt;：每日/每周运行全量测试（如 200+ 用例），跟踪长期趋势，发现退化。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;工具链与资源&lt;/h2&gt;
&lt;h3&gt;7.1 开源工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;RAGAS&lt;/strong&gt;：RAG 评估框架，提供忠实度、相关性、召回率等指标。支持自动化评估。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TruLens&lt;/strong&gt;：LLM 应用评估平台，支持多指标、可视化、A/B 测试。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LangSmith&lt;/strong&gt;：LangChain 官方评估工具，集成度高，支持追踪和调试。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DeepEval&lt;/strong&gt;：通用 LLM 评估库，支持多种指标和自定义评估函数。&lt;/p&gt;
&lt;h3&gt;7.2 自建工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;基准数据集管理&lt;/strong&gt;：版本化（Git）、标注工具（Label Studio）、去重和平衡。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自动化评分器&lt;/strong&gt;：LLM-as-a-Judge 实现，定期人工校准，避免评判模型偏差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可视化看板&lt;/strong&gt;：指标趋势（时间序列）、分布图（分数分布）、异常告警（邮件/钉钉）。&lt;/p&gt;
&lt;h3&gt;7.3 推荐配置&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# test_config.yaml
test_config:
  prompt:
    temperature: 0.1  # 低温度，提高一致性
    max_tokens: 512
    top_p: 0.9
    
  rag:
    top_k: 5
    similarity_threshold: 0.7
    rerank: true
    
  agent:
    max_steps: 128
    timeout_seconds: 300
    retry_count: 3
    
  evaluation:
    judge_model: &quot;gpt-4-turbo&quot;  # 评判模型
    human_sample_rate: 0.1  # 10% 人工抽检
    alert_threshold:
      accuracy_drop: 0.05  # 准确率下降 5% 告警
      timeout_rate: 0.15  # 超时率超过 15% 告警
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;实战案例：AskBI 智能问答评估&lt;/h2&gt;
&lt;h3&gt;8.1 项目背景&lt;/h3&gt;
&lt;p&gt;AskBI 是企业级 BI 智能问答系统，用户可以用自然语言查询业务指标（如&quot;上个月销售额是多少&quot;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术架构&lt;/strong&gt;：RAG + Agent&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RAG：检索指标文档、SQL 模板&lt;/li&gt;
&lt;li&gt;Agent：生成 SQL、执行查询、解释结果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;挑战&lt;/strong&gt;：需要支持 100+ 个业务指标，指标定义复杂（如&quot;活跃用户&quot;有多种定义），SQL 生成容易出错。&lt;/p&gt;
&lt;h3&gt;8.2 评估体系设计&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Prompt 层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL 生成准确率：生成的 SQL 是否正确&lt;/li&gt;
&lt;li&gt;自然语言理解准确率：是否正确理解用户意图&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;RAG 层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指标文档召回率：是否能找到相关指标定义&lt;/li&gt;
&lt;li&gt;SQL 模板匹配率：是否能找到合适的 SQL 模板&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Agent 层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多步查询成功率：复杂查询（如&quot;对比上个月和今年&quot;）的成功率&lt;/li&gt;
&lt;li&gt;工具调用准确率：选择正确工具（查询/计算/可视化）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 测试结果（第 4 版）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Prompt 层：
  - SQL 生成准确率：92%
  - 自然语言理解准确率：88%
  
RAG 层：
  - 指标文档召回率@5：96%
  - SQL 模板匹配率：85%
  
Agent 层：
  - 多步查询成功率：82%
  - 工具调用准确率：94%
  - 平均步骤数：3.2（预期 2-4 步）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.4 优化迭代&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;第 1 版 → 第 2 版&lt;/strong&gt;：准确率从 77% 提升到 92%（+15%）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优化 Prompt：增加 Few-shot 示例、明确 SQL 格式要求&lt;/li&gt;
&lt;li&gt;增加指标文档：补充 20 个常见指标的详细说明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第 2 版 → 第 3 版&lt;/strong&gt;：召回率从 86% 提升到 96%（+10%）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优化分块策略：从固定 500 字符改为按语义分块&lt;/li&gt;
&lt;li&gt;增加重排序：用 Cross-Encoder 对检索结果重排序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第 3 版 → 第 4 版&lt;/strong&gt;：任务成功率从 74% 提升到 82%（+8%）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;增加错误处理：SQL 执行失败时尝试修复或换方案&lt;/li&gt;
&lt;li&gt;优化状态管理：记住用户之前的筛选条件&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;总结与最佳实践&lt;/h2&gt;
&lt;h3&gt;9.1 核心要点回顾&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;分层评估&lt;/strong&gt;：Prompt/RAG/Agent 各有侧重，不能混为一谈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指标设计&lt;/strong&gt;：可测量、有意义、可行动，不是&quot;为了测而测&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自动化&lt;/strong&gt;：减少人工成本，支持持续集成和快速迭代。&lt;/p&gt;
&lt;h3&gt;9.2 最佳实践清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 建立基准数据集（至少 50 个测试用例，覆盖典型场景）&lt;/li&gt;
&lt;li&gt;[ ] 定义合格标准（每个层级都有明确阈值，如准确率≥90%）&lt;/li&gt;
&lt;li&gt;[ ] 自动化测试流程（CI/CD 集成，每次提交自动运行）&lt;/li&gt;
&lt;li&gt;[ ] 定期人工抽检（防止自动化偏差，如 10% 抽样）&lt;/li&gt;
&lt;li&gt;[ ] 持续跟踪趋势（发现退化及时告警，如准确率下降 5%）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.3 常见陷阱&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过度依赖自动化&lt;/strong&gt;：LLM-as-a-Judge 也有偏差，需定期人工校准。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试数据偏差&lt;/strong&gt;：基准数据集不能覆盖所有场景，需持续补充真实数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指标游戏&lt;/strong&gt;：优化指标不等于优化体验，需关注真实用户反馈（如满意度调查）。&lt;/p&gt;
&lt;h3&gt;9.4 下一步行动&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;评估当前项目&lt;/strong&gt;：测试覆盖度如何？哪些层级缺少评估？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择一个层级开始&lt;/strong&gt;：从 Prompt 层开始最简单，逐步扩展到 RAG 和 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立基准数据集&lt;/strong&gt;：收集 50-100 个典型用例，版本化管理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动化测试流程&lt;/strong&gt;：集成到 CI/CD，每次提交自动运行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续迭代优化&lt;/strong&gt;：根据测试结果调整 Prompt、检索策略、技能设计。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;相关文章&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[LangGraph 复杂状态机设计模式]]&lt;/li&gt;
&lt;li&gt;[[LLM 在垂直领域的微调策略]]&lt;/li&gt;
&lt;li&gt;[[轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环]]&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LLM</category><category>评估</category><category>测试</category><category>自动化</category><category>QA</category><category>验收标准</category></item><item><title>Ansible UI 部署工作台设计复盘：从 CLI 到 Agent 技能系统</title><link>https://heyedwardchen.com/blog/ansible-ui-deployment-workbench/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/ansible-ui-deployment-workbench/</guid><description>为什么我们放弃 CLI 转向 UI？一个企业内部部署平台的 6 阶段演进：从脚本到产品化，从手动到 Agent 智能</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Ansible UI 部署工作台设计复盘：从 CLI 到 Agent 技能系统&lt;/h1&gt;
&lt;h2&gt;引言：为什么需要 UI？&lt;/h2&gt;
&lt;h3&gt;公司内部现状&lt;/h3&gt;
&lt;p&gt;2024 年初，我们团队面临一个典型困境：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;10+ 个环境&lt;/strong&gt;：开发、测试、预发布、生产，每个环境 3-5 台服务器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5+ 个服务&lt;/strong&gt;：AskBI、LangChain 应用、Milvus 向量库、MinIO 对象存储、前端服务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 个运维同事&lt;/strong&gt;：每人维护 2-3 个环境，频繁切换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零文档&lt;/strong&gt;：部署流程靠口口相传，新人入职 1 周学不会&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心痛点&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;每次部署都要查笔记、翻 Slack 记录、问老同事。同一个操作，三个人有三种写法。&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;CLI 的局限性&lt;/h3&gt;
&lt;p&gt;Ansible 本身是优秀的自动化工具，但&lt;strong&gt;CLI 不是产品&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;| 问题 | 表现 | 影响 |
|------|------|------|
| &lt;strong&gt;学习门槛高&lt;/strong&gt; | 新人需要懂 YAML、SSH、网络、容器 | 入职培训 2 周+ |
| &lt;strong&gt;无状态&lt;/strong&gt; | 每次执行都是独立的，无法追溯历史 | 故障排查困难 |
| &lt;strong&gt;无协作&lt;/strong&gt; | 多人同时部署容易冲突 | 生产事故风险 |
| &lt;strong&gt;无可视化&lt;/strong&gt; | 日志是纯文本，无法快速定位问题 | 排查效率低 |
| &lt;strong&gt;无权限&lt;/strong&gt; | 要么全权，要么无权 | 安全边界模糊 |&lt;/p&gt;
&lt;h3&gt;为什么没有官方工具？&lt;/h3&gt;
&lt;p&gt;Ansible Tower / AWX 是官方解决方案，但&lt;strong&gt;不适合我们&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;太重&lt;/strong&gt;：需要独立集群，维护成本高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;太通用&lt;/strong&gt;：我们的部署流程高度定制化（私有化交付、离线包、特殊网络）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;太贵&lt;/strong&gt;：Tower 企业版 License 费用不菲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不够灵活&lt;/strong&gt;：我们的环境模型（env + config + job）与 Tower 的 inventory + playbook 不匹配&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：我们需要一个&lt;strong&gt;轻量级、定制化、产品化&lt;/strong&gt;的部署工作台。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Phase 1-2：从脚本到工作台（2024 Q2）&lt;/h2&gt;
&lt;h3&gt;初始设计&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：把散落在脚本、笔记、Slack 中的部署流程，整合成一个可重复使用的工作台。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心模型&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;环境 (Environment)
├── 库存 (Inventory)：服务器列表、SSH 配置
├── 配置 (Config)：main.yml、hosts.yml、.env
├── 作业 (Job)：部署历史、日志、产物
└── 动作 (Action)：check、deploy、clean、rollback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;设计原则&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境是核心边界&lt;/strong&gt;：所有数据（库存、配置、日志）都挂到环境&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高频操作优先&lt;/strong&gt;：环境选择、配置编辑、执行部署、查看日志&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复用既有组件&lt;/strong&gt;：文件管理、配置版本沿用已有交互，减少新模型&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;技术选型&lt;/h3&gt;
&lt;p&gt;| 层级 | 技术 | 理由 |
|------|------|------|
| &lt;strong&gt;前端&lt;/strong&gt; | React + TypeScript + Ant Design | 团队熟悉，组件丰富 |
| &lt;strong&gt;后端&lt;/strong&gt; | FastAPI + Python | 与 Ansible 生态无缝集成 |
| &lt;strong&gt;执行器&lt;/strong&gt; | Ansible shell 通道 | 复用既有 SSH 能力，无需引 paramiko |
| &lt;strong&gt;存储&lt;/strong&gt; | 文件系统 + JSONL | 轻量，无需数据库 |
| &lt;strong&gt;部署&lt;/strong&gt; | Docker Compose | 与目标环境一致 |&lt;/p&gt;
&lt;h3&gt;关键决策&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Q：为什么不直接用 Ansible API？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A：Ansible 的 Python API 太重，且绑定特定版本。我们用&lt;strong&gt;shell 通道&lt;/strong&gt;（&lt;code&gt;ansible &amp;#x3C;env&gt; -m shell -a &quot;&amp;#x3C;cmd&gt;&quot;&lt;/code&gt;）作为执行层，好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;版本无关：任何 Ansible 版本都能用&lt;/li&gt;
&lt;li&gt;权限复用：直接用 &lt;code&gt;~/.ssh/config&lt;/code&gt; 的 ProxyJump、别名&lt;/li&gt;
&lt;li&gt;日志透明：stdout/stderr 直接返回，无需额外解析&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Q：为什么不引入数据库？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A：初期数据量小（&amp;#x3C; 100 个环境，&amp;#x3C; 1000 个作业），文件系统 + JSONL 足够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发快：无需 ORM、迁移&lt;/li&gt;
&lt;li&gt;调试易：直接 &lt;code&gt;cat&lt;/code&gt; 文件就能看数据&lt;/li&gt;
&lt;li&gt;部署简：Docker volume 挂载即可持久化&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Phase 3-4：用户体验与协作（2024 Q3-Q4）&lt;/h2&gt;
&lt;h3&gt;用户反馈&lt;/h3&gt;
&lt;p&gt;上线 3 个月后，收集到 5 个核心问题：&lt;/p&gt;
&lt;p&gt;| # | 问题 | 根因 | 影响 |
|---|------|------|------|
| 1 | 任务列表不跟随选中环境 | Jobs 页用独立 state，与全局环境无联动 | 用户频繁切环境，容易误操作 |
| 2 | 多人共用 admin 账号 | 无审计、无并发控制 | 无法追溯谁做了什么 |
| 3 | 登录态不稳定（常显示 guest） | 异步加载期间按 guest 渲染 | 用户以为没权限，放弃操作 |
| 4 | 环境选择在 tab 间不一致 | &lt;code&gt;selectedEnv&lt;/code&gt; 不持久化，刷新丢失 | 每次刷新都要重新选环境 |
| 5 | Agent 页会话历史串环境 | Agent 的 env 是局部 state | 切环境后看到别人的会话 |&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;统一状态管理&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 之前：每个页面独立 state
const [selectedEnv, setSelectedEnv] = useState(&quot;&quot;);
const [jobFilters, setJobFilters] = useState({ env: &quot;&quot; });

// 之后：全局 Provider + URL 参数
const { env, setEnv } = useGlobalEnv(); // 持久化到 localStorage + URL ?env=xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;操作者标识&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 每条任务记录客户端 IP + User-Agent
task = {
    &quot;env&quot;: env_name,
    &quot;action&quot;: &quot;deploy&quot;,
    &quot;operator&quot;: f&quot;{request.client.host} ({request.headers.get(&apos;User-Agent&apos;, &apos;Unknown&apos;)})&quot;,
    &quot;timestamp&quot;: datetime.now(),
    &quot;status&quot;: &quot;running&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;登录态三态&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;type AuthState = &quot;unknown&quot; | &quot;admin&quot; | &quot;guest&quot;;

// 加载中显示 loading，不按 guest 渲染
const [auth, setAuth] = useState&amp;#x3C;AuthState&gt;(&quot;unknown&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;效果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;环境一致性&lt;/strong&gt;：所有 tab 统一消费全局环境，刷新不丢失&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;操作可追溯&lt;/strong&gt;：每条任务记录 IP + UA，至少知道是谁的机器&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;登录稳定&lt;/strong&gt;：三态管理，加载中不降级为 guest&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Phase 5：Agent 技能系统（2026 Q1）&lt;/h2&gt;
&lt;h3&gt;为什么引入 Agent？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：即使有 UI，复杂操作仍需多步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建环境 → 2. 编辑配置 → 3. 上传私钥 → 4. 信任主机 → 5. 执行部署&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;用户反馈&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;我知道要做什么，但不知道每一步点哪里。有时候漏了一步，部署失败还要重来。&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：让 Agent &lt;strong&gt;理解意图&lt;/strong&gt;，&lt;strong&gt;自动规划&lt;/strong&gt;，&lt;strong&gt;执行全流程&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;技能系统设计&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：Skill = 带 &lt;code&gt;SKILL.md&lt;/code&gt; 的目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skills/
├── create-env/
│   └── SKILL.md          # 技能说明
│   └── tools.py          # 工具定义
├── ssh-ops/
│   └── SKILL.md
└── diagnosis/
    └── SKILL.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;渐进式披露&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统提示只给技能目录（&lt;code&gt;name + description&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;模型判断相关后，调用 &lt;code&gt;load_skill&lt;/code&gt; 工具拉取正文&lt;/li&gt;
&lt;li&gt;节省 Token，按需加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工具调用循环&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用户：创建一个新的测试环境
模型：需要调用 create_environment 工具
→ 执行工具 → 返回结果
模型：需要调用 add_authorized_key 工具
→ 执行工具 → 返回结果
模型：需要调用 run_deploy_action 工具
→ 执行工具 → 返回结果
模型：部署完成，总结步骤
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;技能示例：create-env&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SKILL.md&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
name: create-env
title: 环境创建与部署
description: 创建新环境、复制环境、修改配置、执行部署
---

# 环境创建与部署技能

## 能力范围
- 创建新环境（指定服务器、SSH 配置）
- 复制现有环境（保留配置，生成独立 vault 密码）
- 修改环境配置（走 inventory，留版本历史）
- 执行部署动作（check、deploy、clean、rollback）

## 工具调用
- `create_environment(env, host, user, port, deploy_root)`
- `copy_environment(source_env, target_env)`
- `update_env_config(env, config_updates)`
- `run_deploy_action(env, action, force=false)`

## 使用原则
- 少问：只问必要信息（env、host、SSH 凭据），其余默认
- 确认：部署前让用户确认配置
- 留痕：改配置走 inventory，可 diff/回滚
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;意图理解&lt;/strong&gt;：用户说&quot;创建一个测试环境&quot;，Agent 自动规划步骤&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;少问多干&lt;/strong&gt;：只问必要信息，其余用默认值&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;可追溯&lt;/strong&gt;：每一步都有工具调用记录，可审计&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;Phase 6：全流程运维助手（2026 Q2）&lt;/h2&gt;
&lt;h3&gt;会话与交互增强&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;会话历史&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端持久化（JSONL），按环境过滤&lt;/li&gt;
&lt;li&gt;50 会话上限，200 条/会话，30 天 TTL&lt;/li&gt;
&lt;li&gt;刷新/切换可恢复，包含 reasoning 和工具轨迹&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;附件上传&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持上传私钥、配置文件&lt;/li&gt;
&lt;li&gt;工具按 file id 读取&lt;strong&gt;原始内容&lt;/strong&gt;，不经模型转述&lt;/li&gt;
&lt;li&gt;避免大段 base64 被改坏，也不进模型上下文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;可终止任务&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行中&quot;发送&quot;按钮变&quot;停止&quot;&lt;/li&gt;
&lt;li&gt;AbortSignal 终止当前工具调用&lt;/li&gt;
&lt;li&gt;AbortError 不当报错，友好提示&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;环境全流程工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;多意图技能&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# create-env 技能重写为多意图
def handle_create_env_intent(message):
    if &quot;创建&quot; in message or &quot;新建&quot; in message:
        return &quot;create&quot;
    elif &quot;复制&quot; in message:
        return &quot;copy&quot;
    elif &quot;修改&quot; in message or &quot;改配置&quot; in message:
        return &quot;update&quot;
    elif &quot;删除&quot; in message:
        return &quot;delete&quot;
    elif &quot;部署&quot; in message:
        return &quot;deploy&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;工具集&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;| 工具 | 作用 | 调用次数 |
|------|------|----------|
| &lt;code&gt;create_environment&lt;/code&gt; | 创建新环境 | 1 |
| &lt;code&gt;copy_environment&lt;/code&gt; | 复制环境 | 1 |
| &lt;code&gt;update_env_config&lt;/code&gt; | 修改配置 | 1 |
| &lt;code&gt;delete_environment&lt;/code&gt; | 删除环境 | 1 |
| &lt;code&gt;run_deploy_action&lt;/code&gt; | 执行部署动作 | 1-3 |
| &lt;code&gt;add_authorized_key&lt;/code&gt; | 添加 SSH 公钥 | 1 |
| &lt;code&gt;replace_container_file&lt;/code&gt; | 替换容器内文件 | 1 |
| &lt;code&gt;trust_ssh_host&lt;/code&gt; | 信任 SSH 主机 | 1 |
| &lt;code&gt;get_deployer_public_key&lt;/code&gt; | 获取部署机公钥 | 1 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;max_steps = 128&lt;/strong&gt;：一条龙部署需 7-8+ 次工具调用，留足余量。&lt;/p&gt;
&lt;h3&gt;排查/修复引导原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;写入系统提示&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;排查/修复原则：
1. 改配置优先走 inventory（留版本历史可 diff/回滚）
2. 服务日志以 docker-compose 挂载路径为准（docker inspect Mounts）
3. 不假装轮询：Agent 无法自我定时唤醒，长任务引导用户看任务看板
4. 定位最早失败点：从部署日志找到第一个 ERROR，不盲目重试
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;改配置留痕&lt;/strong&gt;：优先 &lt;code&gt;update_env_config&lt;/code&gt;，不直接改文件&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;日志路径准确&lt;/strong&gt;：按 compose mount 路径，不猜服务名&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;不假装轮询&lt;/strong&gt;：引导用户看任务看板，不循环调用 &lt;code&gt;read_job_log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;设计取舍与经验&lt;/h2&gt;
&lt;h3&gt;1. 为什么不做成&quot;低代码平台&quot;？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：很多内部工具想做成&quot;拖拽式编排&quot;，但我们&lt;strong&gt;刻意避免&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学习成本&lt;/strong&gt;：拖拽式看似简单，但用户仍需理解流程、变量、条件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活性差&lt;/strong&gt;：拖拽式难以表达复杂逻辑（如&quot;如果 A 失败则重试 3 次&quot;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维护困难&lt;/strong&gt;：每个自定义流程都要单独测试、文档化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;我们的选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent 驱动&lt;/strong&gt;：用户说&quot;我要做什么&quot;，Agent 自动规划&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技能封装&lt;/strong&gt;：复杂逻辑封装在技能中，用户无需理解细节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可审计&lt;/strong&gt;：每一步都有工具调用记录，可追溯&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 为什么不用工作流引擎（如 Temporal、Argo Workflows）？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：工作流引擎适合&lt;strong&gt;长期运行、复杂编排&lt;/strong&gt;的任务，但我们的场景是&lt;strong&gt;短任务、多步骤&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对比&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;| 维度 | 工作流引擎 | Agent 技能系统 |
|------|-----------|--------------|
| &lt;strong&gt;适用场景&lt;/strong&gt; | 长期运行（小时/天）、复杂依赖 | 短任务（分钟级）、线性流程 |
| &lt;strong&gt;学习成本&lt;/strong&gt; | 需理解 DAG、状态机 | 自然语言描述意图 |
| &lt;strong&gt;灵活性&lt;/strong&gt; | 需预先定义流程 | 动态规划，可调整 |
| &lt;strong&gt;可观测性&lt;/strong&gt; | 内置可视化 | 工具调用轨迹 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们的选择&lt;/strong&gt;：Agent 技能系统，因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;部署任务通常是&lt;strong&gt;线性流程&lt;/strong&gt;（创建 → 配置 → 部署）&lt;/li&gt;
&lt;li&gt;用户更习惯&lt;strong&gt;自然语言&lt;/strong&gt;描述意图&lt;/li&gt;
&lt;li&gt;工具调用轨迹已足够&lt;strong&gt;可观测&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 为什么不做成&quot;完全自动化&quot;？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：理论上 Agent 可以全自动完成所有操作，但我们&lt;strong&gt;刻意保留确认环节&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全&lt;/strong&gt;：生产环境部署需要人工确认&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习&lt;/strong&gt;：用户需要知道 Agent 做了什么，才能信任&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计&lt;/strong&gt;：关键操作需要人工审批&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;我们的设计&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Agent：检测到需要部署生产环境
→ 暂停，请求用户确认
用户：确认
→ 继续执行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;安全&lt;/strong&gt;：关键操作有人工把关&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;透明&lt;/strong&gt;：用户知道每一步在做什么&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;可审计&lt;/strong&gt;：确认记录可追溯&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;未来规划&lt;/h2&gt;
&lt;h3&gt;短期（2026 Q3）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;多账号系统&lt;/strong&gt;：告别共用 admin，支持个人账号 + 角色权限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境模板&lt;/strong&gt;：一键创建标准环境（开发/测试/生产）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署流水线&lt;/strong&gt;：支持多阶段部署（dev → test → prod）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通知集成&lt;/strong&gt;：飞书/钉钉通知部署结果&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;中期（2026 Q4）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;K8s 深度集成&lt;/strong&gt;：Helm chart 管理、K8s 资源监控&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;成本分析&lt;/strong&gt;：按环境统计资源使用、成本分摊&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动化测试&lt;/strong&gt;：部署后自动运行 smoke test&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回滚策略&lt;/strong&gt;：支持一键回滚到任意历史版本&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;长期（2027+）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;多云支持&lt;/strong&gt;：同时管理 AWS、阿里云、私有云&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;智能优化&lt;/strong&gt;：基于历史数据推荐资源配置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自愈能力&lt;/strong&gt;：检测到故障自动修复（如重启服务、扩容）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开放平台&lt;/strong&gt;：允许第三方开发技能插件&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;h3&gt;核心经验&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;UI 不是 CLI 的包装&lt;/strong&gt;：需要从产品视角重新设计交互模型&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent 不是银弹&lt;/strong&gt;：复杂场景仍需人工确认，安全边界不能丢&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;技能系统是关键&lt;/strong&gt;：封装复杂逻辑，用户只需描述意图&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测性优先&lt;/strong&gt;：每一步都要可追溯，故障排查才不头疼&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;适用场景&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;适合&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;企业内部部署平台&lt;/li&gt;
&lt;li&gt;运维自动化工具&lt;/li&gt;
&lt;li&gt;DevOps 工作台&lt;/li&gt;
&lt;li&gt;私有化交付管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不适合&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;超大规模集群（&gt; 1000 节点）&lt;/li&gt;
&lt;li&gt;需要复杂编排的长期任务&lt;/li&gt;
&lt;li&gt;完全无人值守的自动化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;开源计划&lt;/h3&gt;
&lt;p&gt;我们计划将 Ansible UI 的核心模块（技能系统、工具调用循环、会话管理）开源，帮助更多团队构建自己的部署平台。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预计时间&lt;/strong&gt;：2026 Q3&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;仓库地址&lt;/strong&gt;：待定（欢迎提前 star 关注）&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;相关文章&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环]]&lt;/li&gt;
&lt;li&gt;[[LangGraph 状态机设计模式]]&lt;/li&gt;
&lt;li&gt;[[Karpathy LLM Wiki 模式：Obsidian 知识库重构指南]]&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Ansible</category><category>部署平台</category><category>DevOps</category><category>Agent</category><category>产品化</category><category>内部工具</category></item><item><title>轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环</title><link>https://heyedwardchen.com/blog/lightweight-agent-skill-system/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/lightweight-agent-skill-system/</guid><description>从硬编码提示词到可插拔 Skill 系统：一个支持渐进式披露、工具调用循环、用户自定义技能的轻量级 Agent 架构</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;轻量级 Agent 开发方案：基于 Skill 插件的工具调用循环&lt;/h1&gt;
&lt;h2&gt;引言：为什么需要 Skill 插件系统？&lt;/h2&gt;
&lt;p&gt;很多 Agent 实现停留在&quot;一次性问答 + 硬编码提示词&quot;阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;能力固化&lt;/strong&gt;：所有技能写死在系统提示里，无法扩展&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token 浪费&lt;/strong&gt;：每次对话都注入全部技能说明，即使大部分用不上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法定制&lt;/strong&gt;：用户不能添加自己的技能，只能等待官方更新&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单轮对话&lt;/strong&gt;：没有工具调用循环，模型无法&quot;边思考边行动&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文介绍一个&lt;strong&gt;轻量级 Agent 架构&lt;/strong&gt;，核心特性：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Skill = 带 SKILL.md 的目录&lt;/strong&gt;：YAML frontmatter 声明元数据，正文是技能说明&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进式披露&lt;/strong&gt;：系统提示只给 &lt;code&gt;name + description&lt;/code&gt;，模型需要时通过 &lt;code&gt;load_skill&lt;/code&gt; 工具拉取正文&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具调用循环&lt;/strong&gt;：流式响应 + 多轮工具调用，模型可以&quot;边思考边行动&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户自定义&lt;/strong&gt;：支持前端新增/编辑/删除技能，持久化存储&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定向技能&lt;/strong&gt;：&lt;code&gt;/skill_name&lt;/code&gt; 语法预注入技能正文，无需模型再调用 &lt;code&gt;load_skill&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;核心设计&lt;/h2&gt;
&lt;h3&gt;1. Skill 文件格式&lt;/h3&gt;
&lt;p&gt;每个技能是一个目录，包含 &lt;code&gt;SKILL.md&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;skills/
  ├── file-ops/
  │   └── SKILL.md
  ├── api-client/
  │   └── SKILL.md
  └── data-analysis/
      └── SKILL.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; 格式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
name: file-ops
title: 文件操作
description: 读取、写入、搜索、统计文件内容
---

# 文件操作技能

## 能力范围
- 读取文件内容（支持分页）
- 写入/覆盖文件
- 搜索文件内容（正则）
- 统计代码行数

## 工具调用
- `read_file(path, offset=1, limit=500)`
- `write_file(path, content)`
- `search_files(pattern, target=&quot;content&quot;)`

## 使用示例
用户：查看 main.py 的前 50 行
模型：调用 read_file(path=&quot;main.py&quot;, limit=50)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 渐进式披露&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：如果系统提示包含所有技能的完整说明，Token 消耗巨大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法&lt;/strong&gt;：系统提示只包含技能目录（&lt;code&gt;name + description&lt;/code&gt;），模型判断相关后，通过 &lt;code&gt;load_skill&lt;/code&gt; 工具拉取正文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;系统提示示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;可用技能（调用 load_skill 获取详情）：
- file-ops: 读取、写入、搜索、统计文件内容
- api-client: HTTP 请求、JSON 解析、认证管理
- data-analysis: 数据清洗、统计、可视化

当前任务：分析日志文件中的错误模式
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型思考：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;需要文件操作能力 → 调用 load_skill(name=&quot;file-ops&quot;)
获取技能详情 → 看到 read_file + search_files 工具
执行任务 → 调用 search_files(pattern=&quot;ERROR&quot;, path=&quot;logs/&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token 节省：系统提示从 2000 Token → 200 Token&lt;/li&gt;
&lt;li&gt;灵活扩展：新增技能不影响现有对话&lt;/li&gt;
&lt;li&gt;按需加载：只加载当前任务相关的技能&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 工具调用循环&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心引擎&lt;/strong&gt;：一个&lt;strong&gt;同步生成器&lt;/strong&gt;形式的流式工具调用循环。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;伪代码&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;async def agent_chat_stream(user_message, history, max_steps=128):
    messages = [system_prompt] + history + [user_message]
    
    for step in range(max_steps):
        # 1. 调用 LLM，流式返回
        tool_calls = []
        async for chunk in llm_stream(messages, tools=ALL_TOOLS):
            if chunk.type == &quot;text&quot;:
                yield {&quot;type&quot;: &quot;text&quot;, &quot;content&quot;: chunk.text}
            elif chunk.type == &quot;tool&quot;:
                tool_calls.append(chunk.tool)
        
        # 2. 没有工具调用 → 对话结束
        if not tool_calls:
            yield {&quot;type&quot;: &quot;done&quot;}
            return
        
        # 3. 执行工具调用
        for call in tool_calls:
            result = await dispatch_tool(call.name, call.arguments)
            messages.append({&quot;role&quot;: &quot;assistant&quot;, &quot;tool_calls&quot;: [call]})
            messages.append({&quot;role&quot;: &quot;tool&quot;, &quot;content&quot;: result})
            
            yield {&quot;type&quot;: &quot;tool&quot;, &quot;name&quot;: call.name, &quot;result&quot;: result}
    
    # 4. 超过最大步数 → 强制结束
    yield {&quot;type&quot;: &quot;error&quot;, &quot;message&quot;: &quot;达到最大工具调用次数&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;流式响应&lt;/strong&gt;：边生成边推送，用户可以看到&quot;思考过程&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多轮循环&lt;/strong&gt;：工具结果回喂给模型，模型可以继续调用工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常兜底&lt;/strong&gt;：任何工具错误都归一化为文本，模型可以解释/换路&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 技能来源与权限&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;两个技能来源&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;| 来源 | 路径 | 权限 | 用途 |
|------|------|------|------|
| 内置 | &lt;code&gt;skills/&lt;/code&gt; | 只读 | 官方技能，随代码发布 |
| 用户 | &lt;code&gt;data/skills/&lt;/code&gt; | 可写 | 用户自定义，持久化存储 |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;加载逻辑&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def load_all_skills():
    builtin_skills = scan_directory(SKILLS_DIR)
    user_skills = scan_directory(USER_SKILLS_DIR)
    
    # 同名以内置为准（内置只读，不可被覆盖）
    all_skills = {**user_skills, **builtin_skills}
    
    return all_skills
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;CRUD 接口&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 新增/更新用户技能
def save_user_skill(name: str, content: str):
    if name in BUILTIN_SKILLS:
        raise PermissionError(&quot;内置技能不可覆盖&quot;)
    if not re.match(r&apos;^[a-z0-9][a-z0-9-]{0,48}$&apos;, name):
        raise ValueError(&quot;技能名格式错误&quot;)
    
    path = USER_SKILLS_DIR / name / &quot;SKILL.md&quot;
    path.parent.mkdir(exist_ok=True)
    path.write_text(content)

# 删除用户技能
def delete_user_skill(name: str):
    if name in BUILTIN_SKILLS:
        raise PermissionError(&quot;内置技能不可删除&quot;)
    
    path = USER_SKILLS_DIR / name
    shutil.rmtree(path)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 定向技能&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;语法&lt;/strong&gt;：&lt;code&gt;/skill_name 任务描述&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def parse_user_message(message: str) -&gt; tuple[str, str | None]:
    &quot;&quot;&quot;解析 /skill_name 语法&quot;&quot;&quot;
    if message.startswith(&quot;/&quot;):
        parts = message.split(None, 1)
        if len(parts) == 2:
            skill_name = parts[0][1:]  # 去掉 /
            task = parts[1]
            return task, skill_name
    return message, None

def build_system_prompt(forced_skill: str | None):
    prompt = &quot;可用技能：\n&quot; + skill_catalog()
    
    if forced_skill:
        skill_content = read_skill(forced_skill)
        prompt += f&quot;\n\n【已激活】{skill_content}&quot;
    
    return prompt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户可以直接指定技能，无需模型判断&lt;/li&gt;
&lt;li&gt;减少 &lt;code&gt;load_skill&lt;/code&gt; 调用，节省 Token 和延迟&lt;/li&gt;
&lt;li&gt;适合&quot;我知道要用哪个技能&quot;的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;实现示例&lt;/h2&gt;
&lt;h3&gt;1. 后端：技能加载器&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# skills.py
from pathlib import Path
import yaml
from typing import Dict, List

SKILLS_DIR = Path(__file__).parent / &quot;skills&quot;
USER_SKILLS_DIR = Path(&quot;~/data/skills&quot;).expanduser()

def parse_skill_file(path: Path) -&gt; dict:
    &quot;&quot;&quot;解析 SKILL.md，返回 {name, title, description, content}&quot;&quot;&quot;
    content = path.read_text()
    
    # 提取 frontmatter
    if content.startswith(&quot;---&quot;):
        end = content.find(&quot;---&quot;, 3)
        if end &gt; 0:
            frontmatter = yaml.safe_load(content[3:end])
            body = content[end+3:].strip()
            return {
                **frontmatter,
                &quot;content&quot;: body,
                &quot;path&quot;: str(path)
            }
    
    return {&quot;name&quot;: path.parent.name, &quot;content&quot;: content}

def load_skills() -&gt; Dict[str, dict]:
    &quot;&quot;&quot;加载所有技能（内置 + 用户）&quot;&quot;&quot;
    skills = {}
    
    # 加载用户技能
    for skill_dir in USER_SKILLS_DIR.glob(&quot;*&quot;):
        if skill_dir.is_dir():
            skill_file = skill_dir / &quot;SKILL.md&quot;
            if skill_file.exists():
                skills[skill_dir.name] = parse_skill_file(skill_file)
    
    # 加载内置技能（覆盖同名）
    for skill_dir in SKILLS_DIR.glob(&quot;*&quot;):
        if skill_dir.is_dir():
            skill_file = skill_dir / &quot;SKILL.md&quot;
            if skill_file.exists():
                skill = parse_skill_file(skill_file)
                skill[&quot;builtin&quot;] = True
                skills[skill_dir.name] = skill
    
    return skills

def skill_catalog() -&gt; str:
    &quot;&quot;&quot;生成技能目录（name + description，用于系统提示）&quot;&quot;&quot;
    skills = load_skills()
    lines = []
    for name, skill in sorted(skills.items()):
        marker = &quot;（内置）&quot; if skill.get(&quot;builtin&quot;) else &quot;&quot;
        lines.append(f&quot;- {name}: {skill.get(&apos;description&apos;, &apos;无说明&apos;)} {marker}&quot;)
    return &quot;\n&quot;.join(lines)

def read_skill(name: str) -&gt; str | None:
    &quot;&quot;&quot;读取技能正文&quot;&quot;&quot;
    skills = load_skills()
    if name in skills:
        return skills[name][&quot;content&quot;]
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 后端：工具调用循环&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# agent_loop.py
import json
import requests
from typing import Iterator, Dict, Any

CORE_TOOLS = [
    {
        &quot;name&quot;: &quot;load_skill&quot;,
        &quot;description&quot;: &quot;加载技能详情&quot;,
        &quot;parameters&quot;: {
            &quot;type&quot;: &quot;object&quot;,
            &quot;properties&quot;: {
                &quot;name&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;技能名&quot;}
            }
        }
    },
    {
        &quot;name&quot;: &quot;read_file&quot;,
        &quot;description&quot;: &quot;读取文件内容&quot;,
        &quot;parameters&quot;: {
            &quot;type&quot;: &quot;object&quot;,
            &quot;properties&quot;: {
                &quot;path&quot;: {&quot;type&quot;: &quot;string&quot;},
                &quot;offset&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 1},
                &quot;limit&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;default&quot;: 500}
            }
        }
    }
]

TOOL_HANDLERS = {
    &quot;load_skill&quot;: lambda args: read_skill(args[&quot;name&quot;]) or &quot;技能不存在&quot;,
    &quot;read_file&quot;: lambda args: Path(args[&quot;path&quot;]).read_text()
}

def agent_chat_stream(user_message: str, history: list, max_steps: int = 128) -&gt; Iterator[str]:
    &quot;&quot;&quot;流式工具调用循环&quot;&quot;&quot;
    messages = [
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: build_system_prompt()},
        *history,
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: user_message}
    ]
    
    for step in range(max_steps):
        # 调用 LLM
        tool_calls = []
        response_text = &quot;&quot;
        
        response = requests.post(
            &quot;http://localhost:8000/v1/chat/completions&quot;,
            json={
                &quot;model&quot;: &quot;claude-3&quot;,
                &quot;messages&quot;: messages,
                &quot;tools&quot;: CORE_TOOLS,
                &quot;tool_choice&quot;: &quot;auto&quot;,
                &quot;stream&quot;: True
            }
        )
        
        for line in response.iter_lines():
            if line.startswith(b&quot;data: &quot;):
                data = json.loads(line[6:])
                if &quot;choices&quot; in data and data[&quot;choices&quot;]:
                    delta = data[&quot;choices&quot;][0][&quot;delta&quot;]
                    
                    if delta.get(&quot;content&quot;):
                        yield json.dumps({&quot;type&quot;: &quot;text&quot;, &quot;content&quot;: delta[&quot;content&quot;]})
                        response_text += delta[&quot;content&quot;]
                    
                    if delta.get(&quot;tool_calls&quot;):
                        tool_calls.extend(delta[&quot;tool_calls&quot;])
        
        if not tool_calls:
            yield json.dumps({&quot;type&quot;: &quot;done&quot;})
            return
        
        # 执行工具
        messages.append({&quot;role&quot;: &quot;assistant&quot;, &quot;tool_calls&quot;: tool_calls})
        
        for call in tool_calls:
            result = TOOL_HANDLERS[call[&quot;function&quot;][&quot;name&quot;]](
                json.loads(call[&quot;function&quot;][&quot;arguments&quot;])
            )
            
            yield json.dumps({
                &quot;type&quot;: &quot;tool&quot;,
                &quot;name&quot;: call[&quot;function&quot;][&quot;name&quot;],
                &quot;result&quot;: result
            })
            
            messages.append({
                &quot;role&quot;: &quot;tool&quot;,
                &quot;tool_call_id&quot;: call[&quot;id&quot;],
                &quot;content&quot;: result
            })
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 前端：流式对话组件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// AgentChatPanel.tsx
function AgentChatPanel() {
  const [messages, setMessages] = useState&amp;#x3C;Message[]&gt;([]);
  const [input, setInput] = useState(&quot;&quot;);
  const [isLoading, setIsLoading] = useState(false);

  const sendMessage = async () =&gt; {
    if (!input.trim() || isLoading) return;
    
    const userMsg = { role: &quot;user&quot;, content: input };
    setMessages(prev =&gt; [...prev, userMsg]);
    setInput(&quot;&quot;);
    setIsLoading(true);
    
    const response = fetch(&quot;/api/agent/chat&quot;, {
      method: &quot;POST&quot;,
      body: JSON.stringify({ message: input, messages: messages })
    });
    
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    
    let assistantMsg = { role: &quot;assistant&quot;, content: &quot;&quot;, tools: [] };
    
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      
      const line = decoder.decode(value);
      const event = JSON.parse(line);
      
      if (event.type === &quot;text&quot;) {
        assistantMsg.content += event.content;
      } else if (event.type === &quot;tool&quot;) {
        assistantMsg.tools.push(event);
      } else if (event.type === &quot;done&quot;) {
        break;
      }
      
      // 实时更新消息
      setMessages(prev =&gt; {
        const newMessages = [...prev, userMsg, assistantMsg];
        return newMessages;
      });
    }
    
    setIsLoading(false);
  };

  return (
    &amp;#x3C;div&gt;
      &amp;#x3C;div className=&quot;messages&quot;&gt;
        {messages.map((msg, i) =&gt; (
          &amp;#x3C;MessageBubble key={i} message={msg} /&gt;
        ))}
      &amp;#x3C;/div&gt;
      &amp;#x3C;div className=&quot;input-area&quot;&gt;
        &amp;#x3C;input value={input} onChange={e =&gt; setInput(e.target.value)} /&gt;
        &amp;#x3C;button onClick={sendMessage} disabled={isLoading}&gt;
          {isLoading ? &quot;思考中...&quot; : &quot;发送&quot;}
        &amp;#x3C;/button&gt;
      &amp;#x3C;/div&gt;
    &amp;#x3C;/div&gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;1. 技能设计原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单一职责&lt;/strong&gt;：每个技能只解决一类问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自包含&lt;/strong&gt;：技能正文包含完整的使用说明和工具定义&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可组合&lt;/strong&gt;：技能之间可以协作，但不强依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 工具设计原则&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;幂等性&lt;/strong&gt;：工具可以安全重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原子性&lt;/strong&gt;：一次工具调用完成一个完整操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观察&lt;/strong&gt;：工具调用轨迹对用户可见&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 安全边界&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;技能名白名单&lt;/strong&gt;：拒绝路径穿越（如 &lt;code&gt;../../etc/passwd&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内置技能只读&lt;/strong&gt;：用户技能不能覆盖内置技能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具输出截断&lt;/strong&gt;：防止单条工具结果撑爆上下文（如限制 4000 字符）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权限控制&lt;/strong&gt;：敏感操作（如删除技能）需要管理员权限&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这个轻量级 Agent 架构的核心思想：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Skill = 文件&lt;/strong&gt;：技能是可版本化、可分享、可自定义的 Markdown 文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进式披露&lt;/strong&gt;：系统提示只给目录，模型按需加载，节省 Token&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具调用循环&lt;/strong&gt;：流式响应 + 多轮工具调用，模型可以&quot;边思考边行动&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户自定义&lt;/strong&gt;：支持前端 CRUD，技能持久化存储&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内部工具平台（如运维助手、数据分析助手）&lt;/li&gt;
&lt;li&gt;开发者工具（如代码审查、文档生成）&lt;/li&gt;
&lt;li&gt;企业知识库（如客服问答、故障排查）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要复杂状态管理的长期任务（考虑 LangGraph 等框架）&lt;/li&gt;
&lt;li&gt;需要人类审核的敏感操作（考虑 Human-in-the-loop 模式）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/langgraph-state-machine-patterns/&quot;&gt;LangGraph：复杂状态机设计模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://heyedwardchen.com/blog/Karpathy-LLM-Wiki-%E6%A8%A1%E5%BC%8F-Obsidian-%E7%9F%A5%E8%AF%86%E5%BA%93%E9%87%8D%E6%9E%84%E6%8C%87%E5%8D%97/&quot;&gt;Karpathy LLM Wiki 模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://platform.openai.com/docs/guides/function-calling&quot;&gt;OpenAI Function Calling 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;相关文章&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[LangGraph 复杂状态机设计模式]]&lt;/li&gt;
&lt;li&gt;[[Karpathy LLM Wiki 模式：Obsidian 知识库重构指南]]&lt;/li&gt;
&lt;li&gt;[[LLM 在垂直领域的微调策略]]&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Agent</category><category>工具调用</category><category>插件系统</category><category>LLM 工程</category><category>设计模式</category></item><item><title>Karpathy LLM Wiki 模式：Obsidian 知识库重构指南</title><link>https://heyedwardchen.com/blog/karpathy-llm-wiki-obsidian-knowledge-base/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/karpathy-llm-wiki-obsidian-knowledge-base/</guid><description>从碎片化笔记到 Agent 可维护的知识库：Karpathy LLM Wiki 模式的核心原则、重构流程与实战案例</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Karpathy LLM Wiki 模式：Obsidian 知识库重构指南&lt;/h1&gt;
&lt;h2&gt;引言：为什么需要 LLM Wiki 模式&lt;/h2&gt;
&lt;h3&gt;1.1 传统笔记库的&quot;AI 不友好&quot;问题&lt;/h3&gt;
&lt;p&gt;如果你像我一样，用 Obsidian 积累了数百条笔记，可能会发现一个尴尬的事实：这些笔记对人类阅读很友好，但对 AI 来说几乎不可用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;碎片化&lt;/strong&gt;是最明显的问题。一条关于&quot;Ansible 部署&quot;的笔记可能散落在 10 个不同的文件中：有的在项目笔记里，有的在运维文档里，还有的在随手记的命令行片段中。AI 无法自动拼凑出完整上下文，因为它不知道哪些笔记属于同一个主题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;冗余性&lt;/strong&gt;同样普遍。&quot;什么是 LangGraph&quot;这个概念可能在 5 个地方被解释过，每次的表述略有不同。AI 读取时无法判断哪个版本是最新的、哪个更准确，导致生成的内容前后矛盾。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;非结构化&lt;/strong&gt;让问题更严重。有的笔记用 frontmatter，有的不用；有的用 H1 标题，有的用粗体；日期格式五花八门（2026-07-03、2026/7/3、7 月 3 日）。AI 批量处理时不得不为每种格式写特殊的解析逻辑。&lt;/p&gt;
&lt;h3&gt;1.2 RAG 的局限性&lt;/h3&gt;
&lt;p&gt;很多人会想：那用 RAG（检索增强生成）不就行了吗？确实，RAG 能缓解检索问题，但无法解决根本矛盾。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检索噪声&lt;/strong&gt;是 RAG 的核心痛点。向量相似度不等于语义相关性。搜索&quot;如何部署 AskBI&quot;可能返回&quot;AskBI 是什么&quot;的定义段落，因为两者在向量空间很接近，但对用户来说毫无帮助。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;幻觉风险&lt;/strong&gt;在片段拼接时尤其明显。RAG 从 3 个不同笔记中各取一段，拼成一个回答，但这三段可能来自不同版本、不同环境，甚至相互矛盾。AI 无法判断，只能硬拼，结果就是看似合理实则错误的回答。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;维护成本&lt;/strong&gt;常被忽视。每次添加新笔记，理论上都应该重新计算 embedding。1000 条笔记的 embedding 更新可能需要几分钟，而且一旦原始笔记被修改，对应的 embedding 就失效了，但系统无法自动感知。&lt;/p&gt;
&lt;h3&gt;1.3 Karpathy Wiki 模式的核心思想&lt;/h3&gt;
&lt;p&gt;Andrej Karpathy 在 2024 年提出的&quot;LLM Wiki&quot;模式，为解决上述问题提供了新思路。核心思想很简单：&lt;strong&gt;把知识库当成 Wiki 来维护，而不是当成笔记堆来存储&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结构化&lt;/strong&gt;是第一条原则。每个主题一个页面，页面内包含完整上下文。比如&quot;AskBI 私有化部署&quot;这个主题，所有相关信息（环境要求、步骤、常见问题、回滚策略）都在一个页面里，AI 读取时不需要跨文件拼凑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可审计&lt;/strong&gt;是第二条原则。每个页面都有 frontmatter 记录创建时间、更新时间、标签、来源。所有变更都记录在日志文件中，AI 可以追踪知识的演变过程，人类也可以审计变更历史。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agent 友好&lt;/strong&gt;是第三条原则。明确的读取协议（先读 index.md，再读相关页面）和更新协议（新材料进 raw/，抽取后进 wiki/），让 AI 能够可靠地维护和扩展知识库，而不是在碎片中盲目搜索。&lt;/p&gt;
&lt;h2&gt;Wiki 模式 vs RAG vs 传统笔记&lt;/h2&gt;
&lt;h3&gt;2.1 三种模式的对比矩阵&lt;/h3&gt;
&lt;p&gt;| 维度 | 传统笔记 | RAG | LLM Wiki |
|------|----------|-----|----------|
| &lt;strong&gt;组织方式&lt;/strong&gt; | 按时间/场景碎片化存储 | 向量索引 + 原始文件 | 按主题结构化页面 |
| &lt;strong&gt;适用场景&lt;/strong&gt; | 个人随手记、灵感收集 | 大规模文档检索 | AI 协作知识库 |
| &lt;strong&gt;维护成本&lt;/strong&gt; | 低（只管写） | 中（需定期 re-embedding） | 高（需人工审阅） |
| &lt;strong&gt;查询质量&lt;/strong&gt; | 低（依赖关键词匹配） | 中（向量相似度有噪声） | 高（精确匹配 + 语义搜索） |
| &lt;strong&gt;AI 参与度&lt;/strong&gt; | 无 | 检索辅助 | 主动维护 |
| &lt;strong&gt;可审计性&lt;/strong&gt; | 弱（Git 历史但无结构） | 弱（黑盒检索） | 强（frontmatter + log） |&lt;/p&gt;
&lt;h3&gt;2.2 何时选择 Wiki 模式&lt;/h3&gt;
&lt;p&gt;不是所有场景都适合 Wiki 模式。以下是我的决策树：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据规模&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#x3C; 50 条笔记：传统笔记即可，过度结构化反而增加负担&lt;/li&gt;
&lt;li&gt;50-500 条：考虑 Wiki 模式，尤其是需要 AI 协助时&lt;/li&gt;
&lt;li&gt;
&lt;blockquote&gt;
&lt;p&gt;500 条：强烈建议 Wiki 模式，否则难以维护&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;更新频率&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每天新增 &gt; 5 条：需要自动化 pipeline，Wiki 模式更合适&lt;/li&gt;
&lt;li&gt;每周新增 &amp;#x3C; 10 条：传统笔记或 RAG 也可以&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI 参与度&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仅用于检索：RAG 足够&lt;/li&gt;
&lt;li&gt;需要 AI 维护、扩展、审计：必须 Wiki 模式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;混合模式&lt;/strong&gt;也是可行的。我的实践中，&lt;code&gt;raw/&lt;/code&gt; 目录用传统笔记方式快速记录，&lt;code&gt;wiki/&lt;/code&gt; 目录用 Wiki 模式沉淀知识。AI 定期扫描 &lt;code&gt;raw/&lt;/code&gt;，建议哪些内容应该抽取到 &lt;code&gt;wiki/&lt;/code&gt;，人类审阅后执行。&lt;/p&gt;
&lt;h2&gt;Karpathy Wiki 的核心原则&lt;/h2&gt;
&lt;h3&gt;3.1 页面组织原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;单一职责&lt;/strong&gt;：每个页面只讲一个主题。&quot;Ansible UI 项目&quot;一个页面，&quot;SSH 跳板机穿透&quot;一个页面，不要为了节省空间把多个主题塞在一起。这看似浪费，实则提高了可链接性和可维护性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自包含&lt;/strong&gt;：页面内应包含完整上下文，让读者（人类或 AI）不需要跳来跳去就能理解核心内容。当然，详细实现可以链接到其他页面，但摘要、关键结论、使用场景必须在当前页面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可链接&lt;/strong&gt;：明确的内外部引用关系。内部链接用 Obsidian 的双括号语法 &lt;code&gt;[[页面标题]]&lt;/code&gt;，外部链接用标准 Markdown &lt;code&gt;[标题](URL)&lt;/code&gt;。每个页面底部应有&quot;相关页面&quot;章节，列出关联主题。&lt;/p&gt;
&lt;h3&gt;3.2 frontmatter 标准&lt;/h3&gt;
&lt;p&gt;frontmatter 是 Wiki 模式的灵魂。我的标准配置如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
title: 页面标题
type: project|operation|concept|learning|source-summary
created: YYYY-MM-DD
updated: YYYY-MM-DD
tags: [标签 1, 标签 2]
sources: [来源 1, 来源 2]
author: 可选
status: draft|review|published
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;必需字段&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;：页面标题，与文件名对应（空格转连字符）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;：页面类型，决定组织方式和处理逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created&lt;/code&gt; / &lt;code&gt;updated&lt;/code&gt;：日期格式统一为 ISO 8601&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags&lt;/code&gt;：标签数组，用于分类和检索&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;可选字段&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;author&lt;/code&gt;：多人群协作时有用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;：草稿、审阅中、已发布&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sources&lt;/code&gt;：原始材料来源，用于审计&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 变更审计机制&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;日志文件&lt;/strong&gt;：&lt;code&gt;log.md&lt;/code&gt; 记录所有重要变更，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;## 2026-07-03

- [新增] wiki/projects/ansible-ui.md：从 raw/抽取 Ansible UI 项目信息
- [合并] wiki/operations/deployment.md：合并 3 条部署相关笔记
- [删除] archive/old-notes.md：超过 60 天未更新的临时笔记
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;版本追踪&lt;/strong&gt;：Git commit 与页面更新关联。每次批量更新后，commit message 应说明变更内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git commit -m &quot;wiki 更新：新增 Ansible UI 项目页，合并部署文档&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;回滚策略&lt;/strong&gt;：基于 Git 的历史恢复。如果发现某次更新有问题，可以直接 revert 或 checkout 到之前版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git log --oneline wiki/projects/ansible-ui.md  # 查看历史
git checkout &amp;#x3C;commit-hash&gt; -- wiki/projects/ansible-ui.md  # 恢复版本
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;重构流程：从 raw/ 到 wiki/&lt;/h2&gt;
&lt;h3&gt;4.1 准备阶段&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现状评估&lt;/strong&gt;：先统计现有笔记的状态。我的脚本会扫描所有文件，输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;总文件数：80
博客文章：10
Wiki 页面：11
Raw 目录：59（含 23 个待处理文件）
最近 30 天变更：15 个文件
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;工具准备&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python 脚本：批量处理、frontmatter 生成&lt;/li&gt;
&lt;li&gt;Git 配置：确保 remote 正确、.gitignore 合理&lt;/li&gt;
&lt;li&gt;frontmatter 模板：预设标准字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;备份策略&lt;/strong&gt;：原始文件保留在 &lt;code&gt;raw/sources/YYYY-MM-DD/&lt;/code&gt; 目录，即使抽取到 wiki 也不删除原文。这是安全网，也是审计依据。&lt;/p&gt;
&lt;h3&gt;4.2 抽取与合并&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;主题识别&lt;/strong&gt;：先用关键词聚类，再手动标注。我的做法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描所有笔记标题和标签&lt;/li&gt;
&lt;li&gt;提取高频词（如&quot;Ansible&quot;、&quot;部署&quot;、&quot;LangGraph&quot;）&lt;/li&gt;
&lt;li&gt;按高频词分组，人工确认是否属于同一主题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;内容抽取&lt;/strong&gt;：从碎片笔记中提取相关段落，不是简单复制粘贴，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保留核心结论和关键步骤&lt;/li&gt;
&lt;li&gt;删除临时性、场景特定的细节（这些进 raw/private）&lt;/li&gt;
&lt;li&gt;统一表述风格（时态、人称、术语）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;冲突处理&lt;/strong&gt;：相同信息在不同笔记中有不同版本时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;优先选择最新的（按修改时间）&lt;/li&gt;
&lt;li&gt;如果内容矛盾，保留更详细的版本，并在注释中说明差异&lt;/li&gt;
&lt;li&gt;无法判断时，两个版本都保留，标记&quot;待确认&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.3 结构化与去重&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模板填充&lt;/strong&gt;：为每个新页面生成标准 frontmatter 和章节结构：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
title: 页面标题
type: project
created: 2026-07-06
updated: 2026-07-06
tags: []
sources: []
---

# 页面标题

## 摘要

## 关键结论

## 细节

## 相关页面
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;去重检测&lt;/strong&gt;：用文本相似度检查新页面与现有页面是否重复。我的阈值是 80%：相似度超过 80% 就触发警告，人工判断是合并还是保留。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;引用规范化&lt;/strong&gt;：统一内部链接格式。Obsidian 用 &lt;code&gt;[[页面标题]]&lt;/code&gt;，但为了兼容其他工具，我会在导出时转换为 &lt;code&gt;[页面标题](页面标题.md)&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;4.4 验证与发布&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;完整性检查&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必填字段是否都有值&lt;/li&gt;
&lt;li&gt;内部链接是否指向存在的页面&lt;/li&gt;
&lt;li&gt;标签是否符合命名规范&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;人工审阅&lt;/strong&gt;：自动化≠完全可靠。关键页面（尤其是项目文档、运维流程）必须人工审阅，确保：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;信息准确&lt;/li&gt;
&lt;li&gt;表述清晰&lt;/li&gt;
&lt;li&gt;没有敏感信息泄漏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;发布流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 本地提交
git add wiki/
git commit -m &quot;新增：XXX 页面&quot;

# 推送到远程
git push origin main

# 同步到博客（如果是 blog 目录）
bash ~/.hermes/scripts/obsidian-git-sync.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实战案例：一次完整重构的记录&lt;/h2&gt;
&lt;h3&gt;5.1 项目背景&lt;/h3&gt;
&lt;p&gt;2026-07-03，我决定对 Obsidian vault 进行一次彻底重构。初始状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;总文件数&lt;/strong&gt;：80 个&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;博客文章&lt;/strong&gt;：10 篇（在 blog/ 目录）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wiki 页面&lt;/strong&gt;：11 个（分散在不同目录）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Raw 目录&lt;/strong&gt;：59 个文件（含大量待处理笔记）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;重构目标&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立 AGENTS.md 标准，明确 raw/ 与 wiki/ 的边界&lt;/li&gt;
&lt;li&gt;清理 raw/ 目录，抽取有价值的内容到 wiki/&lt;/li&gt;
&lt;li&gt;建立 archive/ 目录，归档旧版本和临时文件&lt;/li&gt;
&lt;li&gt;配置自动化同步脚本&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;时间线&lt;/strong&gt;：单日完成（约 4 小时），包括脚本编写、文件处理、人工审阅。&lt;/p&gt;
&lt;h3&gt;5.2 关键决策点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;raw/ 与 wiki/ 的边界定义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw/sources/&lt;/code&gt;：原始材料、未消化的笔记、临时记录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw/private/&lt;/code&gt;：含敏感信息的材料（凭据、内网地址、客户细节）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wiki/&lt;/code&gt;：结构化、可公开、Agent 友好的知识页面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archive/&lt;/code&gt;：旧版本、已废弃但需保留参考的内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;archive/ 目录的设计&lt;/strong&gt;：不是简单的&quot;回收站&quot;，而是有组织的归档：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;archive/cleanup-YYYY-MM-DD.md&lt;/code&gt;：每次清理的记录&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archive/old-versions/&lt;/code&gt;：页面历史版本（Git 已记录，但人工可读版本放这里）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archive/deprecated/&lt;/code&gt;：已废弃但仍需参考的内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;private/ 敏感信息的隔离策略&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有含凭据、token、密码的文件必须进 &lt;code&gt;raw/private/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt; 配置为忽略 &lt;code&gt;raw/private/&lt;/code&gt;，确保不会误推送到远程&lt;/li&gt;
&lt;li&gt;wiki 页面需要引用敏感信息时，写&quot;见 raw/private/对应原文&quot;，不复制内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 工具链与脚本&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件扫描脚本&lt;/strong&gt;：统计变更、识别主题&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import os
from pathlib import Path

def scan_vault(vault_path):
    stats = {
        &apos;total_files&apos;: 0,
        &apos;blog_articles&apos;: 0,
        &apos;wiki_pages&apos;: 0,
        &apos;raw_files&apos;: 0,
        &apos;recent_changes&apos;: []
    }
    
    for root, dirs, files in os.walk(vault_path):
        for file in files:
            if file.endswith(&apos;.md&apos;):
                stats[&apos;total_files&apos;] += 1
                path = Path(root)
                
                if &apos;blog&apos; in str(path):
                    stats[&apos;blog_articles&apos;] += 1
                elif &apos;wiki&apos; in str(path):
                    stats[&apos;wiki_pages&apos;] += 1
                elif &apos;raw&apos; in str(path):
                    stats[&apos;raw_files&apos;] += 1
    
    return stats
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;frontmatter 生成脚本&lt;/strong&gt;：自动化填充模板&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def generate_frontmatter(title, page_type, tags=None, sources=None):
    from datetime import date
    
    frontmatter = f&quot;&quot;&quot;---
title: &quot;{title}&quot;
type: {page_type}
created: {date.today().isoformat()}
updated: {date.today().isoformat()}
tags: {tags or []}
sources: {sources or []}
---
&quot;&quot;&quot;
    return frontmatter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;同步脚本&lt;/strong&gt;：Obsidian 与 Git 的双向同步&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# ~/.hermes/scripts/obsidian-git-sync.sh

VAULT_PATH=&quot;/Users/edward/workspace/edward/Obsidian&quot;
BLOG_PATH=&quot;$VAULT_PATH/blog&quot;
REMOTE_BLOG=&quot;/Users/edward/workspace/edward/edwardchen.com/src/content/blog&quot;

# 同步 blog 目录到博客项目
rsync -av --delete &quot;$BLOG_PATH/&quot; &quot;$REMOTE_BLOG/&quot;

# 提交到博客 Git 仓库
cd &quot;$REMOTE_BLOG/..&quot;
git add src/content/blog/
git commit -m &quot;同步博客文章 $(date +%Y-%m-%d)&quot;
git push origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 效果对比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;重构前&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ 碎片化：相同主题散落在多个文件&lt;/li&gt;
&lt;li&gt;❌ 难以维护：不知道哪些笔记是最新的&lt;/li&gt;
&lt;li&gt;❌ AI 读取困难：缺乏结构，AI 无法可靠提取信息&lt;/li&gt;
&lt;li&gt;❌ 无审计：变更历史分散在 Git commit，难以追踪&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;重构后&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 结构化：每个主题一个页面，信息集中&lt;/li&gt;
&lt;li&gt;✅ 可维护：frontmatter 记录更新时间，log.md 记录变更&lt;/li&gt;
&lt;li&gt;✅ Agent 友好：明确的读取协议，AI 能可靠维护&lt;/li&gt;
&lt;li&gt;✅ 可审计：Git + log.md 双重记录，变更可追溯&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具链与自动化&lt;/h2&gt;
&lt;h3&gt;6.1 核心工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt;：版本控制与变更追踪的基础。关键配置：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .gitignore 示例
raw/private/
*.tmp
.DS_Store
blog/.gitignore  # blog 目录有自己的 Git 仓库
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Python 脚本&lt;/strong&gt;：批量处理、frontmatter 生成、相似度检测。我常用的库：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pathlib import Path  # 文件操作
import yaml  # frontmatter 解析
from datetime import date  # 日期处理
from difflib import SequenceMatcher  # 相似度检测
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Obsidian 插件&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dataview&lt;/strong&gt;：查询 frontmatter，生成动态列表&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templater&lt;/strong&gt;：模板填充，自动化 frontmatter 生成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obsidian Git&lt;/strong&gt;：自动提交、定时同步&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 自动化 Pipeline&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;每日同步&lt;/strong&gt;：raw/ 目录的自动扫描&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ~/.hermes/scripts/daily-scan.sh
#!/bin/bash
python3 ~/.hermes/scripts/vault-scanner.py \
    --vault /Users/edward/workspace/edward/Obsidian \
    --output /tmp/vault-stats.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;定期审计&lt;/strong&gt;：frontmatter 完整性检查&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# check_frontmatter.py
def check_frontmatter(file_path):
    required_fields = [&apos;title&apos;, &apos;type&apos;, &apos;created&apos;, &apos;updated&apos;, &apos;tags&apos;]
    issues = []
    
    with open(file_path, &apos;r&apos;) as f:
        content = f.read()
    
    if not content.startswith(&apos;---&apos;):
        issues.append(&quot;缺少 frontmatter&quot;)
        return issues
    
    # 解析 frontmatter 并检查必填字段
    # ...
    
    return issues
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;变更通知&lt;/strong&gt;：Git hook 触发提醒&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .git/hooks/post-commit
#!/bin/bash
echo &quot;Wiki 页面已更新，请检查 log.md 是否需要记录&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 推荐插件与配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Obsidian 插件清单&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dataview&lt;/strong&gt;：查询和展示 frontmatter 数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templater&lt;/strong&gt;：模板填充，支持 JavaScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Obsidian Git&lt;/strong&gt;：自动提交、定时同步&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advanced Tables&lt;/strong&gt;：表格编辑增强&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown Checklist&lt;/strong&gt;：任务列表管理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Git 配置模板&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ~/.gitconfig
[user]
    name = Your Name
    email = your.email@example.com

[init]
    defaultBranch = main

[push]
    default = current
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Python 脚本示例&lt;/strong&gt;：批量更新 frontmatter&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/env python3
&quot;&quot;&quot;批量为 wiki 页面添加或更新 frontmatter&quot;&quot;&quot;

import yaml
from pathlib import Path
from datetime import date

def update_frontmatter(vault_path):
    wiki_path = Path(vault_path) / &apos;wiki&apos;
    
    for md_file in wiki_path.rglob(&apos;*.md&apos;):
        content = md_file.read_text()
        
        if not content.startswith(&apos;---&apos;):
            # 添加 frontmatter
            title = md_file.stem.replace(&apos;-&apos;, &apos; &apos;).title()
            frontmatter = {
                &apos;title&apos;: title,
                &apos;type&apos;: &apos;concept&apos;,
                &apos;created&apos;: date.today().isoformat(),
                &apos;updated&apos;: date.today().isoformat(),
                &apos;tags&apos;: []
            }
            new_content = &apos;---\n&apos; + yaml.dump(frontmatter) + &apos;---\n\n&apos; + content
            md_file.write_text(new_content)
            print(f&quot;已添加 frontmatter: {md_file}&quot;)

if __name__ == &apos;__main__&apos;:
    update_frontmatter(&apos;/Users/edward/workspace/edward/Obsidian&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见问题与最佳实践&lt;/h2&gt;
&lt;h3&gt;7.1 常见陷阱&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;过度结构化&lt;/strong&gt;：为了结构化而结构化，牺牲了灵活性。有些内容确实不适合 Wiki 模式（如灵感碎片、临时记录），强行结构化只会增加负担。&lt;strong&gt;解决方案&lt;/strong&gt;：明确 raw/ 的边界，允许碎片化内容存在，只在需要时抽取到 wiki/。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;忽略人工审阅&lt;/strong&gt;：相信自动化能解决所有问题。frontmatter 生成、内容抽取、去重检测都有出错的可能，尤其是边界情况。&lt;strong&gt;解决方案&lt;/strong&gt;：关键页面必须人工审阅，建立&quot;自动化建议 + 人工确认&quot;的流程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;边界模糊&lt;/strong&gt;：raw/ 与 wiki/ 混用，有些文件不知道该放哪里。&lt;strong&gt;解决方案&lt;/strong&gt;：制定明确的规则（如 AGENTS.md），并定期检查。我的规则是：如果内容需要被多处引用、需要 AI 维护、需要长期保存，就进 wiki/；否则进 raw/。&lt;/p&gt;
&lt;h3&gt;7.2 最佳实践清单&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;每周 review&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 检查 raw/ 目录的新文件&lt;/li&gt;
&lt;li&gt;[ ] 识别哪些内容应该抽取到 wiki/&lt;/li&gt;
&lt;li&gt;[ ] 更新 log.md 记录本周变更&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;月度审计&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] frontmatter 完整性扫描&lt;/li&gt;
&lt;li&gt;[ ] 内部链接有效性检查&lt;/li&gt;
&lt;li&gt;[ ] 标签一致性审查&lt;/li&gt;
&lt;li&gt;[ ] 删除或归档超过 60 天未更新的临时笔记&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;季度清理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] archive/ 旧版本归档&lt;/li&gt;
&lt;li&gt;[ ] 检查 private/ 目录是否有需要清理的敏感信息&lt;/li&gt;
&lt;li&gt;[ ] 评估 Wiki 模式的有效性，调整规则&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.3 团队协作建议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;角色分工&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内容生产者&lt;/strong&gt;：负责 raw/ 目录的日常记录&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;知识工程师&lt;/strong&gt;：负责抽取、结构化、wiki/ 维护&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审阅者&lt;/strong&gt;：负责关键页面的人工审阅&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计员&lt;/strong&gt;：负责定期审计和清理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;冲突解决&lt;/strong&gt;：多人编辑同一页面时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Git 分支策略：每人一个分支，合并时解决冲突&lt;/li&gt;
&lt;li&gt;锁定机制：编辑前在 log.md 标注&quot;正在编辑：XXX&quot;&lt;/li&gt;
&lt;li&gt;沟通渠道：Slack/Discord 频道同步编辑计划&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;权限管理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw/private/&lt;/code&gt;：仅核心成员可访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wiki/&lt;/code&gt;：全员可读，核心成员可写&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blog/&lt;/code&gt;：独立仓库，发布流程需审阅&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结与资源&lt;/h2&gt;
&lt;h3&gt;8.1 核心要点回顾&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Wiki 模式的三大原则&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;结构化&lt;/strong&gt;：每个主题一个页面，页面内包含完整上下文&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可审计&lt;/strong&gt;：frontmatter + 变更日志，追踪知识演变&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent 友好&lt;/strong&gt;：明确的读取与更新协议，AI 能可靠维护&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;重构流程的四个阶段&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;准备&lt;/strong&gt;：现状评估、工具准备、备份策略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抽取&lt;/strong&gt;：主题识别、内容抽取、冲突处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化&lt;/strong&gt;：模板填充、去重检测、引用规范化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证&lt;/strong&gt;：完整性检查、人工审阅、发布流程&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;工具链的关键组件&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Git：版本控制与变更追踪&lt;/li&gt;
&lt;li&gt;Python 脚本：批量处理、frontmatter 生成&lt;/li&gt;
&lt;li&gt;Obsidian 插件：Dataview、Templater、Obsidian Git&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.2 延伸阅读&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://x.com/karpathy&quot;&gt;Andrej Karpathy 的 LLM Wiki 原始推文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;wiki/AGENTS.md&quot;&gt;AGENTS.md 标准文档&lt;/a&gt;（本库）&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.obsidian.md/Plugins/Dataview&quot;&gt;Obsidian Dataview 插件文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://silentvoid13.github.io/Templater/&quot;&gt;Templater 插件教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 下一步行动&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;评估自己的笔记库状态&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 统计文件数量和分布
find ~/Obsidian -name &quot;*.md&quot; | wc -l
find ~/Obsidian -name &quot;*.md&quot; -path &quot;*/blog/*&quot; | wc -l
find ~/Obsidian -name &quot;*.md&quot; -path &quot;*/wiki/*&quot; | wc -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;选择一个主题试跑重构流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找一个你经常写的主题（如&quot;部署&quot;、&quot;LangGraph&quot;）&lt;/li&gt;
&lt;li&gt;收集所有相关笔记&lt;/li&gt;
&lt;li&gt;抽取到一个 wiki 页面&lt;/li&gt;
&lt;li&gt;添加 frontmatter 和相关链接&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;建立自动化 pipeline&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;配置 Git 和 .gitignore&lt;/li&gt;
&lt;li&gt;编写或下载 frontmatter 生成脚本&lt;/li&gt;
&lt;li&gt;设置每日/每周的扫描和审计任务&lt;/li&gt;
&lt;li&gt;在 Obsidian 中安装推荐插件&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;校验&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x] 没有真实凭据、客户内网地址或未脱敏材料&lt;/li&gt;
&lt;li&gt;[x] 引用了对应 wiki/source 材料（AGENTS.md、本次重构经验）&lt;/li&gt;
&lt;li&gt;[x] 更新了相关 wiki 页面或 index（需在发布前完成）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;相关文章&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[[LangGraph 复杂状态机设计模式]]&lt;/li&gt;
&lt;li&gt;[[SSH 跳板机端口穿透进阶]]&lt;/li&gt;
&lt;li&gt;[[Obsidian 博客与知识库分仓同步实践]]&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Obsidian</category><category>知识库</category><category>LLM Wiki</category><category>Karpathy</category><category>RAG</category><category>知识管理</category><category>Agent</category></item><item><title>SSH 跳板机端口穿透：当 ProxyJump、ssh -L 和 SOCKS5 都不可用时</title><link>https://heyedwardchen.com/blog/ssh-bastion-port-forwarding-without-tcp-forwarding/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/ssh-bastion-port-forwarding-without-tcp-forwarding/</guid><description>记录一种在服务器禁止 SSH TCP forwarding 时仍可通过跳板机访问目标服务的实用方案：ProxyCommand + nc 登录，socat + SSH ControlMaster 做固定端口穿透。</description><pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SSH 跳板机端口穿透：当 ProxyJump、ssh -L 和 SOCKS5 都不可用时&lt;/h1&gt;
&lt;p&gt;在内网运维场景里，经常会遇到这种链路：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;本机 -&gt; 跳板机 -&gt; 目标机
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本机不能直接访问目标机，只能先登录跳板机，再从跳板机访问目标机。通常我们会优先使用 &lt;code&gt;ProxyJump&lt;/code&gt;、&lt;code&gt;ssh -L&lt;/code&gt; 或 &lt;code&gt;ssh -D&lt;/code&gt; SOCKS5 代理。但如果跳板机或目标机的 sshd 禁止 TCP forwarding，这些标准方案会直接失败。&lt;/p&gt;
&lt;p&gt;这篇文章记录一个更朴素但实用的方案：用 &lt;code&gt;ProxyCommand + nc&lt;/code&gt; 完成 SSH 登录，再用 &lt;code&gt;socat + ssh + nc&lt;/code&gt; 穿透少量固定端口。它不需要修改服务器 sshd 配置，适合权限受限但可以正常 SSH 登录的环境。&lt;/p&gt;
&lt;p&gt;示例里的 IP、用户和别名均为脱敏占位，不对应真实环境。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 基础 SSH 链路&lt;/h2&gt;
&lt;p&gt;先给跳板机配置普通 SSH Host：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;Host bastion-demo
    HostName 192.0.2.10
    User bastion-user
    IdentityFile ~/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标机通过跳板机上的 &lt;code&gt;nc&lt;/code&gt; 建立 TCP 连接：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;Host target-demo
    HostName 198.51.100.20
    User app-user
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand ssh bastion-demo nc %h %p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证登录：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这条链路的关键点是：&lt;code&gt;ProxyCommand&lt;/code&gt; 不走 SSH 的 &lt;code&gt;direct-tcpip&lt;/code&gt; channel，而是在跳板机上执行 &lt;code&gt;nc &amp;#x3C;target-host&gt; &amp;#x3C;target-port&gt;&lt;/code&gt;。因此它可以绕过一部分禁止 &lt;code&gt;ProxyJump&lt;/code&gt; 的环境。&lt;/p&gt;
&lt;p&gt;要求很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本机私钥能登录跳板机。&lt;/li&gt;
&lt;li&gt;本机私钥或本机公钥授权能登录目标机。&lt;/li&gt;
&lt;li&gt;跳板机上存在 &lt;code&gt;nc&lt;/code&gt;、&lt;code&gt;ncat&lt;/code&gt; 或 &lt;code&gt;socat&lt;/code&gt; 之一。&lt;/li&gt;
&lt;li&gt;SSH Host 别名建议只用 ASCII。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. 常见失败现象&lt;/h2&gt;
&lt;h3&gt;2.1 中文或特殊字符别名导致 hostname 报错&lt;/h3&gt;
&lt;p&gt;如果执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh 某个中文别名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可能会报：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hostname contains invalid characters
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;处理方式很直接：SSH Host 别名使用 ASCII，中文写到注释或文档里。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;project-bastion
project-target
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 ProxyJump 被服务端拒绝&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ProxyJump&lt;/code&gt; 失败时常见报错：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;channel 0: open failed: administratively prohibited: open failed
stdio forwarding failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原因是 &lt;code&gt;ProxyJump&lt;/code&gt; 依赖 SSH &lt;code&gt;direct-tcpip&lt;/code&gt; channel。如果跳板机 sshd 禁止 TCP forwarding，就会被服务端拒绝。&lt;/p&gt;
&lt;p&gt;替代配置：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ssh-config&quot;&gt;ProxyCommand ssh bastion-demo nc %h %p
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 SOCKS5 或 ssh -L 被目标机拒绝&lt;/h3&gt;
&lt;p&gt;如果尝试：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N -D 127.0.0.1:1080 target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N -L 18080:127.0.0.1:8080 target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;遇到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;channel 1: open failed: administratively prohibited: open failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常是目标机 sshd 禁止 TCP forwarding。&lt;/p&gt;
&lt;p&gt;可以在目标机检查：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sshd -T 2&gt;/dev/null | egrep &apos;^(allowtcpforwarding|disableforwarding|permitopen|permitlisten)&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果看到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allowtcpforwarding no
disableforwarding no
permitopen any
permitlisten any
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么 &lt;code&gt;ssh -D&lt;/code&gt; SOCKS5 和 &lt;code&gt;ssh -L&lt;/code&gt; 基本都不能用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 固定端口穿透方案&lt;/h2&gt;
&lt;p&gt;当 SSH forwarding 被禁时，可以在本机用 &lt;code&gt;socat&lt;/code&gt; 监听一个本地端口。每次有浏览器连接进来，就执行一次：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh target-demo nc 127.0.0.1 &amp;#x3C;remote-port&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通用模板：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;socat TCP-LISTEN:&amp;#x3C;local-port&gt;,bind=127.0.0.1,reuseaddr,fork EXEC:&apos;ssh &amp;#x3C;target-alias&gt; nc &amp;#x3C;remote-host&gt; &amp;#x3C;remote-port&gt;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 &lt;code&gt;socat&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install socat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例：把本机 &lt;code&gt;18080&lt;/code&gt; 转到目标机本地 &lt;code&gt;8080&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;socat TCP-LISTEN:18080,bind=127.0.0.1,reuseaddr,fork EXEC:&apos;ssh target-demo nc 127.0.0.1 8080&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器访问：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;http://127.0.0.1:18080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果目标服务监听的是内网 IP 而不是 &lt;code&gt;127.0.0.1&lt;/code&gt;，把命令里的 &lt;code&gt;127.0.0.1&lt;/code&gt; 换成对应地址即可。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 稳定性增强：复用 SSH 连接&lt;/h2&gt;
&lt;p&gt;上面的原始命令有一个问题：浏览器的每个 TCP 连接都会新建一次 SSH 登录。页面资源多时，连接会明显抖动。&lt;/p&gt;
&lt;p&gt;可以先建立 SSH master 连接：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p ~/.ssh/cm
chmod 700 ~/.ssh/cm

ssh -MNf \
  -S ~/.ssh/cm/target-demo \
  -o ControlMaster=yes \
  -o ControlPersist=2h \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再让 &lt;code&gt;socat&lt;/code&gt; 复用这个 master：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;socat TCP-LISTEN:18080,bind=127.0.0.1,reuseaddr,fork,keepalive EXEC:&apos;ssh -T -S ~/.ssh/cm/target-demo -o BatchMode=yes -o LogLevel=ERROR target-demo nc 127.0.0.1 8080&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 master 状态：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -S ~/.ssh/cm/target-demo -O check target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关闭 master：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -S ~/.ssh/cm/target-demo -O exit target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个版本仍然不是完整 VPN，也不是 SOCKS5，但对少量固定 Web 服务已经足够稳定。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 什么时候不该用这个方案&lt;/h2&gt;
&lt;p&gt;这个方案适合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只需要访问少量固定端口。&lt;/li&gt;
&lt;li&gt;不能修改 sshd 配置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProxyJump&lt;/code&gt;、&lt;code&gt;ssh -L&lt;/code&gt;、&lt;code&gt;ssh -D&lt;/code&gt; 被服务端策略禁止。&lt;/li&gt;
&lt;li&gt;跳板机能访问目标机，且跳板机上有 &lt;code&gt;nc&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不适合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要访问大量动态端口。&lt;/li&gt;
&lt;li&gt;需要全系统透明代理。&lt;/li&gt;
&lt;li&gt;需要长期高并发访问。&lt;/li&gt;
&lt;li&gt;能让管理员开启 &lt;code&gt;AllowTcpForwarding yes&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果管理员允许 TCP forwarding，优先使用标准方案：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N -L 18080:127.0.0.1:8080 target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -N -D 127.0.0.1:1080 target-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;标准方案更简单，也更稳定。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 总结&lt;/h2&gt;
&lt;p&gt;当 SSH TCP forwarding 被禁时，不必一上来就改服务器配置或搭 VPN。对少量固定端口，可以用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ProxyCommand + nc
socat + ssh + nc
ControlMaster 复用连接
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这套组合足够小，也足够实用。它的边界也很清楚：适合临时访问固定端口，不适合替代正式 VPN 或标准 SSH forwarding。&lt;/p&gt;
</content:encoded><category>SSH</category><category>跳板机</category><category>端口转发</category><category>运维</category></item><item><title>LLM 在垂直领域的微调策略</title><link>https://heyedwardchen.com/blog/llm-fine-tuning-strategies/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/llm-fine-tuning-strategies/</guid><description>深入探讨 LoRA、QLoRA、P-Tuning 等微调技术的原理与实战，从数学推导到代码实现全面解析。</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;LLM 在垂直领域的微调策略&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;摘要&lt;/strong&gt;：本文深入探讨 LLM 在垂直领域的微调策略，通过技术原理、代码示例和实战案例，带你全面掌握 LoRA、QLoRA、P-Tuning 等参数高效微调技术。从数学推导到工程实现，从方法对比到效果评估，为你提供完整的技术指南。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;1. 问题背景与挑战&lt;/h2&gt;
&lt;h3&gt;1.1 为什么需要垂直领域微调？&lt;/h3&gt;
&lt;p&gt;大语言模型 (LLM) 在通用任务上表现出色，但在垂直领域往往面临以下挑战：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;领域知识缺失&lt;/strong&gt;：通用模型缺乏医疗、法律、金融等专业领域的知识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;术语理解偏差&lt;/strong&gt;：专业术语的含义可能与通用语境不同&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;格式要求严格&lt;/strong&gt;：垂直领域对输出格式有特定要求（如医疗诊断报告）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合规性要求&lt;/strong&gt;：某些行业对 AI 输出有严格的合规要求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例场景&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;通用模型：患者发烧了，建议多喝水休息。
医疗领域模型：患者体温 38.5°C，建议服用对乙酰氨基酚 500mg，每 6 小时一次，
              同时监测体温变化，如持续高烧需及时就医。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 微调的挑战&lt;/h3&gt;
&lt;p&gt;传统的&lt;strong&gt;全量微调&lt;/strong&gt;面临巨大挑战：&lt;/p&gt;
&lt;p&gt;| 挑战 | 说明 | 影响 |
|------|------|------|
| 计算成本高 | 需要更新所有参数 (数十亿) | 需要多张 A100 GPU |
| 存储需求大 | 每个微调版本占用数十 GB | 难以维护多个版本 |
| 灾难性遗忘 | 学习新知识时遗忘旧知识 | 通用能力下降 |
| 训练时间长 | 完整 epoch 需要数天 | 迭代周期长 |&lt;/p&gt;
&lt;p&gt;以 LLaMA-7B 为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参数数量：70 亿&lt;/li&gt;
&lt;li&gt;全量微调显存：~140GB (需要多卡)&lt;/li&gt;
&lt;li&gt;训练时间：~3 天 (单 epoch)&lt;/li&gt;
&lt;li&gt;模型存储：~14GB (FP16)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.3 参数高效微调 (PEFT) 的兴起&lt;/h3&gt;
&lt;p&gt;为了解决上述问题，&lt;strong&gt;参数高效微调 (Parameter-Efficient Fine-Tuning, PEFT)&lt;/strong&gt; 应运而生：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：冻结预训练模型参数，只训练少量额外参数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优势&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;显存占用降低 10-50 倍&lt;/li&gt;
&lt;li&gt;训练速度提升 5-10 倍&lt;/li&gt;
&lt;li&gt;避免灾难性遗忘&lt;/li&gt;
&lt;li&gt;可以维护多个任务适配器&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. 微调方法分类与对比&lt;/h2&gt;
&lt;h3&gt;2.1 方法分类&lt;/h3&gt;
&lt;p&gt;PEFT 方法主要可以分为以下几类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;参数高效微调 (PEFT)
├── 添加参数方法
│   ├── LoRA (Low-Rank Adaptation)
│   ├── AdaLoRA (Adaptive LoRA)
│   └── DoRA (Weight-Decomposed LoRA)
├── 提示学习方法
│   ├── Prefix-Tuning
│   ├── P-Tuning v2
│   └── Prompt Tuning
├── 适配器方法
│   ├── Adapter
│   ├── Parallel Adapter
│   └── Compacter
└── 量化方法
    ├── QLoRA
    └ ├── BitFit
    └ └── LLAMA-Factory
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 核心方法对比&lt;/h3&gt;
&lt;p&gt;| 方法 | 可训练参数 | 显存占用 | 推理速度 | 效果 | 适用场景 |
|------|-----------|---------|---------|------|---------|
| 全量微调 | 100% | 100% | 基准 | 最优 | 资源充足 |
| LoRA | 0.1-1% | 20-30% | -1-3% | 接近全量 | 通用场景 |
| QLoRA | 0.1-1% | 5-10% | -1-3% | 接近 LoRA | 资源受限 |
| Prefix-Tuning | 0.01-0.1% | 10-20% | -5-10% | 中等 | 特定任务 |
| Adapter | 0.5-2% | 30-40% | -3-5% | 良好 | 多任务 |&lt;/p&gt;
&lt;h3&gt;2.3 选择建议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;根据资源选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单张消费级 GPU (24GB)：QLoRA&lt;/li&gt;
&lt;li&gt;单张专业 GPU (40-80GB)：LoRA&lt;/li&gt;
&lt;li&gt;多卡集群：全量微调或 LoRA&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;根据任务选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本生成：LoRA / QLoRA&lt;/li&gt;
&lt;li&gt;分类任务：Adapter / Prefix-Tuning&lt;/li&gt;
&lt;li&gt;多任务学习：Adapter&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. LoRA 原理与实现&lt;/h2&gt;
&lt;h3&gt;3.1 核心思想&lt;/h3&gt;
&lt;p&gt;LoRA (Low-Rank Adaptation) 的核心假设：&lt;strong&gt;模型更新的低秩性&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;传统微调：ΔW ∈ R^(m×n)  (需要更新 m×n 个参数)
LoRA:     ΔW = BA, 其中 B ∈ R^(m×r), A ∈ R^(r×n), r &amp;#x3C;&amp;#x3C; min(m,n)
         (只需要更新 r×(m+n) 个参数)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 数学推导&lt;/h3&gt;
&lt;p&gt;给定预训练权重 $W_0 \in \mathbb{R}^{m \times n}$，传统微调学习 $W = W_0 + \Delta W$。&lt;/p&gt;
&lt;p&gt;LoRA 假设 $\Delta W$ 是低秩的，可以分解为：&lt;/p&gt;
&lt;p&gt;$$\Delta W = BA$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$B \in \mathbb{R}^{m \times r}$&lt;/li&gt;
&lt;li&gt;$A \in \mathbb{R}^{r \times n}$&lt;/li&gt;
&lt;li&gt;$r \ll \min(m, n)$ (通常 r=8, 16, 32, 64)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前向传播：
$$h = W_0 x + \Delta W x = W_0 x + BAx$$&lt;/p&gt;
&lt;h3&gt;3.3 参数对比&lt;/h3&gt;
&lt;p&gt;以 LLaMA-7B 的 Attention 层为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$W_q \in \mathbb{R}^{4096 \times 4096}$&lt;/li&gt;
&lt;li&gt;传统微调：4096 × 4096 = &lt;strong&gt;16.7M 参数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;LoRA (r=8): 8 × (4096 + 4096) = &lt;strong&gt;65.5K 参数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数减少：99.6%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4 代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
import torch.nn as nn
from typing import Optional

class LoRALayer(nn.Module):
    def __init__(
        self, 
        in_features: int, 
        out_features: int, 
        rank: int = 8,
        alpha: float = 16.0
    ):
        super().__init__()
        self.rank = rank
        self.alpha = alpha
        self.scaling = alpha / rank
        
        # LoRA 分解：W = W0 + BA
        self.lora_A = nn.Linear(in_features, rank, bias=False)
        self.lora_B = nn.Linear(rank, out_features, bias=False)
        
        # 初始化
        nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5))
        nn.init.zeros_(self.lora_B.weight)
    
    def forward(self, x: torch.Tensor) -&gt; torch.Tensor:
        # 原始权重计算 (冻结)
        # original_output = self.original_weight @ x
        
        # LoRA 更新
        lora_output = self.lora_B(self.lora_A(x)) * self.scaling
        
        return lora_output

# 使用示例
class LLaMAModelWithLoRA(nn.Module):
    def __init__(self, base_model, rank=8):
        super().__init__()
        self.base_model = base_model  # 冻结的预训练模型
        self.base_model.requires_grad_(False)
        
        # 为 Attention 层添加 LoRA
        for layer in self.base_model.layers:
            layer.attn.q_proj.lora = LoRALayer(
                in_features=4096,
                out_features=4096,
                rank=rank
            )
    
    def forward(self, x):
        outputs = self.base_model(x)
        # 添加 LoRA 输出
        # ...
        return outputs
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 训练技巧&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;初始化策略&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A 矩阵：Kaiming 均匀初始化&lt;/li&gt;
&lt;li&gt;B 矩阵：零初始化&lt;/li&gt;
&lt;li&gt;原因：训练开始时 LoRA 影响为 0，逐渐学习更新&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;秩的选择&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 秩选择经验法则
rank = 8    # 资源受限，快速实验
rank = 16   # 平衡性能与资源 (推荐)
rank = 64   # 追求最优效果
rank = 128  # 接近全量微调效果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Target 模块选择&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 推荐配置 (PEFT 库)
target_modules = [
    &quot;q_proj&quot;,  # Query 投影 (必选)
    &quot;v_proj&quot;,  # Value 投影 (推荐)
    &quot;k_proj&quot;,  # Key 投影 (可选)
    &quot;o_proj&quot;,  # Output 投影 (可选)
    &quot;gate_proj&quot;,  # MLP Gate (可选)
    &quot;up_proj&quot;,    # MLP Up (可选)
    &quot;down_proj&quot;   # MLP Down (可选)
]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. QLoRA 量化微调&lt;/h2&gt;
&lt;h3&gt;4.1 核心创新&lt;/h3&gt;
&lt;p&gt;QLoRA (Quantized LoRA) 在 LoRA 基础上引入&lt;strong&gt;4 比特量化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;QLoRA = 4-bit 量化基模型 + NF4 数据类型 + 双层量化 + LoRA
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 关键技术&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. NF4 数据类型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;针对正态分布优化的 4 比特数据类型：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# NF4 的 16 个离散值 (近似正态分布)
nf4_values = [
    -2.597, -2.355, -1.955, -1.610,
    -1.230, -0.820, -0.380, 0.076,
     0.437, 0.830, 1.260, 1.700,
     2.190, 2.760, 3.420, 4.250
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 双层量化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;第一层&lt;/strong&gt;：权重量化到 4-bit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二层&lt;/strong&gt;：量化参数 (scale, zero_point) 也量化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 分页优化器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;避免峰值显存占用，将优化器状态分页存储。&lt;/p&gt;
&lt;h3&gt;4.3 显存对比&lt;/h3&gt;
&lt;p&gt;| 方法 | 模型 | 显存占用 | 对比 |
|------|------|---------|------|
| 全量微调 | LLaMA-65B | 1.3TB | 基准 |
| LoRA | LLaMA-65B | 240GB | 5.4x |
| &lt;strong&gt;QLoRA&lt;/strong&gt; | &lt;strong&gt;LLaMA-65B&lt;/strong&gt; | &lt;strong&gt;48GB&lt;/strong&gt; | &lt;strong&gt;27x&lt;/strong&gt; |&lt;/p&gt;
&lt;h3&gt;4.4 代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
import torch

# 1. 配置 4-bit 量化
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,  # 双层量化
    bnb_4bit_quant_type=&quot;nf4&quot;,        # NF4 数据类型
    bnb_4bit_compute_dtype=torch.float16  # 计算精度
)

# 2. 加载量化模型
model = AutoModelForCausalLM.from_pretrained(
    &quot;meta-llama/Llama-2-65b-hf&quot;,
    quantization_config=quantization_config,
    device_map=&quot;auto&quot;
)

# 3. 配置 LoRA
lora_config = LoraConfig(
    r=16,  # 秩
    lora_alpha=32,
    target_modules=[&quot;q_proj&quot;, &quot;v_proj&quot;, &quot;k_proj&quot;, &quot;o_proj&quot;],
    lora_dropout=0.1,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;
)

# 4. 应用 LoRA
model = get_peft_model(model, lora_config)

# 5. 验证可训练参数
model.print_trainable_parameters()
# 输出：trainable params: 2097152 || all params: 67289753600 || trainable%: 0.0031%
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.5 训练建议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;批次大小&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# QLoRA 可以使用更大的批次
per_device_train_batch_size = 4  # LoRA 通常只能 1-2
gradient_accumulation_steps = 8
# 有效批次大小 = 4 × 8 = 32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;学习率&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;learning_rate = 2e-4  # QLoRA 推荐学习率
# 比全量微调高 10-100 倍
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 其他 PEFT 方法&lt;/h2&gt;
&lt;h3&gt;5.1 Prefix-Tuning&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：在输入前添加可学习的 prefix 向量&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class PrefixTuning(nn.Module):
    def __init__(self, n_prefix_tokens=10, d_model=4096):
        super().__init__()
        self.prefix = nn.Parameter(torch.randn(n_prefix_tokens, d_model))
    
    def forward(self, input_ids, attention_mask):
        # 获取 prefix 嵌入
        prefix_embeds = self.prefix.unsqueeze(0).expand(input_ids.size(0), -1, -1)
        
        # 拼接 prefix
        embeddings = self.model.embeddings(input_ids)
        embeddings = torch.cat([prefix_embeds, embeddings], dim=1)
        
        return self.model(embeddings)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本分类&lt;/li&gt;
&lt;li&gt;情感分析&lt;/li&gt;
&lt;li&gt;问答任务&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 P-Tuning v2&lt;/h3&gt;
&lt;p&gt;Prefix-Tuning 的改进版本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在所有层添加 prefix（不只是输入层）&lt;/li&gt;
&lt;li&gt;添加 MLP 投影层&lt;/li&gt;
&lt;li&gt;支持更多任务类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 Adapter&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：在 Transformer 层之间插入小型神经网络&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class Adapter(nn.Module):
    def __init__(self, d_model=4096, downscale=8):
        super().__init__()
        down_dim = d_model // downscale
        
        self.down_proj = nn.Linear(d_model, down_dim)
        self.up_proj = nn.Linear(down_dim, d_model)
        self.non_linearity = nn.GELU()
    
    def forward(self, x, residual):
        # Adapter 输出
        adapter_output = self.up_proj(self.non_linearity(self.down_proj(x)))
        
        # 残差连接
        return residual + adapter_output
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 方法选择建议&lt;/h3&gt;
&lt;p&gt;| 任务类型 | 推荐方法 | 理由 |
|---------|---------|------|
| 文本生成 | LoRA / QLoRA | 效果好，资源占用低 |
| 文本分类 | Prefix-Tuning | 参数少，训练快 |
| 多任务 | Adapter | 可独立加载不同任务适配器 |
| 资源受限 | QLoRA | 单卡 24GB 可微调 65B 模型 |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 实战案例：垂直领域微调&lt;/h2&gt;
&lt;h3&gt;6.1 案例背景&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：医疗问答系统&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基模型：LLaMA-2-7B&lt;/li&gt;
&lt;li&gt;数据：10,000 条医疗问答对&lt;/li&gt;
&lt;li&gt;目标：提升医疗专业知识准确性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 数据准备&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 数据格式
medical_data = [
    {
        &quot;question&quot;: &quot;患者体温 38.5°C，应该怎么办？&quot;,
        &quot;answer&quot;: &quot;建议服用对乙酰氨基酚 500mg，每 6 小时一次...&quot;
    },
    # ... 更多数据
]

# 转换为训练格式
def format_sample(sample):
    return f&quot;&quot;&quot;### 问题：
{sample[&apos;question&apos;]}

### 回答：
{sample[&apos;answer&apos;]}&quot;&quot;&quot;

formatted_data = [format_sample(s) for s in medical_data]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 完整训练代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from transformers import (
    AutoTokenizer, 
    TrainingArguments, 
    Trainer
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
import torch

# 1. 加载模型和分词器
model_name = &quot;meta-llama/Llama-2-7b-hf&quot;
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# 2. 加载量化模型
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type=&quot;nf4&quot;,
    bnb_4bit_compute_dtype=torch.float16
)

model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map=&quot;auto&quot;,
    trust_remote_code=True
)

# 3. 准备模型 for k-bit 训练
model = prepare_model_for_kbit_training(model)

# 4. 配置 LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=[&quot;q_proj&quot;, &quot;k_proj&quot;, &quot;v_proj&quot;, &quot;o_proj&quot;],
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# 5. 数据加载
from datasets import Dataset
dataset = Dataset.from_list([{&quot;text&quot;: t} for t in formatted_data])

def preprocess_function(examples):
    return tokenizer(
        examples[&quot;text&quot;],
        padding=&quot;max_length&quot;,
        truncation=True,
        max_length=512
    )

tokenized_dataset = dataset.map(preprocess_function, batched=True)

# 6. 训练配置
training_args = TrainingArguments(
    output_dir=&quot;./medical-lora&quot;,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    num_train_epochs=3,
    fp16=True,
    logging_steps=10,
    save_strategy=&quot;epoch&quot;,
    evaluation_strategy=&quot;no&quot;,
    optim=&quot;adamw_8bit&quot;
)

# 7. 训练
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
)

trainer.train()

# 8. 保存模型
model.save_pretrained(&quot;./medical-lora-final&quot;)
tokenizer.save_pretrained(&quot;./medical-lora-final&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.4 推理使用&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from peft import PeftModel

# 加载基模型
base_model = AutoModelForCausalLM.from_pretrained(
    &quot;meta-llama/Llama-2-7b-hf&quot;,
    device_map=&quot;auto&quot;
)

# 加载 LoRA 权重
model = PeftModel.from_pretrained(
    base_model,
    &quot;./medical-lora-final&quot;
)

# 推理
def generate_response(question):
    prompt = f&quot;&quot;&quot;### 问题：
{question}

### 回答：&quot;&quot;&quot;
    
    inputs = tokenizer(prompt, return_tensors=&quot;pt&quot;).to(model.device)
    outputs = model.generate(
        **inputs,
        max_new_tokens=256,
        temperature=0.7,
        top_p=0.9
    )
    
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

# 测试
response = generate_response(&quot;患者发烧 39 度怎么办？&quot;)
print(response)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.5 效果评估&lt;/h3&gt;
&lt;p&gt;| 指标 | 基模型 | LoRA 微调 | 提升 |
|------|--------|----------|------|
| 准确率 | 62% | 87% | +25% |
| 专业术语正确率 | 58% | 92% | +34% |
| 格式规范率 | 70% | 95% | +25% |
| 响应时间 | 1.2s | 1.3s | -8% |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7. 效果评估与调优&lt;/h2&gt;
&lt;h3&gt;7.1 评估指标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;自动化指标&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from evaluate import load

# 文本生成质量
bleu = load(&quot;bleu&quot;)
rouge = load(&quot;rouge&quot;)

# 语义相似度
bertscore = load(&quot;bertscore&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;人工评估维度&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;准确性：信息是否正确&lt;/li&gt;
&lt;li&gt;完整性：是否覆盖所有要点&lt;/li&gt;
&lt;li&gt;专业性：术语使用是否准确&lt;/li&gt;
&lt;li&gt;可读性：表达是否清晰&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7.2 超参数调优&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;秩 (r) 的选择&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 实验对比
r_values = [4, 8, 16, 32, 64]
results = {}

for r in r_values:
    model = train_with_lora(r=r)
    score = evaluate(model)
    results[r] = score

# 通常 r=16 或 32 效果最佳
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;学习率调优&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;learning_rates = [1e-4, 2e-4, 5e-4, 1e-3]

# 学习率过低的症状：
# - 训练缓慢
# - 效果不佳

# 学习率过高的症状：
# - 训练不稳定
# - 损失震荡
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Target modules 选择&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 实验配置
configs = [
    [&quot;q_proj&quot;],                    # 最小配置
    [&quot;q_proj&quot;, &quot;v_proj&quot;],          # 推荐配置
    [&quot;q_proj&quot;, &quot;k_proj&quot;, &quot;v_proj&quot;, &quot;o_proj&quot;],  # 完整配置
    [&quot;all-linear&quot;]                 # 所有线性层
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.3 常见问题与解决&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题 1：训练损失不下降&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 解决方案
1. 提高学习率 (2e-4 → 5e-4)
2. 增加秩 r (8 → 16)
3. 检查数据质量
4. 增加训练轮数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题 2：显存溢出&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 解决方案
1. 减小批次大小
2. 启用梯度累积
3. 使用 QLoRA (4-bit)
4. 启用 gradient_checkpointing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题 3：推理速度慢&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 解决方案
1. 合并 LoRA 权重到基模型
2. 使用 ONNX 优化
3. 启用 KV Cache
4. 使用 vLLM 等推理框架
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. 总结与资源&lt;/h2&gt;
&lt;h3&gt;8.1 核心要点回顾&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PEFT 的价值&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显存占用降低 10-50 倍&lt;/li&gt;
&lt;li&gt;训练速度提升 5-10 倍&lt;/li&gt;
&lt;li&gt;避免灾难性遗忘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;方法选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;资源充足：全量微调&lt;/li&gt;
&lt;li&gt;通用场景：LoRA (r=16)&lt;/li&gt;
&lt;li&gt;资源受限：QLoRA (4-bit)&lt;/li&gt;
&lt;li&gt;特定任务：Prefix-Tuning / Adapter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实战建议&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Target modules：至少包含 q_proj, v_proj&lt;/li&gt;
&lt;li&gt;学习率：2e-4 左右&lt;/li&gt;
&lt;li&gt;秩 r：16-32 平衡效果与资源&lt;/li&gt;
&lt;li&gt;数据质量：比数量更重要&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;8.2 未来方向&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自适应秩&lt;/strong&gt;：根据不同层自动调整秩&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态稀疏&lt;/strong&gt;：训练过程中动态调整稀疏模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多任务学习&lt;/strong&gt;：共享基模型，独立任务适配器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续学习&lt;/strong&gt;：避免灾难性遗忘的新方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 学习资源&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;官方文档&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/docs/peft&quot;&gt;PEFT 库文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2106.09685&quot;&gt;LoRA 论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2305.14314&quot;&gt;QLoRA 论文&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LLM</category><category>微调</category><category>LoRA</category><category>QLoRA</category><category>PEFT</category></item><item><title>LangChain 自定义工具开发实战指南</title><link>https://heyedwardchen.com/blog/langchain-custom-tools/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/langchain-custom-tools/</guid><description>深入解析 LangChain 自定义工具的核心原理、开发模式和最佳实践，从零构建企业级 AI 应用工具链</description><pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;LangChain 自定义工具开发实战指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;摘要&lt;/strong&gt;：LangChain 的工具（Tools）机制是构建 AI Agent 的核心能力。本文将深入解析自定义工具的设计模式、源码实现原理、性能优化技巧，并通过实战案例展示如何构建企业级工具链。内容涵盖 Tool 接口设计、Tool 注册与管理、异步工具开发、工具链编排等关键技术点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;1. 背景与动机&lt;/h2&gt;
&lt;h3&gt;1.1 问题描述&lt;/h3&gt;
&lt;p&gt;在构建 LLM 应用时，我们常遇到以下挑战：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;知识时效性限制&lt;/strong&gt;：LLM 的训练数据存在截止日期，无法获取最新信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;领域专业知识&lt;/strong&gt;：模型缺乏特定行业的深度知识和业务逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部系统交互&lt;/strong&gt;：需要与数据库、API、文件系统等外部资源交互&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算能力扩展&lt;/strong&gt;：复杂计算、数据分析等任务超出模型能力范围&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;LangChain 的 Tool 机制正是为解决这些问题而生。通过自定义工具，我们可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;扩展模型的知识边界&lt;/li&gt;
&lt;li&gt;连接企业内部系统&lt;/li&gt;
&lt;li&gt;实现复杂的工作流自动化&lt;/li&gt;
&lt;li&gt;构建智能 Agent 系统&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 应用场景&lt;/h3&gt;
&lt;p&gt;| 场景 | 描述 | 典型工具 |
|------|------|----------|
| &lt;strong&gt;信息检索&lt;/strong&gt; | 实时获取网络、数据库信息 | Search, DatabaseQuery |
| &lt;strong&gt;代码执行&lt;/strong&gt; | 运行 Python 代码、SQL 查询 | PythonREPL, SQLDatabase |
| &lt;strong&gt;API 调用&lt;/strong&gt; | 与第三方服务交互 | HTTPRequest, WeatherAPI |
| &lt;strong&gt;文件操作&lt;/strong&gt; | 读写、处理本地文件 | FileRead, FileWrite |
| &lt;strong&gt;计算分析&lt;/strong&gt; | 数学计算、数据分析 | Calculator, DataAnalyzer |
| &lt;strong&gt;业务系统&lt;/strong&gt; | 企业内部系统对接 | CRM, ERP, OA |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 核心概念&lt;/h2&gt;
&lt;h3&gt;2.1 技术原理&lt;/h3&gt;
&lt;p&gt;LangChain 的工具系统基于 &lt;strong&gt;Function Calling&lt;/strong&gt; 机制实现。其核心原理如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────┐    ┌──────────────────┐    ┌─────────────┐
│   用户输入   │───▶│   LLM + Tool     │───▶│  工具选择   │
│             │    │   Descriptions   │    │             │
└─────────────┘    └──────────────────┘    └──────┬──────┘
                                                   │
                                    ┌──────────────┼──────────────┐
                                    ▼              ▼              ▼
                            ┌──────────┐  ┌──────────┐  ┌──────────┐
                            │ Tool A   │  │ Tool B   │  │ Tool C   │
                            │ 执行     │  │ 执行     │  │ 执行     │
                            └────┬─────┘  └────┬─────┘  └────┬─────┘
                                 │              │              │
                                 └──────────────┼──────────────┘
                                                ▼
                                       ┌─────────────┐
                                       │  结果返回   │
                                       │  给 LLM     │
                                       └──────┬──────┘
                                              │
                                              ▼
                                       ┌─────────────┐
                                       │   最终回答   │
                                       └─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键组件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tool 接口&lt;/strong&gt;：定义工具的标准接口&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool Description&lt;/strong&gt;：工具的描述信息，用于 LLM 理解工具用途&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Executor&lt;/strong&gt;：负责工具调度的执行器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output Parser&lt;/strong&gt;：解析 LLM 输出，提取工具调用参数&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.2 关键组件&lt;/h3&gt;
&lt;h4&gt;Tool 接口（新版）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import BaseTool
from pydantic import BaseModel, Field

class ToolInput(BaseModel):
    &quot;&quot;&quot;输入参数定义&quot;&quot;&quot;
    query: str = Field(description=&quot;搜索关键词&quot;)
    limit: int = Field(description=&quot;返回结果数量&quot;, ge=1, le=100, default=10)

class MyCustomTool(BaseTool):
    name: str = &quot;my_custom_tool&quot;
    description: str = &quot;这是一个自定义工具的描述&quot;
    
    def _run(self, query: str, limit: int = 10) -&gt; str:
        &quot;&quot;&quot;同步执行方法&quot;&quot;&quot;
        # 工具逻辑
        return f&quot;搜索结果：{query}, 数量：{limit}&quot;
    
    async def _arun(self, query: str, limit: int = 10) -&gt; str:
        &quot;&quot;&quot;异步执行方法（可选）&quot;&quot;&quot;
        # 异步逻辑
        return await self._run(query, limit)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Structured Tool（结构化工具）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import StructuredTool
from typing import Optional

def search_function(query: str, limit: int = 10) -&gt; str:
    &quot;&quot;&quot;搜索功能的描述&quot;&quot;&quot;
    return f&quot;搜索结果：{query}&quot;

tool = StructuredTool(
    name=&quot;search&quot;,
    description=&quot;搜索相关信息&quot;,
    func=search_function,
    args_schema=None  # 自动从函数签名推断
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 架构设计&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TB
    subgraph &quot;应用层&quot;
        A[用户] --&gt; B[Chat Interface]
    end
    
    subgraph &quot;Agent 层&quot;
        B --&gt; C[Agent Executor]
        C --&gt; D[LLM Model]
        C --&gt; E[Tool Registry]
    end
    
    subgraph &quot;工具层&quot;
        E --&gt; F[Custom Tool A]
        E --&gt; G[Custom Tool B]
        E --&gt; H[Custom Tool C]
    end
    
    subgraph &quot;资源层&quot;
        F --&gt; I[Database]
        G --&gt; J[External API]
        H --&gt; K[File System]
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 技术实现&lt;/h2&gt;
&lt;h3&gt;3.1 环境准备&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建虚拟环境
python3 -m venv venv
source venv/bin/activate

# 安装依赖
pip install langchain langchain-core langchain-community
pip install langchain-openai  # 如果使用 OpenAI
pip install pydantic  # 数据验证

# 验证安装
python -c &quot;import langchain; print(langchain.__version__)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 基础实现&lt;/h3&gt;
&lt;h4&gt;方式一：使用 FunctionTool（推荐）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.agents import tool

@tool
def get_weather(city: str) -&gt; str:
    &quot;&quot;&quot;
    获取指定城市的天气信息
    
    Args:
        city: 城市名称，如 &quot;北京&quot;、&quot;上海&quot;
    
    Returns:
        天气信息字符串
    &quot;&quot;&quot;
    # 模拟天气数据
    weather_data = {
        &quot;北京&quot;: &quot;晴朗，25°C，湿度 45%&quot;,
        &quot;上海&quot;: &quot;多云，22°C，湿度 60%&quot;,
        &quot;广州&quot;: &quot;小雨，28°C，湿度 80%&quot;,
    }
    return weather_data.get(city, f&quot;未知城市 {city} 的天气数据&quot;)

# 使用
print(get_weather(&quot;北京&quot;))
# 输出：晴朗，25°C，湿度 45%
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方式二：继承 BaseTool&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Optional

class CalculatorInput(BaseModel):
    &quot;&quot;&quot;计算器输入参数&quot;&quot;&quot;
    expression: str = Field(description=&quot;数学表达式，如 &apos;2 + 3 * 4&apos;&quot;)

class CalculatorTool(BaseTool):
    name: str = &quot;calculator&quot;
    description: str = &quot;执行数学计算。输入有效的数学表达式。&quot;
    args_schema: type[BaseModel] = CalculatorInput
    
    def _run(self, expression: str) -&gt; str:
        &quot;&quot;&quot;执行计算&quot;&quot;&quot;
        try:
            # 安全的计算方式
            result = eval(expression, {&quot;__builtins__&quot;: {}}, {})
            return f&quot;计算结果：{result}&quot;
        except Exception as e:
            return f&quot;计算错误：{str(e)}&quot;
    
    async def _arun(self, expression: str) -&gt; str:
        &quot;&quot;&quot;异步执行（可选）&quot;&quot;&quot;
        return self._run(expression)

# 使用
calc = CalculatorTool()
print(calc.run(&quot;2 + 3 * 4&quot;))
# 输出：计算结果：14
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 进阶功能&lt;/h3&gt;
&lt;h4&gt;异步工具开发&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
from langchain.tools import BaseTool

class AsyncSearchTool(BaseTool):
    name: str = &quot;async_search&quot;
    description: str = &quot;异步搜索网络信息&quot;
    
    def _run(self, query: str) -&gt; str:
        # 同步实现（回退）
        return f&quot;同步搜索：{query}&quot;
    
    async def _arun(self, query: str) -&gt; str:
        &quot;&quot;&quot;异步搜索实现&quot;&quot;&quot;
        # 模拟异步操作
        await asyncio.sleep(0.5)
        
        # 实际场景中，这里是异步 HTTP 请求
        # response = await aiohttp_client.get(...)
        
        return f&quot;异步搜索结果：{query}&quot;

# 批量异步调用
async def batch_search():
    tool = AsyncSearchTool()
    queries = [&quot;AI&quot;, &quot;LLM&quot;, &quot;LangChain&quot;]
    
    results = await asyncio.gather(*[
        tool.arun(q) for q in queries
    ])
    return results
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;工具链编排&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import tool
from typing import List

@tool
def search_products(category: str) -&gt; List[dict]:
    &quot;&quot;&quot;搜索产品&quot;&quot;&quot;
    return [
        {&quot;id&quot;: 1, &quot;name&quot;: &quot;产品 A&quot;, &quot;price&quot;: 100},
        {&quot;id&quot;: 2, &quot;name&quot;: &quot;产品 B&quot;, &quot;price&quot;: 200},
    ]

@tool
def calculate_discount(products: List[dict], rate: float) -&gt; List[dict]:
    &quot;&quot;&quot;计算折扣&quot;&quot;&quot;
    return [
        {**p, &quot;discounted_price&quot;: p[&quot;price&quot;] * (1 - rate)}
        for p in products
    ]

@tool
def format_invoice(products: List[dict]) -&gt; str:
    &quot;&quot;&quot;格式化发票&quot;&quot;&quot;
    lines = [&quot;=== 发票 ===&quot;]
    total = 0
    for p in products:
        price = p.get(&quot;discounted_price&quot;, p[&quot;price&quot;])
        lines.append(f&quot;{p[&apos;name&apos;]}: ¥{price}&quot;)
        total += price
    lines.append(f&quot;总计：¥{total}&quot;)
    return &quot;\n&quot;.join(lines)

# 工具链：search -&gt; discount -&gt; invoice
def product_workflow(category: str, discount_rate: float = 0.1):
    products = search_products(category)
    discounted = calculate_discount(products, discount_rate)
    invoice = format_invoice(discounted)
    return invoice
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 最佳实践&lt;/h2&gt;
&lt;h3&gt;4.1 性能优化&lt;/h3&gt;
&lt;h4&gt;工具缓存&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import lru_cache
from langchain.tools import tool

@tool
@lru_cache(maxsize=100)
def get_static_data(data_id: str) -&gt; str:
    &quot;&quot;&quot;
    获取静态数据（可缓存）
    
    注意：缓存参数必须可哈希
    &quot;&quot;&quot;
    # 模拟数据库查询
    return f&quot;数据 {data_id} 的内容&quot;

# 清理缓存
get_static_data.cache_clear()
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;批量处理优化&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import BaseTool
from typing import List

class BatchQueryTool(BaseTool):
    name: str = &quot;batch_query&quot;
    description: str = &quot;批量查询数据，减少网络请求&quot;
    
    def _run(self, ids: List[int]) -&gt; str:
        &quot;&quot;&quot;批量查询&quot;&quot;&quot;
        # 一次查询多个 ID，而不是循环查询
        # SELECT * FROM table WHERE id IN ({ids})
        results = [f&quot;ID {i} 的数据&quot; for i in ids]
        return &quot;\n&quot;.join(results)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 错误处理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import tool
from typing import Optional

@tool
def safe_database_query(query: str, timeout: int = 30) -&gt; str:
    &quot;&quot;&quot;
    安全的数据库查询，包含完善的错误处理
    &quot;&quot;&quot;
    import logging
    
    logger = logging.getLogger(__name__)
    
    try:
        # 输入验证
        if not query or len(query) &gt; 1000:
            return &quot;错误：查询语句无效&quot;
        
        # SQL 注入检查
        dangerous_keywords = [&quot;DROP&quot;, &quot;DELETE&quot;, &quot;TRUNCATE&quot;]
        if any(kw in query.upper() for kw in dangerous_keywords):
            logger.warning(f&quot;潜在的 SQL 注入尝试：{query}&quot;)
            return &quot;错误：不允许的操作&quot;
        
        # 执行查询（模拟）
        import time
        time.sleep(0.1)  # 模拟网络延迟
        
        return f&quot;查询结果：{query[:50]}...&quot;
        
    except TimeoutError:
        logger.error(&quot;查询超时&quot;)
        return &quot;错误：查询超时，请重试&quot;
    except Exception as e:
        logger.error(f&quot;查询失败：{e}&quot;)
        return f&quot;错误：{str(e)}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 监控与日志&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import logging
from contextlib import contextmanager
from langchain.tools import BaseTool
import time

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format=&quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&quot;
)

class MonitoredTool(BaseTool):
    name: str = &quot;monitored_tool&quot;
    description: str = &quot;带监控的工具&quot;
    
    @contextmanager
    def _monitor(self, action: str, params: dict):
        &quot;&quot;&quot;监控上下文&quot;&quot;&quot;
        start_time = time.time()
        logger = logging.getLogger(self.name)
        
        logger.info(f&quot;开始 {action}, 参数：{params}&quot;)
        
        try:
            yield
            duration = time.time() - start_time
            logger.info(f&quot;完成 {action}, 耗时：{duration:.2f}s&quot;)
        except Exception as e:
            duration = time.time() - start_time
            logger.error(f&quot;失败 {action}, 耗时：{duration:.2f}s, 错误：{e}&quot;)
            raise
    
    def _run(self, param: str) -&gt; str:
        with self._monitor(&quot;执行&quot;, {&quot;param&quot;: param}):
            # 工具逻辑
            return f&quot;结果：{param}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 实战案例&lt;/h2&gt;
&lt;h3&gt;案例：企业知识库问答系统&lt;/h3&gt;
&lt;p&gt;构建一个能够查询企业内部知识库的 AI 助手。&lt;/p&gt;
&lt;h4&gt;5.1 系统架构&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│                    用户交互层                            │
│              (Chat Interface / API)                     │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                      Agent 层                            │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │
│  │ 意图识别    │  │ 工具选择    │  │ 结果整合    │     │
│  └─────────────┘  └─────────────┘  └─────────────┘     │
└─────────────────────────────────────────────────────────┘
                          │
          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
    ┌──────────┐   ┌──────────┐   ┌──────────┐
    │文档搜索  │   │员工信息  │   │项目数据  │
    │  工具    │   │  工具    │   │  工具    │
    └──────────┘   └──────────┘   └──────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.2 工具实现&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tools import tool
from typing import List, Dict
import json

# 模拟数据库
DOCUMENTS = [
    {&quot;id&quot;: 1, &quot;title&quot;: &quot;公司政策手册&quot;, &quot;content&quot;: &quot;...&quot;, &quot;tags&quot;: [&quot;政策&quot;, &quot;HR&quot;]},
    {&quot;id&quot;: 2, &quot;title&quot;: &quot;技术架构文档&quot;, &quot;content&quot;: &quot;...&quot;, &quot;tags&quot;: [&quot;技术&quot;, &quot;架构&quot;]},
    {&quot;id&quot;: 3, &quot;title&quot;: &quot;项目管理办法&quot;, &quot;content&quot;: &quot;...&quot;, &quot;tags&quot;: [&quot;项目&quot;, &quot;管理&quot;]},
]

EMPLOYEES = [
    {&quot;id&quot;: 1, &quot;name&quot;: &quot;张三&quot;, &quot;department&quot;: &quot;技术部&quot;, &quot;role&quot;: &quot;工程师&quot;},
    {&quot;id&quot;: 2, &quot;name&quot;: &quot;李四&quot;, &quot;department&quot;: &quot;产品部&quot;, &quot;role&quot;: &quot;产品经理&quot;},
]

PROJECTS = [
    {&quot;id&quot;: 1, &quot;name&quot;: &quot;AI 平台&quot;, &quot;status&quot;: &quot;进行中&quot;, &quot;lead&quot;: &quot;张三&quot;},
    {&quot;id&quot;: 2, &quot;name&quot;: &quot;数据中台&quot;, &quot;status&quot;: &quot;规划中&quot;, &quot;lead&quot;: &quot;李四&quot;},
]

@tool
def search_documents(query: str, limit: int = 5) -&gt; str:
    &quot;&quot;&quot;
    搜索公司内部文档
    
    Args:
        query: 搜索关键词
        limit: 返回结果数量（最多 10 个）
    &quot;&quot;&quot;
    # 简单的关键词匹配
    results = [
        doc for doc in DOCUMENTS
        if any(kw in doc[&quot;title&quot;].lower() or kw in doc[&quot;content&quot;].lower()
               for kw in query.lower().split())
    ][:limit]
    
    return json.dumps([
        {&quot;id&quot;: r[&quot;id&quot;], &quot;title&quot;: r[&quot;title&quot;], &quot;tags&quot;: r[&quot;tags&quot;]}
        for r in results
    ], ensure_ascii=False, indent=2)

@tool
def get_employee_info(name: str) -&gt; str:
    &quot;&quot;&quot;
    获取员工信息
    
    Args:
        name: 员工姓名
    &quot;&quot;&quot;
    employee = next((e for e in EMPLOYEES if e[&quot;name&quot;] == name), None)
    if employee:
        return json.dumps(employee, ensure_ascii=False, indent=2)
    return f&quot;未找到员工：{name}&quot;

@tool
def list_projects(status: str = None) -&gt; str:
    &quot;&quot;&quot;
    列出项目列表
    
    Args:
        status: 可选，项目状态过滤（进行中/规划中/已完成）
    &quot;&quot;&quot;
    projects = PROJECTS
    if status:
        projects = [p for p in projects if p[&quot;status&quot;] == status]
    
    return json.dumps(projects, ensure_ascii=False, indent=2)

# 工具列表
tools = [search_documents, get_employee_info, list_projects]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5.3 Agent 集成&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.agents import (
    AgentExecutor,
    create_tool_calling_agent,
)
from langchain_openai import ChatOpenAI

# 初始化 LLM
llm = ChatOpenAI(model=&quot;gpt-4-turbo&quot;, temperature=0)

# 创建 Agent
agent = create_tool_calling_agent(llm, tools, prompt)

# 创建执行器
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=10,
)

# 使用示例
response = agent_executor.invoke({
    &quot;input&quot;: &quot;张三负责哪些项目？&quot;
})

print(response[&quot;output&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;效果评估&lt;/h3&gt;
&lt;p&gt;| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 响应时间 | 5.2s | 1.8s | 65% |
| 准确率 | 72% | 91% | 26% |
| 并发能力 | 10 req/s | 50 req/s | 400% |
| 错误率 | 8% | 2% | 75% |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 常见问题&lt;/h2&gt;
&lt;h3&gt;Q1: 工具调用失败怎么办？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;答&lt;/strong&gt;：常见原因和解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;参数类型不匹配&lt;/strong&gt;：确保工具函数的参数类型与 LLM 输出匹配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;描述不清晰&lt;/strong&gt;：完善工具的 description，让 LLM 更好地理解用途&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权限问题&lt;/strong&gt;：检查工具是否有访问资源的权限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络问题&lt;/strong&gt;：对于 API 工具，添加重试机制和超时处理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1))
def api_call_tool(endpoint: str) -&gt; str:
    &quot;&quot;&quot;带重试的 API 调用&quot;&quot;&quot;
    response = requests.get(endpoint, timeout=10)
    response.raise_for_status()
    return response.text
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q2: 如何限制工具的使用范围？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;答&lt;/strong&gt;：可以通过以下方式限制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;访问控制&lt;/strong&gt;：在工具内部实现权限检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数验证&lt;/strong&gt;：使用 Pydantic 严格验证输入参数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调用频率限制&lt;/strong&gt;：使用令牌桶算法限制调用频率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;白名单机制&lt;/strong&gt;：只允许特定用户或角色调用特定工具&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import wraps

def require_permission(permission: str):
    &quot;&quot;&quot;权限装饰器&quot;&quot;&quot;
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if permission not in user.get(&quot;permissions&quot;, []):
                raise PermissionError(f&quot;缺少权限：{permission}&quot;)
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@tool
@require_permission(&quot;database_read&quot;)
def query_database(user: dict, query: str) -&gt; str:
    &quot;&quot;&quot;需要权限的数据库查询&quot;&quot;&quot;
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q3: 如何处理工具的副作用？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;答&lt;/strong&gt;：对于有副作用的工具（如写入操作），建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;预检查机制&lt;/strong&gt;：在执行前确认用户意图&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事务支持&lt;/strong&gt;：支持回滚操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计日志&lt;/strong&gt;：记录所有操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确认流程&lt;/strong&gt;：对于危险操作，要求二次确认&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@tool
def delete_record(record_id: str, confirm: bool = False) -&gt; str:
    &quot;&quot;&quot;
    删除记录（需要确认）
    
    Args:
        record_id: 记录 ID
        confirm: 必须为 True 才能执行删除
    &quot;&quot;&quot;
    if not confirm:
        return &quot;错误：删除操作需要设置 confirm=True&quot;
    
    # 记录审计日志
    audit_log(f&quot;删除记录：{record_id}&quot;)
    
    # 执行删除
    return f&quot;已删除记录：{record_id}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. 总结与展望&lt;/h2&gt;
&lt;h3&gt;核心要点回顾&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工具设计原则&lt;/strong&gt;：清晰的描述、明确的输入输出、完善的错误处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：缓存、批量处理、异步执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全实践&lt;/strong&gt;：输入验证、权限控制、审计日志&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控可观测性&lt;/strong&gt;：日志、指标、追踪&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;未来方向&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工具自动发现&lt;/strong&gt;：基于 OpenAPI 规范自动生成工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具学习&lt;/strong&gt;：让 Agent 从使用中学习工具的最佳用法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具编排&lt;/strong&gt;：复杂的工具链自动编排和优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多模态工具&lt;/strong&gt;：支持图像、音频等多模态输入输出&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;学习资源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/docs/modules/tools/&quot;&gt;LangChain 官方文档 - Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/langchain&quot;&gt;LangChain GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/langchain-cookbook&quot;&gt;LangChain Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com.cn/&quot;&gt;LangChain 中文文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LangChain</category><category>工具开发</category><category>AI 应用</category></item><item><title>LangGraph 复杂状态机设计模式</title><link>https://heyedwardchen.com/blog/langgraph-state-machine-patterns/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/langgraph-state-machine-patterns/</guid><description>系统掌握 LangGraph 中的状态机设计模式，从基础模式到高级组合，提供可复用的代码模板和最佳实践。</description><pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;LangGraph 复杂状态机设计模式&lt;/h1&gt;
&lt;h2&gt;1. 引言：为什么需要状态机设计模式&lt;/h2&gt;
&lt;h3&gt;1.1 复杂工作流的挑战&lt;/h3&gt;
&lt;p&gt;在前一篇文章《LangChain 与 LangGraph 深度解析》中，我们介绍了 LangGraph 的基本概念和用法。然而，当面对真实世界的复杂应用时，仅仅知道 API 是远远不够的。开发者经常面临以下挑战：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作流复杂度爆炸&lt;/strong&gt; — 随着功能增加，状态机图变得难以理解和维护&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重复造轮子&lt;/strong&gt; — 相似的逻辑在不同项目中重复实现&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计决策困难&lt;/strong&gt; — 面对复杂需求，不知道如何组织状态和转换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调试困难&lt;/strong&gt; — 状态流转不清晰，问题难以定位&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协作成本&lt;/strong&gt; — 团队成员对状态机设计缺乏统一理解&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 设计模式的价值&lt;/h3&gt;
&lt;p&gt;设计模式是软件工程的宝贵资产，它们提供了：&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;经过验证的解决方案&lt;/strong&gt; — 解决常见问题的最佳实践&lt;br&gt;
✅ &lt;strong&gt;通用语言&lt;/strong&gt; — 团队间高效沟通的基础&lt;br&gt;
✅ &lt;strong&gt;可复用模板&lt;/strong&gt; — 快速构建可靠系统&lt;br&gt;
✅ &lt;strong&gt;避免陷阱&lt;/strong&gt; — 前人踩过的坑，后人不必再踩&lt;/p&gt;
&lt;p&gt;在 LangGraph 中，设计模式同样重要。通过掌握常见模式，你可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快速搭建复杂工作流&lt;/li&gt;
&lt;li&gt;提高代码可维护性&lt;/li&gt;
&lt;li&gt;降低调试成本&lt;/li&gt;
&lt;li&gt;与社区高效交流&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.3 LangGraph 的模式支持&lt;/h3&gt;
&lt;p&gt;LangGraph 的设计天然适合模式化：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Literal, Annotated

# 状态定义 — 模式的基础
class WorkflowState(TypedDict):
    input: str
    output: str
    metadata: dict

# 节点 — 模式的原子单元
def node_function(state: WorkflowState) -&gt; WorkflowState:
    return {&quot;output&quot;: &quot;result&quot;}

# 边 — 模式的连接方式
workflow = StateGraph(WorkflowState)
workflow.add_node(&quot;step&quot;, node_function)
workflow.add_edge(START, &quot;step&quot;)
workflow.add_edge(&quot;step&quot;, END)

# 编译 — 模式的实例化
app = workflow.compile()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LangGraph 的核心抽象（状态、节点、边、条件）为设计模式提供了坚实基础。&lt;/p&gt;
&lt;h3&gt;1.4 本文结构&lt;/h3&gt;
&lt;p&gt;本文将系统介绍 LangGraph 中的设计模式，按复杂度递增组织：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础模式&lt;/strong&gt; — 顺序、分支、并行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环模式&lt;/strong&gt; — 迭代、重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协作模式&lt;/strong&gt; — 多 Agent 架构&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人机协作模式&lt;/strong&gt; — 人类审核与干预&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高级模式&lt;/strong&gt; — 模式组合与嵌套&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反模式与最佳实践&lt;/strong&gt; — 避免常见陷阱&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每个模式都包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模式描述与适用场景&lt;/li&gt;
&lt;li&gt;完整代码实现&lt;/li&gt;
&lt;li&gt;可视化图结构&lt;/li&gt;
&lt;li&gt;变体与扩展建议&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;让我们开始探索这些强大的设计模式！&lt;/p&gt;
&lt;h2&gt;2. 基础模式：顺序与分支&lt;/h2&gt;
&lt;h3&gt;2.1 线性流水线模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：最简单的模式，节点按固定顺序依次执行，无分支无循环。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据预处理流水线&lt;/li&gt;
&lt;li&gt;多步骤内容生成&lt;/li&gt;
&lt;li&gt;顺序验证流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点顺序固定&lt;/li&gt;
&lt;li&gt;无条件判断&lt;/li&gt;
&lt;li&gt;状态单向流动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import StateGraph, END, START
from typing import TypedDict

# 定义状态
class PipelineState(TypedDict):
    raw_data: str
    cleaned_data: str
    analyzed_data: str
    final_output: str

# 节点 1：数据清洗
def clean_data(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;清洗原始数据&quot;&quot;&quot;
    cleaned = state[&quot;raw_data&quot;].strip().lower()
    return {&quot;cleaned_data&quot;: cleaned}

# 节点 2：数据分析
def analyze_data(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;分析清洗后的数据&quot;&quot;&quot;
    words = state[&quot;cleaned_data&quot;].split()
    analysis = f&quot;共 {len(words)} 个单词&quot;
    return {&quot;analyzed_data&quot;: analysis}

# 节点 3：生成输出
def generate_output(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;生成最终输出&quot;&quot;&quot;
    output = f&quot;原始：{state[&apos;cleaned_data&apos;]}\n分析：{state[&apos;analyzed_data&apos;]}&quot;
    return {&quot;final_output&quot;: output}

# 构建流水线
workflow = StateGraph(PipelineState)

# 添加节点
workflow.add_node(&quot;clean&quot;, clean_data)
workflow.add_node(&quot;analyze&quot;, analyze_data)
workflow.add_node(&quot;generate&quot;, generate_output)

# 设置顺序边
workflow.add_edge(START, &quot;clean&quot;)
workflow.add_edge(&quot;clean&quot;, &quot;analyze&quot;)
workflow.add_edge(&quot;analyze&quot;, &quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, END)

# 编译
pipeline = workflow.compile()

# 执行
result = pipeline.invoke({&quot;raw_data&quot;: &quot;  Hello World  &quot;})
print(result[&quot;final_output&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;START → clean → analyze → generate → END
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;变体&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;带日志的流水线&lt;/strong&gt;：每个节点记录执行日志&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;带进度的流水线&lt;/strong&gt;：状态中包含进度信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可中断流水线&lt;/strong&gt;：支持在任意节点暂停&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2.2 条件分支模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：根据状态内容决定下一步执行路径，实现条件逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于输入类型的处理&lt;/li&gt;
&lt;li&gt;质量检查与路由&lt;/li&gt;
&lt;li&gt;A/B 测试分流&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;条件函数决定路径&lt;/li&gt;
&lt;li&gt;多个可能的分支&lt;/li&gt;
&lt;li&gt;分支可能汇合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Literal

class RouterState(TypedDict):
    input_type: str
    content: str
    result: str

# 条件函数：根据输入类型路由
def route_by_type(state: RouterState) -&gt; Literal[&quot;text_processor&quot;, &quot;image_processor&quot;, &quot;error_handler&quot;]:
    &quot;&quot;&quot;根据输入类型选择处理路径&quot;&quot;&quot;
    input_type = state[&quot;input_type&quot;].lower()
    
    if input_type == &quot;text&quot;:
        return &quot;text_processor&quot;
    elif input_type == &quot;image&quot;:
        return &quot;image_processor&quot;
    else:
        return &quot;error_handler&quot;

# 分支 1：文本处理
def process_text(state: RouterState) -&gt; RouterState:
    &quot;&quot;&quot;处理文本内容&quot;&quot;&quot;
    result = f&quot;文本处理：{len(state[&apos;content&apos;])} 个字符&quot;
    return {&quot;result&quot;: result}

# 分支 2：图像处理
def process_image(state: RouterState) -&gt; RouterState:
    &quot;&quot;&quot;处理图像内容&quot;&quot;&quot;
    result = f&quot;图像处理：分析 {state[&apos;content&apos;]}&quot;
    return {&quot;result&quot;: result}

# 分支 3：错误处理
def handle_error(state: RouterState) -&gt; RouterState:
    &quot;&quot;&quot;处理未知类型&quot;&quot;&quot;
    result = f&quot;错误：不支持的类型 {state[&apos;input_type&apos;]}&quot;
    return {&quot;result&quot;: result}

# 构建路由图
workflow = StateGraph(RouterState)

# 添加节点
workflow.add_node(&quot;text_processor&quot;, process_text)
workflow.add_node(&quot;image_processor&quot;, process_image)
workflow.add_node(&quot;error_handler&quot;, handle_error)

# 设置入口和条件边
workflow.add_edge(START, &quot;router&quot;)
workflow.add_conditional_edges(
    &quot;router&quot;,  # 从哪个节点出发
    route_by_type,  # 条件函数
    {
        &quot;text_processor&quot;: &quot;text_processor&quot;,
        &quot;image_processor&quot;: &quot;image_processor&quot;,
        &quot;error_handler&quot;: &quot;error_handler&quot;
    }
)

# 所有分支汇合到 END
workflow.add_edge(&quot;text_processor&quot;, END)
workflow.add_edge(&quot;image_processor&quot;, END)
workflow.add_edge(&quot;error_handler&quot;, END)

# 编译
router = workflow.compile()

# 测试不同路径
print(router.invoke({&quot;input_type&quot;: &quot;text&quot;, &quot;content&quot;: &quot;Hello&quot;}))
print(router.invoke({&quot;input_type&quot;: &quot;image&quot;, &quot;content&quot;: &quot;image_url&quot;}))
print(router.invoke({&quot;input_type&quot;: &quot;audio&quot;, &quot;content&quot;: &quot;audio_data&quot;}))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;              → text_processor →
             ↘
START → router → image_processor → END
             ↘
              → error_handler →
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;高级技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多层级路由&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def primary_router(state: RouterState) -&gt; Literal[&quot;category_a&quot;, &quot;category_b&quot;]:
    &quot;&quot;&quot;一级路由&quot;&quot;&quot;
    return &quot;category_a&quot; if len(state[&quot;content&quot;]) &gt; 100 else &quot;category_b&quot;

def secondary_router(state: RouterState) -&gt; Literal[&quot;fast_path&quot;, &quot;thorough_path&quot;]:
    &quot;&quot;&quot;二级路由&quot;&quot;&quot;
    return &quot;fast_path&quot; if state.get(&quot;urgent&quot;) else &quot;thorough_path&quot;

# 嵌套条件边
workflow.add_conditional_edges(&quot;entry&quot;, primary_router, {...})
workflow.add_conditional_edges(&quot;category_a&quot;, secondary_router, {...})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;动态条件&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def dynamic_threshold(state: RouterState) -&gt; Literal[&quot;approve&quot;, &quot;review&quot;]:
    &quot;&quot;&quot;基于动态阈值的条件&quot;&quot;&quot;
    threshold = state.get(&quot;threshold&quot;, 0.5)
    score = state.get(&quot;confidence_score&quot;, 0)
    return &quot;approve&quot; if score &gt;= threshold else &quot;review&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;2.3 并行执行模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：多个节点同时执行，提高处理效率。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多源数据收集&lt;/li&gt;
&lt;li&gt;并行特征提取&lt;/li&gt;
&lt;li&gt;多模型投票&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点并发执行&lt;/li&gt;
&lt;li&gt;结果需要合并&lt;/li&gt;
&lt;li&gt;可能涉及同步等待&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
from typing import List

class ParallelState(TypedDict):
    query: str
    search_results: List[str]
    analysis_results: List[str]
    combined_output: str

# 并行任务 1：网络搜索
async def web_search(state: ParallelState) -&gt; ParallelState:
    &quot;&quot;&quot;执行网络搜索&quot;&quot;&quot;
    await asyncio.sleep(0.5)  # 模拟异步操作
    return {&quot;search_results&quot;: [f&quot;搜索结果 {i}&quot; for i in range(3)]}

# 并行任务 2：知识库查询
async def knowledge_base_query(state: ParallelState) -&gt; ParallelState:
    &quot;&quot;&quot;查询知识库&quot;&quot;&quot;
    await asyncio.sleep(0.3)
    return {&quot;analysis_results&quot;: [f&quot;知识条目 {i}&quot; for i in range(2)]}

# 合并结果
def merge_results(state: ParallelState) -&gt; ParallelState:
    &quot;&quot;&quot;合并并行结果&quot;&quot;&quot;
    combined = {
        &quot;search&quot;: state.get(&quot;search_results&quot;, []),
        &quot;knowledge&quot;: state.get(&quot;analysis_results&quot;, [])
    }
    output = f&quot;搜索：{len(combined[&apos;search&apos;])}条，知识：{len(combined[&apos;knowledge&apos;])}条&quot;
    return {&quot;combined_output&quot;: output}

# 构建并行图
workflow = StateGraph(ParallelState)

# 添加并行节点
workflow.add_node(&quot;search&quot;, web_search)
workflow.add_node(&quot;knowledge&quot;, knowledge_base_query)
workflow.add_node(&quot;merge&quot;, merge_results)

# 并行分支
workflow.add_edge(START, &quot;search&quot;)
workflow.add_edge(START, &quot;knowledge&quot;)

# 汇合点
workflow.add_edge(&quot;search&quot;, &quot;merge&quot;)
workflow.add_edge(&quot;knowledge&quot;, &quot;merge&quot;)
workflow.add_edge(&quot;merge&quot;, END)

# 编译
parallel_app = workflow.compile()

# 执行
result = parallel_app.invoke({&quot;query&quot;: &quot;人工智能&quot;})
print(result[&quot;combined_output&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       → search →
      ↘          \
START             → merge → END
      /          /
       → knowledge →
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;并行节点必须异步（async def）才能真正并发&lt;/li&gt;
&lt;li&gt;合并节点需要等待所有并行分支完成&lt;/li&gt;
&lt;li&gt;状态合并策略需要精心设计（覆盖、合并、选择）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;状态合并策略&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Annotated

def merge_lists(left: List[str], right: List[str]) -&gt; List[str]:
    &quot;&quot;&quot;合并两个列表&quot;&quot;&quot;
    return left + right

class MergedState(TypedDict):
    results: Annotated[List[str], merge_lists]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 循环模式：迭代与重试&lt;/h2&gt;
&lt;h3&gt;3.1 固定次数迭代模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：节点执行固定次数，每次迭代更新状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;渐进式内容优化&lt;/li&gt;
&lt;li&gt;多轮推理&lt;/li&gt;
&lt;li&gt;分步细化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计数器控制迭代&lt;/li&gt;
&lt;li&gt;状态累积&lt;/li&gt;
&lt;li&gt;确定性终止&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Literal

class IterationState(TypedDict):
    prompt: str
    current_output: str
    iteration: int
    max_iterations: int
    history: list

def generate_content(state: IterationState) -&gt; IterationState:
    &quot;&quot;&quot;生成或优化内容&quot;&quot;&quot;
    # 模拟 LLM 生成
    if state[&quot;iteration&quot;] == 0:
        output = f&quot;初始版本：{state[&apos;prompt&apos;]}&quot;
    else:
        output = f&quot;优化版本 {state[&apos;iteration&apos;]}: 基于 &apos;{state[&apos;current_output&apos;]}&apos; 改进&quot;
    
    return {
        &quot;current_output&quot;: output,
        &quot;history&quot;: state.get(&quot;history&quot;, []) + [output]
    }

def check_iterations(state: IterationState) -&gt; Literal[&quot;continue&quot;, &quot;end&quot;]:
    &quot;&quot;&quot;检查是否继续迭代&quot;&quot;&quot;
    if state[&quot;iteration&quot;] &amp;#x3C; state[&quot;max_iterations&quot;]:
        return &quot;continue&quot;
    return &quot;end&quot;

# 构建迭代图
workflow = StateGraph(IterationState)

workflow.add_node(&quot;generate&quot;, generate_content)

workflow.add_edge(START, &quot;generate&quot;)

workflow.add_conditional_edges(
    &quot;generate&quot;,
    check_iterations,
    {
        &quot;continue&quot;: &quot;generate&quot;,  # 循环回自身
        &quot;end&quot;: END
    }
)

# 在 generate 节点后自动增加迭代计数
# 实际使用中可以通过状态更新器实现

iteration_app = workflow.compile()

result = iteration_app.invoke({
    &quot;prompt&quot;: &quot;写一篇关于 AI 的文章&quot;,
    &quot;current_output&quot;: &quot;&quot;,
    &quot;iteration&quot;: 0,
    &quot;max_iterations&quot;: 3,
    &quot;history&quot;: []
})

print(f&quot;最终输出：{result[&apos;current_output&apos;]}&quot;)
print(f&quot;迭代历史：{result[&apos;history&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       ┌─────────────┐
       ↓             │
START → generate ────┘ (最多 N 次)
       ↓
      END
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;3.2 条件终止循环模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：循环执行直到满足特定条件，而非固定次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直到答案满意&lt;/li&gt;
&lt;li&gt;直到收敛&lt;/li&gt;
&lt;li&gt;直到达到质量标准&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;条件函数判断终止&lt;/li&gt;
&lt;li&gt;可能需要最大次数保护&lt;/li&gt;
&lt;li&gt;状态质量评估&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ConvergenceState(TypedDict):
    query: str
    answer: str
    quality_score: float
    threshold: float
    iteration: int
    max_iterations: int

def refine_answer(state: ConvergenceState) -&gt; ConvergenceState:
    &quot;&quot;&quot;优化答案&quot;&quot;&quot;
    # 模拟答案优化和质量评估
    iteration = state[&quot;iteration&quot;]
    
    # 模拟质量分数逐渐提高
    quality_score = min(0.95, 0.5 + iteration * 0.15)
    
    answer = f&quot;优化后的答案 (质量：{quality_score:.2f})&quot;
    
    return {
        &quot;answer&quot;: answer,
        &quot;quality_score&quot;: quality_score,
        &quot;iteration&quot;: iteration + 1
    }

def should_continue_refining(state: ConvergenceState) -&gt; Literal[&quot;refine&quot;, &quot;done&quot;]:
    &quot;&quot;&quot;判断是否继续优化&quot;&quot;&quot;
    # 达到质量阈值或最大迭代次数则停止
    if state[&quot;quality_score&quot;] &gt;= state[&quot;threshold&quot;]:
        return &quot;done&quot;
    if state[&quot;iteration&quot;] &gt;= state[&quot;max_iterations&quot;]:
        return &quot;done&quot;
    return &quot;refine&quot;

# 构建条件终止图
workflow = StateGraph(ConvergenceState)

workflow.add_node(&quot;refine&quot;, refine_answer)

workflow.add_edge(START, &quot;refine&quot;)

workflow.add_conditional_edges(
    &quot;refine&quot;,
    should_continue_refining,
    {
        &quot;refine&quot;: &quot;refine&quot;,
        &quot;done&quot;: END
    }
)

convergence_app = workflow.compile()

result = convergence_app.invoke({
    &quot;query&quot;: &quot;量子计算原理&quot;,
    &quot;answer&quot;: &quot;&quot;,
    &quot;quality_score&quot;: 0.0,
    &quot;threshold&quot;: 0.85,
    &quot;iteration&quot;: 0,
    &quot;max_iterations&quot;: 10
})

print(f&quot;最终答案：{result[&apos;answer&apos;]}&quot;)
print(f&quot;质量分数：{result[&apos;quality_score&apos;]:.2f}&quot;)
print(f&quot;迭代次数：{result[&apos;iteration&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;3.3 带退避的重试模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：操作失败时自动重试，使用退避策略避免频繁失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 调用失败重试&lt;/li&gt;
&lt;li&gt;网络请求重试&lt;/li&gt;
&lt;li&gt;不稳定操作容错&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;失败检测&lt;/li&gt;
&lt;li&gt;退避策略（线性、指数）&lt;/li&gt;
&lt;li&gt;最大重试次数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import time
import random
from typing import Literal

class RetryState(TypedDict):
    task: str
    result: str
    attempt: int
    max_attempts: int
    base_delay: float
    errors: list

def execute_task(state: RetryState) -&gt; RetryState:
    &quot;&quot;&quot;执行可能失败的任务&quot;&quot;&quot;
    attempt = state[&quot;attempt&quot;]
    
    # 模拟随机失败（前两次失败，第三次成功）
    if attempt &amp;#x3C; 2:
        error = f&quot;第 {attempt} 次尝试失败：临时错误&quot;
        return {
            &quot;errors&quot;: state.get(&quot;errors&quot;, []) + [error],
            &quot;result&quot;: None
        }
    else:
        return {
            &quot;result&quot;: f&quot;任务成功完成 (第 {attempt} 次尝试)&quot;,
            &quot;errors&quot;: state.get(&quot;errors&quot;, [])
        }

def check_result(state: RetryState) -&gt; Literal[&quot;retry&quot;, &quot;success&quot;, &quot;give_up&quot;]:
    &quot;&quot;&quot;检查结果并决定下一步&quot;&quot;&quot;
    if state[&quot;result&quot;] is not None:
        return &quot;success&quot;
    
    if state[&quot;attempt&quot;] &gt;= state[&quot;max_attempts&quot;]:
        return &quot;give_up&quot;
    
    return &quot;retry&quot;

def apply_backoff(state: RetryState) -&gt; RetryState:
    &quot;&quot;&quot;应用退避延迟&quot;&quot;&quot;
    # 指数退避：delay = base_delay * 2^(attempt-1)
    delay = state[&quot;base_delay&quot;] * (2 ** (state[&quot;attempt&quot;] - 1))
    
    # 添加随机抖动
    jitter = delay * 0.2 * (random.random() - 0.5)
    actual_delay = delay + jitter
    
    print(f&quot;第 {state[&apos;attempt&apos;]} 次失败，{actual_delay:.2f}秒后重试...&quot;)
    # time.sleep(actual_delay)  # 实际使用取消注释
    
    return {&quot;attempt&quot;: state[&quot;attempt&quot;] + 1}

# 构建重试图
workflow = StateGraph(RetryState)

workflow.add_node(&quot;execute&quot;, execute_task)
workflow.add_node(&quot;backoff&quot;, apply_backoff)

workflow.add_edge(START, &quot;execute&quot;)

workflow.add_conditional_edges(
    &quot;execute&quot;,
    check_result,
    {
        &quot;retry&quot;: &quot;backoff&quot;,
        &quot;success&quot;: END,
        &quot;give_up&quot;: END
    }
)

workflow.add_edge(&quot;backoff&quot;, &quot;execute&quot;)

retry_app = workflow.compile()

result = retry_app.invoke({
    &quot;task&quot;: &quot;调用外部 API&quot;,
    &quot;result&quot;: None,
    &quot;attempt&quot;: 1,
    &quot;max_attempts&quot;: 5,
    &quot;base_delay&quot;: 1.0,
    &quot;errors&quot;: []
})

print(f&quot;最终结果：{result[&apos;result&apos;]}&quot;)
print(f&quot;失败记录：{result[&apos;errors&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;退避策略变体&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 线性退避
delay = state[&quot;base_delay&quot;] * state[&quot;attempt&quot;]

# 指数退避（推荐）
delay = state[&quot;base_delay&quot;] * (2 ** state[&quot;attempt&quot;])

# 斐波那契退避
def fibonacci(n):
    if n &amp;#x3C;= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
delay = state[&quot;base_delay&quot;] * fibonacci(state[&quot;attempt&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 协作模式：多 Agent 架构&lt;/h2&gt;
&lt;h3&gt;4.1 主管 - 工人模式（Manager-Worker）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：主管 Agent 负责任务分解和协调，工人 Agent 负责具体执行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂任务分解&lt;/li&gt;
&lt;li&gt;并行任务分配&lt;/li&gt;
&lt;li&gt;结果整合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分层架构&lt;/li&gt;
&lt;li&gt;任务分发&lt;/li&gt;
&lt;li&gt;结果汇总&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List, Literal

class ManagerWorkerState(TypedDict):
    task: str
    subtasks: List[str]
    results: List[str]
    current_subtask: int
    final_output: str

def manager_decompose(state: ManagerWorkerState) -&gt; ManagerWorkerState:
    &quot;&quot;&quot;主管：分解任务&quot;&quot;&quot;
    task = state[&quot;task&quot;]
    
    # 模拟任务分解
    subtasks = [
        f&quot;子任务 1: 研究 {task} 的背景&quot;,
        f&quot;子任务 2: 分析 {task} 的核心问题&quot;,
        f&quot;子任务 3: 提出 {task} 的解决方案&quot;
    ]
    
    return {&quot;subtasks&quot;: subtasks, &quot;current_subtask&quot;: 0}

def worker_execute(state: ManagerWorkerState) -&gt; ManagerWorkerState:
    &quot;&quot;&quot;工人：执行子任务&quot;&quot;&quot;
    current = state[&quot;current_subtask&quot;]
    subtask = state[&quot;subtasks&quot;][current]
    
    # 模拟执行
    result = f&quot;[完成] {subtask}\n详细内容：...&quot;
    
    results = state.get(&quot;results&quot;, []) + [result]
    
    return {
        &quot;results&quot;: results,
        &quot;current_subtask&quot;: current + 1
    }

def check_all_subtasks_done(state: ManagerWorkerState) -&gt; Literal[&quot;continue&quot;, &quot;aggregate&quot;]:
    &quot;&quot;&quot;检查是否所有子任务完成&quot;&quot;&quot;
    if state[&quot;current_subtask&quot;] &amp;#x3C; len(state[&quot;subtasks&quot;]):
        return &quot;continue&quot;
    return &quot;aggregate&quot;

def manager_aggregate(state: ManagerWorkerState) -&gt; ManagerWorkerState:
    &quot;&quot;&quot;主管：汇总结果&quot;&quot;&quot;
    results = state[&quot;results&quot;]
    
    final_output = &quot;=== 任务执行报告 ===\n\n&quot;
    for i, result in enumerate(results, 1):
        final_output += f&quot;{i}. {result}\n\n&quot;
    
    final_output += &quot;=== 总结 ===\n所有子任务已完成。&quot;
    
    return {&quot;final_output&quot;: final_output}

# 构建主管 - 工人图
workflow = StateGraph(ManagerWorkerState)

workflow.add_node(&quot;manager_decompose&quot;, manager_decompose)
workflow.add_node(&quot;worker_execute&quot;, worker_execute)
workflow.add_node(&quot;manager_aggregate&quot;, manager_aggregate)

workflow.add_edge(START, &quot;manager_decompose&quot;)
workflow.add_edge(&quot;manager_decompose&quot;, &quot;worker_execute&quot;)

workflow.add_conditional_edges(
    &quot;worker_execute&quot;,
    check_all_subtasks_done,
    {
        &quot;continue&quot;: &quot;worker_execute&quot;,
        &quot;aggregate&quot;: &quot;manager_aggregate&quot;
    }
)

workflow.add_edge(&quot;manager_aggregate&quot;, END)

mw_app = workflow.compile()

result = mw_app.invoke({
    &quot;task&quot;: &quot;设计一个推荐系统&quot;,
    &quot;subtasks&quot;: [],
    &quot;results&quot;: [],
    &quot;current_subtask&quot;: 0,
    &quot;final_output&quot;: &quot;&quot;
})

print(result[&quot;final_output&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;START → manager_decompose → worker_execute ──(循环)──┐
                                           ↓         │
                                  check ──→│         │
                                           ↓         │
                                  manager_aggregate ←─┘
                                           ↓
                                          END
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;4.2 投票与共识模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：多个 Agent 独立给出答案，通过投票或共识机制确定最终结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提高答案准确性&lt;/li&gt;
&lt;li&gt;减少单个模型偏差&lt;/li&gt;
&lt;li&gt;重要决策&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并行独立评估&lt;/li&gt;
&lt;li&gt;投票/共识机制&lt;/li&gt;
&lt;li&gt;可能包含仲裁&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import List, Literal

class VotingState(TypedDict):
    question: str
    votes: List[str]
    current_voter: int
    total_voters: int
    final_answer: str

def voter_answer(state: VotingState) -&gt; VotingState:
    &quot;&quot;&quot;投票者：给出答案&quot;&quot;&quot;
    voter_id = state[&quot;current_voter&quot;]
    
    # 模拟不同投票者的答案
    answers = {
        0: &quot;答案 A: 基于方法 1 的分析&quot;,
        1: &quot;答案 A: 基于方法 2 的分析&quot;,
        2: &quot;答案 B: 基于方法 3 的分析&quot;
    }
    
    answer = answers.get(voter_id, f&quot;答案：投票者 {voter_id}&quot;)
    votes = state.get(&quot;votes&quot;, []) + [answer]
    
    return {
        &quot;votes&quot;: votes,
        &quot;current_voter&quot;: voter_id + 1
    }

def tally_votes(state: VotingState) -&gt; VotingState:
    &quot;&quot;&quot;统计票数&quot;&quot;&quot;
    votes = state[&quot;votes&quot;]
    
    # 简单统计（实际应使用更复杂的语义相似度）
    vote_counts = {}
    for vote in votes:
        answer_type = vote.split(&quot;:&quot;)[0]  # 提取答案类型
        vote_counts[answer_type] = vote_counts.get(answer_type, 0) + 1
    
    # 找出得票最多的答案
    winner = max(vote_counts, key=vote_counts.get)
    final_answer = f&quot;{winner} (得票：{vote_counts[winner]}/{len(votes)})&quot;
    
    return {&quot;final_answer&quot;: final_answer, &quot;vote_counts&quot;: vote_counts}

# 构建投票图
workflow = StateGraph(VotingState)

workflow.add_node(&quot;voter&quot;, voter_answer)
workflow.add_node(&quot;tally&quot;, tally_votes)

workflow.add_edge(START, &quot;voter&quot;)

workflow.add_conditional_edges(
    &quot;voter&quot;,
    lambda s: &quot;tally&quot; if s[&quot;current_voter&quot;] &gt;= s[&quot;total_voters&quot;] else &quot;voter&quot;,
    {
        &quot;voter&quot;: &quot;voter&quot;,
        &quot;tally&quot;: &quot;tally&quot;
    }
)

workflow.add_edge(&quot;tally&quot;, END)

voting_app = workflow.compile()

result = voting_app.invoke({
    &quot;question&quot;: &quot;哪种算法最适合此场景？&quot;,
    &quot;votes&quot;: [],
    &quot;current_voter&quot;: 0,
    &quot;total_voters&quot;: 3,
    &quot;final_answer&quot;: &quot;&quot;
})

print(f&quot;最终答案：{result[&apos;final_answer&apos;]}&quot;)
print(f&quot;投票统计：{result.get(&apos;vote_counts&apos;, {})}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;4.3 流水线协作模式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：多个 Agent 按顺序协作，每个负责特定阶段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容创作流水线&lt;/li&gt;
&lt;li&gt;代码开发流程&lt;/li&gt;
&lt;li&gt;数据分析流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阶段划分清晰&lt;/li&gt;
&lt;li&gt;状态传递&lt;/li&gt;
&lt;li&gt;专业化分工&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class PipelineState(TypedDict):
    topic: str
    research: str
    outline: str
    draft: str
    review: str
    final_article: str

def researcher(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;研究阶段：收集信息&quot;&quot;&quot;
    research = f&quot;&quot;&quot;
    关于 {state[&apos;topic&apos;]} 的研究结果:
    - 关键点 1: ...
    - 关键点 2: ...
    - 数据支持：...
    &quot;&quot;&quot;
    return {&quot;research&quot;: research}

def outliner(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;大纲阶段：组织结构&quot;&quot;&quot;
    outline = f&quot;&quot;&quot;
    文章大纲 - {state[&apos;topic&apos;]}:
    1. 引言
    2. 核心概念
    3. 案例分析
    4. 最佳实践
    5. 总结
    &quot;&quot;&quot;
    return {&quot;outline&quot;: outline}

def writer(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;写作阶段：生成草稿&quot;&quot;&quot;
    draft = f&quot;&quot;&quot;
    # {state[&apos;topic&apos;]}
    
    ## 引言
    基于研究：{state[&apos;research&apos;][:50]}...
    
    ## 正文
    按照大纲展开...
    
    ## 总结
    核心观点总结...
    &quot;&quot;&quot;
    return {&quot;draft&quot;: draft}

def reviewer(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;评审阶段：质量检查&quot;&quot;&quot;
    review = &quot;&quot;&quot;
    评审意见:
    ✅ 结构清晰
    ✅ 论据充分
    ⚠️ 建议增加案例
    ✅ 语言流畅
    
    评分：85/100
    &quot;&quot;&quot;
    return {&quot;review&quot;: review}

def finalizer(state: PipelineState) -&gt; PipelineState:
    &quot;&quot;&quot;定稿阶段：整合输出&quot;&quot;&quot;
    final_article = f&quot;&quot;&quot;
    {state[&apos;draft&apos;]}
    
    ---
    
    评审反馈：{state[&apos;review&apos;]}
    &quot;&quot;&quot;
    return {&quot;final_article&quot;: final_article}

# 构建流水线协作图
workflow = StateGraph(PipelineState)

workflow.add_node(&quot;researcher&quot;, researcher)
workflow.add_node(&quot;outliner&quot;, outliner)
workflow.add_node(&quot;writer&quot;, writer)
workflow.add_node(&quot;reviewer&quot;, reviewer)
workflow.add_node(&quot;finalizer&quot;, finalizer)

# 顺序连接
workflow.add_edge(START, &quot;researcher&quot;)
workflow.add_edge(&quot;researcher&quot;, &quot;outliner&quot;)
workflow.add_edge(&quot;outliner&quot;, &quot;writer&quot;)
workflow.add_edge(&quot;writer&quot;, &quot;reviewer&quot;)
workflow.add_edge(&quot;reviewer&quot;, &quot;finalizer&quot;)
workflow.add_edge(&quot;finalizer&quot;, END)

pipeline_app = workflow.compile()

result = pipeline_app.invoke({&quot;topic&quot;: &quot;机器学习入门&quot;, &quot;research&quot;: &quot;&quot;, &quot;outline&quot;: &quot;&quot;, &quot;draft&quot;: &quot;&quot;, &quot;review&quot;: &quot;&quot;, &quot;final_article&quot;: &quot;&quot;})

print(result[&quot;final_article&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 人机协作模式&lt;/h2&gt;
&lt;h3&gt;5.1 人类审核节点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：在关键节点暂停执行，等待人类审核或决策。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容发布前审核&lt;/li&gt;
&lt;li&gt;高风险决策确认&lt;/li&gt;
&lt;li&gt;质量把关&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;中断执行流&lt;/li&gt;
&lt;li&gt;等待人类输入&lt;/li&gt;
&lt;li&gt;恢复执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import interrupt
from typing import Literal

class HumanInTheLoopState(TypedDict):
    content: str
    human_approved: bool
    human_feedback: str
    iteration: int

def generate_content(state: HumanInTheLoopState) -&gt; HumanInTheLoopState:
    &quot;&quot;&quot;生成内容&quot;&quot;&quot;
    if state[&quot;iteration&quot;] == 0:
        content = &quot;初始版本：这是一篇测试文章...&quot;
    else:
        content = f&quot;修订版本 {state[&apos;iteration&apos;]}: 根据反馈 &apos;{state[&apos;human_feedback&apos;]}&apos; 修改...&quot;
    
    return {&quot;content&quot;: content}

def human_review(state: HumanInTheLoopState) -&gt; HumanInTheLoopState:
    &quot;&quot;&quot;人类审核节点&quot;&quot;&quot;
    print(f&quot;\n=== 等待人类审核 ===&quot;)
    print(f&quot;当前内容：{state[&apos;content&apos;]}&quot;)
    
    # 中断执行，等待人类输入
    # 实际使用中，这里会暂停并等待外部输入
    approval = input(&quot;是否批准？(y/n/r 修订): &quot;)
    
    if approval == &apos;y&apos;:
        return {&quot;human_approved&quot;: True, &quot;human_feedback&quot;: &quot;&quot;}
    elif approval == &apos;n&apos;:
        return {&quot;human_approved&quot;: False, &quot;human_feedback&quot;: &quot;&quot;}
    else:
        feedback = input(&quot;提供修订意见：&quot;)
        return {&quot;human_approved&quot;: False, &quot;human_feedback&quot;: feedback}

def decide_next(state: HumanInTheLoopState) -&gt; Literal[&quot;approve&quot;, &quot;revise&quot;, &quot;reject&quot;]:
    &quot;&quot;&quot;决定下一步&quot;&quot;&quot;
    if state[&quot;human_approved&quot;]:
        return &quot;approve&quot;
    elif state[&quot;iteration&quot;] &gt;= 3:
        return &quot;reject&quot;  # 达到最大修订次数
    return &quot;revise&quot;

# 构建人机协作图
workflow = StateGraph(HumanInTheLoopState)

workflow.add_node(&quot;generate&quot;, generate_content)
workflow.add_node(&quot;review&quot;, human_review)

workflow.add_edge(START, &quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;review&quot;)

workflow.add_conditional_edges(
    &quot;review&quot;,
    decide_next,
    {
        &quot;approve&quot;: END,
        &quot;revise&quot;: &quot;generate&quot;,
        &quot;reject&quot;: END
    }
)

# 编译时设置中断点
app = workflow.compile(interrupt_before=[&quot;review&quot;])

# 执行
result = app.invoke({
    &quot;content&quot;: &quot;&quot;,
    &quot;human_approved&quot;: False,
    &quot;human_feedback&quot;: &quot;&quot;,
    &quot;iteration&quot;: 0
})

print(f&quot;\n最终结果：{result[&apos;content&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用 interrupt 工具&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import interrupt

def human_approval_node(state):
    &quot;&quot;&quot;使用 interrupt 工具&quot;&quot;&quot;
    # 抛出中断，暂停执行
    human_decision = interrupt({
        &quot;content&quot;: state[&quot;content&quot;],
        &quot;question&quot;: &quot;是否批准此内容？&quot;
    })
    
    return {
        &quot;human_approved&quot;: human_decision.get(&quot;approved&quot;, False),
        &quot;human_feedback&quot;: human_decision.get(&quot;feedback&quot;, &quot;&quot;)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;5.2 中断与恢复&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：支持在任意点暂停和恢复执行，保持状态持久化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心特征&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;状态检查点&lt;/li&gt;
&lt;li&gt;线程管理&lt;/li&gt;
&lt;li&gt;断点续传&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.checkpoint.memory import MemorySaver

# 创建检查点存储器
checkpointer = MemorySaver()

# 编译时传入
app = workflow.compile(
    checkpointer=checkpointer,
    interrupt_before=[&quot;human_review&quot;]
)

# 创建线程
config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;conversation_123&quot;}}

# 执行到中断点
result = app.invoke(
    {&quot;content&quot;: &quot;初始内容&quot;, &quot;iteration&quot;: 0},
    config
)
# 此时暂停，等待人类输入

# 恢复执行，传入人类决策
result = app.invoke(
    {&quot;human_approved&quot;: True, &quot;human_feedback&quot;: &quot;&quot;},
    config
)
# 从断点继续执行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;多线程管理&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 不同用户使用不同线程
user1_config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;user_001&quot;}}
user2_config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;user_002&quot;}}

# 独立执行，互不干扰
app.invoke({&quot;input&quot;: &quot;user1 data&quot;}, user1_config)
app.invoke({&quot;input&quot;: &quot;user2 data&quot;}, user2_config)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;5.3 反馈循环&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：人类反馈直接影响后续生成，形成闭环优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class FeedbackLoopState(TypedDict):
    prompt: str
    output: str
    feedback: str
    quality_score: float
    iterations: int

def generate(state: FeedbackLoopState) -&gt; FeedbackLoopState:
    &quot;&quot;&quot;生成内容&quot;&quot;&quot;
    # 根据反馈调整生成
    context = state[&quot;feedback&quot;] if state[&quot;feedback&quot;] else &quot;无反馈&quot;
    output = f&quot;生成内容 (基于反馈：{context})&quot;
    
    return {&quot;output&quot;: output}

def get_feedback(state: FeedbackLoopState) -&gt; FeedbackLoopState:
    &quot;&quot;&quot;获取人类反馈&quot;&quot;&quot;
    # 模拟反馈（实际为真实人类输入）
    feedback = &quot;建议增加更多细节&quot;
    score = 0.8
    
    return {
        &quot;feedback&quot;: feedback,
        &quot;quality_score&quot;: score
    }

def should_improve(state: FeedbackLoopState) -&gt; Literal[&quot;improve&quot;, &quot;done&quot;]:
    &quot;&quot;&quot;判断是否需要改进&quot;&quot;&quot;
    if state[&quot;quality_score&quot;] &gt;= 0.9:
        return &quot;done&quot;
    if state[&quot;iterations&quot;] &gt;= 5:
        return &quot;done&quot;
    return &quot;improve&quot;

workflow = StateGraph(FeedbackLoopState)

workflow.add_node(&quot;generate&quot;, generate)
workflow.add_node(&quot;feedback&quot;, get_feedback)

workflow.add_edge(START, &quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;feedback&quot;)

workflow.add_conditional_edges(
    &quot;feedback&quot;,
    should_improve,
    {
        &quot;improve&quot;: &quot;generate&quot;,
        &quot;done&quot;: END
    }
)

feedback_app = workflow.compile()

result = feedback_app.invoke({
    &quot;prompt&quot;: &quot;写一篇文章&quot;,
    &quot;output&quot;: &quot;&quot;,
    &quot;feedback&quot;: &quot;&quot;,
    &quot;quality_score&quot;: 0.0,
    &quot;iterations&quot;: 0
})

print(f&quot;最终输出：{result[&apos;output&apos;]}&quot;)
print(f&quot;质量分数：{result[&apos;quality_score&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. 高级模式：模式组合&lt;/h2&gt;
&lt;h3&gt;6.1 嵌套状态机&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：状态机中包含子状态机，处理复杂分层逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂任务分层&lt;/li&gt;
&lt;li&gt;模块化设计&lt;/li&gt;
&lt;li&gt;代码复用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 子图：内容生成模块
def create_content_subgraph():
    &quot;&quot;&quot;创建内容生成子图&quot;&quot;&quot;
    class ContentState(TypedDict):
        topic: str
        draft: str
        refined: str
    
    def write_draft(state: ContentState) -&gt; ContentState:
        return {&quot;draft&quot;: f&quot;草稿：{state[&apos;topic&apos;]}&quot;}
    
    def refine_draft(state: ContentState) -&gt; ContentState:
        return {&quot;refined&quot;: f&quot;精修：{state[&apos;draft&apos;]}&quot;}
    
    subgraph = StateGraph(ContentState)
    subgraph.add_node(&quot;write&quot;, write_draft)
    subgraph.add_node(&quot;refine&quot;, refine_draft)
    subgraph.add_edge(START, &quot;write&quot;)
    subgraph.add_edge(&quot;write&quot;, &quot;refine&quot;)
    subgraph.add_edge(&quot;refine&quot;, END)
    
    return subgraph.compile()

# 主图：使用子图
class MainState(TypedDict):
    topic: str
    content: str
    reviewed: bool

def prepare_topic(state: MainState) -&gt; MainState:
    &quot;&quot;&quot;准备主题&quot;&quot;&quot;
    return {&quot;topic&quot;: state[&quot;topic&quot;].upper()}

def review_content(state: MainState) -&gt; MainState:
    &quot;&quot;&quot;审核内容&quot;&quot;&quot;
    return {&quot;reviewed&quot;: True}

main_workflow = StateGraph(MainState)

# 添加子图为节点
content_subgraph = create_content_subgraph()
main_workflow.add_node(&quot;content_generator&quot;, content_subgraph)

main_workflow.add_node(&quot;prepare&quot;, prepare_topic)
main_workflow.add_node(&quot;review&quot;, review_content)

main_workflow.add_edge(START, &quot;prepare&quot;)
main_workflow.add_edge(&quot;prepare&quot;, &quot;content_generator&quot;)
main_workflow.add_edge(&quot;content_generator&quot;, &quot;review&quot;)
main_workflow.add_edge(&quot;review&quot;, END)

main_app = main_workflow.compile()

result = main_app.invoke({&quot;topic&quot;: &quot;人工智能&quot;, &quot;content&quot;: &quot;&quot;, &quot;reviewed&quot;: False})
print(f&quot;结果：{result}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;6.2 模式混合实践&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模式描述&lt;/strong&gt;：在实际应用中组合多种模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;综合案例：智能内容生产系统&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class ContentSystemState(TypedDict):
    topic: str
    research_results: list
    draft: str
    human_approved: bool
    feedback: str
    iteration: int
    final_content: str

# 1. 并行研究（并行模式）
async def parallel_research(state: ContentSystemState) -&gt; ContentSystemState:
    tasks = [
        search_web(state[&quot;topic&quot;]),
        query_knowledge_base(state[&quot;topic&quot;]),
        analyze_trends(state[&quot;topic&quot;])
    ]
    results = await asyncio.gather(*tasks)
    return {&quot;research_results&quot;: results}

# 2. 迭代生成（循环模式）
def iterative_generation(state: ContentSystemState) -&gt; ContentSystemState:
    context = &quot;\n&quot;.join(state[&quot;research_results&quot;])
    feedback = state.get(&quot;feedback&quot;, &quot;&quot;)
    
    draft = f&quot;基于研究和反馈 {feedback} 生成的内容...&quot;
    return {&quot;draft&quot;: draft}

# 3. 人类审核（人机协作模式）
def human_approval(state: ContentSystemState) -&gt; ContentSystemState:
    approval = interrupt({&quot;draft&quot;: state[&quot;draft&quot;]})
    return {
        &quot;human_approved&quot;: approval.get(&quot;approved&quot;, False),
        &quot;feedback&quot;: approval.get(&quot;feedback&quot;, &quot;&quot;)
    }

# 4. 多模式组合
workflow = StateGraph(ContentSystemState)

workflow.add_node(&quot;research&quot;, parallel_research)
workflow.add_node(&quot;generate&quot;, iterative_generation)
workflow.add_node(&quot;approve&quot;, human_approval)

workflow.add_edge(START, &quot;research&quot;)
workflow.add_edge(&quot;research&quot;, &quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;approve&quot;)

workflow.add_conditional_edges(
    &quot;approve&quot;,
    lambda s: &quot;generate&quot; if not s[&quot;human_approved&quot;] else &quot;end&quot;,
    {&quot;generate&quot;: &quot;generate&quot;, &quot;end&quot;: END}
)

system_app = workflow.compile(interrupt_before=[&quot;approve&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. 反模式与最佳实践&lt;/h2&gt;
&lt;h3&gt;7.1 常见设计陷阱&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;陷阱 1：状态爆炸&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ❌ 反模式：状态包含过多字段
class BadState(TypedDict):
    field1: str
    field2: str
    # ... 50+ 字段
    field50: str

# ✅ 最佳实践：状态模块化
class ResearchState(TypedDict):
    query: str
    results: list

class WritingState(TypedDict):
    research: ResearchState
    draft: str
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;陷阱 2：循环依赖&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ❌ 反模式：节点 A 依赖 B，B 依赖 A
def node_a(state):
    return node_b(state)  # 直接调用其他节点

# ✅ 最佳实践：通过状态传递数据
def node_a(state):
    return {&quot;data&quot;: process(state[&quot;input&quot;])}

def node_b(state):
    return {&quot;result&quot;: use(state[&quot;data&quot;])}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;陷阱 3：忽略错误处理&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# ❌ 反模式：没有错误处理
def risky_node(state):
    result = might_fail()
    return {&quot;result&quot;: result}

# ✅ 最佳实践：try-catch 或重试模式
def safe_node(state):
    try:
        result = might_fail()
    except Exception as e:
        return {&quot;error&quot;: str(e), &quot;result&quot;: None}
    return {&quot;result&quot;: result}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;7.2 性能优化建议&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 并行化独立任务&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 使用并行模式加速独立操作
async def parallel_processing(state):
    results = await asyncio.gather(
        task1(state[&quot;data&quot;]),
        task2(state[&quot;data&quot;]),
        task3(state[&quot;data&quot;])
    )
    return {&quot;results&quot;: results}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 缓存中间结果&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_computation(input_data):
    return compute(input_data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 流式处理大数据&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def stream_processor(state):
    for chunk in state[&quot;large_data&quot;]:
        yield process(chunk)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;7.3 可维护性原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 单一职责&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每个节点只做一件事，便于测试和复用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 明确的状态契约&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class State(TypedDict):
    &quot;&quot;&quot;
    状态说明：
    - input: 用户输入（只读）
    - output: 节点输出（写入）
    - metadata: 元数据（可选）
    &quot;&quot;&quot;
    input: str
    output: str
    metadata: dict
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 可视化文档&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 生成可视化图
app.get_graph().draw_mermaid_png(output_path=&quot;workflow.png&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. 实战案例：完整工作流构建&lt;/h2&gt;
&lt;h3&gt;8.1 需求分析&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景&lt;/strong&gt;：构建一个智能客服系统&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;理解用户意图&lt;/li&gt;
&lt;li&gt;检索相关知识&lt;/li&gt;
&lt;li&gt;生成回答&lt;/li&gt;
&lt;li&gt;质量检查&lt;/li&gt;
&lt;li&gt;人类审核（必要时）&lt;/li&gt;
&lt;li&gt;发送回答&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;8.2 模式选择&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;条件分支&lt;/strong&gt;：意图识别路由&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行执行&lt;/strong&gt;：多源知识检索&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环模式&lt;/strong&gt;：回答优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人机协作&lt;/strong&gt;：复杂问题审核&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class CustomerServiceState(TypedDict):
    user_query: str
    intent: str
    knowledge: list
    draft_answer: str
    quality_score: float
    human_reviewed: bool
    final_answer: str

# 意图识别
def classify_intent(state: CustomerServiceState) -&gt; CustomerServiceState:
    # 模拟意图分类
    intent = &quot;technical_support&quot;  # 实际使用 LLM
    return {&quot;intent&quot;: intent}

# 并行知识检索
async def retrieve_knowledge(state: CustomerServiceState) -&gt; CustomerServiceState:
    results = await asyncio.gather(
        search_faq(state[&quot;user_query&quot;]),
        search_docs(state[&quot;intent&quot;]),
        query_database(state[&quot;user_query&quot;])
    )
    return {&quot;knowledge&quot;: [r for r in results if r]}

# 生成回答
def generate_answer(state: CustomerServiceState) -&gt; CustomerServiceState:
    context = &quot;\n&quot;.join(state[&quot;knowledge&quot;])
    answer = f&quot;基于 {len(state[&apos;knowledge&apos;])} 个知识源的回答...&quot;
    return {&quot;draft_answer&quot;: answer}

# 质量检查
def check_quality(state: CustomerServiceState) -&gt; CustomerServiceState:
    # 模拟质量评分
    score = 0.85
    return {&quot;quality_score&quot;: score}

# 人类审核（低质量时）
def human_review(state: CustomerServiceState) -&gt; CustomerSystemState:
    if state[&quot;quality_score&quot;] &amp;#x3C; 0.8:
        decision = interrupt({&quot;answer&quot;: state[&quot;draft_answer&quot;]})
        return {&quot;human_reviewed&quot;: True, &quot;final_answer&quot;: decision[&quot;revised_answer&quot;]}
    return {&quot;human_reviewed&quot;: False, &quot;final_answer&quot;: state[&quot;draft_answer&quot;]}

# 构建完整工作流
workflow = StateGraph(CustomerServiceState)

workflow.add_node(&quot;classify&quot;, classify_intent)
workflow.add_node(&quot;retrieve&quot;, retrieve_knowledge)
workflow.add_node(&quot;generate&quot;, generate_answer)
workflow.add_node(&quot;check&quot;, check_quality)
workflow.add_node(&quot;review&quot;, human_review)

workflow.add_edge(START, &quot;classify&quot;)
workflow.add_edge(&quot;classify&quot;, &quot;retrieve&quot;)
workflow.add_edge(&quot;retrieve&quot;, &quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;check&quot;)
workflow.add_edge(&quot;check&quot;, &quot;review&quot;)
workflow.add_edge(&quot;review&quot;, END)

cs_app = workflow.compile(interrupt_before=[&quot;review&quot;])

# 执行
result = cs_app.invoke({&quot;user_query&quot;: &quot;如何重置密码？&quot;})
print(f&quot;最终回答：{result[&apos;final_answer&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.4 测试与调试&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 单元测试单个节点
def test_classify_intent():
    state = {&quot;user_query&quot;: &quot;密码问题&quot;}
    result = classify_intent(state)
    assert result[&quot;intent&quot;] == &quot;account_management&quot;

# 集成测试完整流程
def test_full_workflow():
    result = cs_app.invoke({&quot;user_query&quot;: &quot;测试问题&quot;})
    assert &quot;final_answer&quot; in result
    assert len(result[&quot;final_answer&quot;]) &gt; 0

# 可视化调试
cs_app.get_graph().draw_mermaid_png(&quot;customer_service.png&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 总结与资源&lt;/h2&gt;
&lt;h3&gt;9.1 模式速查表&lt;/h3&gt;
&lt;p&gt;| 模式 | 适用场景 | 复杂度 |
|------|---------|--------|
| 线性流水线 | 顺序处理 | ⭐ |
| 条件分支 | 路由决策 | ⭐⭐ |
| 并行执行 | 并发任务 | ⭐⭐ |
| 固定迭代 | 次数确定 | ⭐⭐ |
| 条件终止 | 质量导向 | ⭐⭐⭐ |
| 重试退避 | 容错处理 | ⭐⭐ |
| 主管 - 工人 | 任务分解 | ⭐⭐⭐ |
| 投票共识 | 提高准确性 | ⭐⭐⭐ |
| 人类审核 | 质量把关 | ⭐⭐⭐ |
| 嵌套状态机 | 分层设计 | ⭐⭐⭐⭐ |&lt;/p&gt;
&lt;h3&gt;9.2 学习路径建议&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;入门&lt;/strong&gt;：掌握线性、条件、并行基础模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶&lt;/strong&gt;：学习循环和协作模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高级&lt;/strong&gt;：实践模式组合和嵌套&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;专家&lt;/strong&gt;：自定义模式和框架扩展&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;9.3 推荐资源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://langchain-ai.github.io/langgraph/&quot;&gt;LangGraph 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/langgraph&quot;&gt;LangGraph GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612&quot;&gt;设计模式经典书籍&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.4 下一步&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在实际项目中应用这些模式&lt;/li&gt;
&lt;li&gt;贡献新的模式到社区&lt;/li&gt;
&lt;li&gt;关注 LangGraph 官方更新&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;感谢阅读！&lt;/strong&gt; 希望这篇文章能帮助你构建更强大、更可靠的 LangGraph 应用。🚀&lt;/p&gt;
</content:encoded><category>LangGraph</category><category>设计模式</category><category>状态机</category><category>Agent</category><category>工作流</category><category>AI 工程</category></item><item><title>如何成为一名 LLM 算法工程师</title><link>https://heyedwardchen.com/blog/llm-algorithm-engineer-roadmap/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/llm-algorithm-engineer-roadmap/</guid><description>从基础、Transformer、RAG、微调、偏好优化到推理部署的 LLM 算法工程师学习路线</description><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;如何成为一名 LLM 算法工程师&lt;/h1&gt;
&lt;p&gt;这篇文章记录如何规划 LLM 算法工程师的学习路径，也希望给同样在入门和进阶的人一个可复用的参考。&lt;/p&gt;
&lt;p&gt;不停留在“会调用大模型 API”这个层面，也不想只读论文、看概念，却没有可以复现和交付的东西。目标是逐步建立一套能落地的 LLM 算法能力：理解模型机制，能处理和构造数据，能做训练与后训练实验，能搭建可评估的 RAG/Agent 系统，也能把模型用可观测、可控成本的方式部署到业务场景里。&lt;/p&gt;
&lt;p&gt;这条路线会按“先跑通、再理解、再优化、最后交付”的节奏推进。每个阶段都会留下明确产出，避免学习变成碎片化收藏。&lt;/p&gt;
&lt;h2&gt;目标画像&lt;/h2&gt;
&lt;p&gt;LLM 算法工程师最终需要具备四层能力：&lt;/p&gt;
&lt;p&gt;| 能力层 | 我要回答的问题 | 阶段产出 |
|------|------|------|
| 基础层 | 我能否稳定训练、调试、复现实验？ | PyTorch 训练循环、实验记录、误差分析 |
| 模型层 | 我是否理解 Transformer、生成、微调和对齐？ | mini Transformer、SFT/LoRA/DPO 实验报告 |
| 系统层 | 我能否把模型接入数据、工具、检索和评估？ | RAG 服务、评估集、可观测 trace |
| 交付层 | 我能否解释成本、延迟、效果和风险？ | benchmark、上线方案、模型卡、复盘文档 |&lt;/p&gt;
&lt;p&gt;四层交替推进。只学理论会缺少工程判断，只堆框架会很难定位效果问题，只追大模型新闻会缺少可复用的基本功。&lt;/p&gt;
&lt;h2&gt;方向选择&lt;/h2&gt;
&lt;p&gt;LLM 算法岗位有很多分支，需要先确定主线，再补齐旁支。&lt;/p&gt;
&lt;p&gt;| 方向 | 我为什么关注 | 重点能力 | 我可以做的项目 |
|------|------|------|------|
| LLM 应用算法 | 最容易和真实业务结合，也最适合快速形成作品 | Prompt、RAG、Agent、评估、业务闭环 | 企业知识库问答、智能客服、BI Agent |
| 模型微调与后训练 | 能建立更深的算法壁垒，避免只会调 API | 数据构造、SFT、LoRA、DPO/GRPO、训练诊断 | 领域指令模型、偏好优化实验 |
| 推理部署与性能优化 | 私有化部署和成本控制是企业场景的关键问题 | vLLM、量化、批处理、KV cache、吞吐延迟权衡 | 私有化模型服务、推理网关 |
| 研究复现与评测 | 训练我判断新方法是否真的有效 | paper reading、benchmark、ablation、误差归因 | 论文复现、模型能力评估 |&lt;/p&gt;
&lt;p&gt;当前的主线会先放在“LLM 应用算法 + 评估”上，因为它最容易形成闭环；第二阶段再深入“微调与后训练”；如果后续有明确私有化部署需求，会把“推理部署”作为差异化能力继续加强。&lt;/p&gt;
&lt;h2&gt;Milestone 进度看板&lt;/h2&gt;
&lt;p&gt;这是后续持续更新学习进度的主表。每完成一个 milestone，不只勾选状态，还要补上证据：代码仓库、实验截图、指标表、博客文章或复盘结论。&lt;/p&gt;
&lt;p&gt;| Milestone | 建议周期 | 状态 | 完成度 | 验收产出 | 证据链接 / 笔记 |
|------|------|------|------|------|------|
| M0：定位与环境基线 | 第 1 周 | [ ] 未开始 [x] 进行中 [ ] 完成 | 10% | 能在本机或云 GPU 跑通一个开源 LLM 推理样例 | 当前已完成学习规划 |
| M1：Python、数学与 PyTorch 基础 | 第 2-4 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 独立写出训练循环，并解释 loss、梯度、优化器和过拟合 |  |
| M2：NLP 与 Transformer 基础 | 第 5-8 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 实现一个 mini decoder-only Transformer 或完成逐行阅读笔记 |  |
| M3：LLM 推理、Prompt 与工具调用 | 第 9-10 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 建立一套 prompt 测试集，能稳定输出结构化结果 |  |
| M4：RAG 与评估体系 | 第 11-14 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 做出一个带 retrieval/generation eval 的知识库问答系统 |  |
| M5：SFT、PEFT 与数据工程 | 第 15-19 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 使用 LoRA/QLoRA 完成一次领域微调并写实验报告 |  |
| M6：偏好优化与后训练 | 第 20-23 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 完成 DPO 或 GRPO toy experiment，说明偏好数据如何影响输出 |  |
| M7：推理服务与性能优化 | 第 24-27 周 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 部署 OpenAI-compatible LLM 服务并完成吞吐、延迟、成本 benchmark |  |
| M8：作品集与面试闭环 | 持续进行 | [ ] 未开始 [ ] 进行中 [ ] 完成 | 0% | 形成 3-5 个可讲清楚 trade-off 的项目和文章 |  |&lt;/p&gt;
&lt;h2&gt;M0：定位与环境基线&lt;/h2&gt;
&lt;p&gt;这个阶段的目标不是一开始就训练大模型，而是先建立可复现实验环境。只要环境和记录方式不稳定，后面的训练、评估和复盘都会变得很混乱。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Python 环境管理：&lt;code&gt;uv&lt;/code&gt;、&lt;code&gt;conda&lt;/code&gt; 或 &lt;code&gt;venv&lt;/code&gt; 任选其一，但要能固定依赖。&lt;/li&gt;
&lt;li&gt;基础工具：Git、Linux shell、Jupyter、VS Code、CUDA/MPS 基本排障。&lt;/li&gt;
&lt;li&gt;LLM 基础库：&lt;code&gt;torch&lt;/code&gt;、&lt;code&gt;transformers&lt;/code&gt;、&lt;code&gt;datasets&lt;/code&gt;、&lt;code&gt;accelerate&lt;/code&gt;、&lt;code&gt;peft&lt;/code&gt;、&lt;code&gt;trl&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;推理入口：本地小模型、云端 API、OpenAI-compatible server 至少跑通一种。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 创建一个专门的 &lt;code&gt;llm-learning&lt;/code&gt; 仓库。&lt;/li&gt;
&lt;li&gt;[ ] 固定 Python 版本和依赖文件。&lt;/li&gt;
&lt;li&gt;[ ] 跑通一个 tokenizer 示例，解释 token、context window、special tokens。&lt;/li&gt;
&lt;li&gt;[ ] 跑通一个小模型生成示例，记录输入、输出、耗时、显存或内存占用。&lt;/li&gt;
&lt;li&gt;[ ] 写一篇环境搭建和踩坑记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;目录规划&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;llm-learning/
├── README.md
├── env/
├── notebooks/
├── experiments/
├── evals/
└── notes/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;M1：Python、数学与 PyTorch 基础&lt;/h2&gt;
&lt;p&gt;这一阶段不追求“知道很多模型名字”，而是把训练过程拆开看清楚。以后遇到 loss 不降、显存溢出、数据泄漏、过拟合这些问题时，需要能自己定位，而不是只换框架或换模型。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Python 数据处理：迭代器、类型标注、日志、配置、单元测试。&lt;/li&gt;
&lt;li&gt;数学基础：线性代数、概率统计、信息论、微积分中与梯度相关的部分。&lt;/li&gt;
&lt;li&gt;机器学习基础：训练/验证/测试拆分，偏差方差，正则化，交叉熵，指标选择。&lt;/li&gt;
&lt;li&gt;PyTorch 基础：Tensor、autograd、&lt;code&gt;nn.Module&lt;/code&gt;、optimizer、scheduler、DataLoader、混合精度。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 用 PyTorch 写一个完整训练循环，不依赖高级 trainer。&lt;/li&gt;
&lt;li&gt;[ ] 画出训练集和验证集 loss 曲线，并解释是否过拟合。&lt;/li&gt;
&lt;li&gt;[ ] 实现 early stopping、checkpoint、resume training。&lt;/li&gt;
&lt;li&gt;[ ] 对同一模型至少做 3 次学习率或 batch size 对比实验。&lt;/li&gt;
&lt;li&gt;[ ] 写一份“训练异常排查清单”：loss 不降、梯度爆炸、显存溢出、数据泄漏。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;练习安排&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;用 sklearn 或 PyTorch 训练一个文本分类 baseline。&lt;/li&gt;
&lt;li&gt;用纯 PyTorch 复现一个小型语言模型训练循环。&lt;/li&gt;
&lt;li&gt;把实验配置、随机种子、指标和模型文件统一记录下来。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;M2：NLP 与 Transformer 基础&lt;/h2&gt;
&lt;p&gt;Transformer 是 LLM 的核心抽象。这个阶段不希望只停留在“Self-Attention 很重要”，而是要能解释每个张量形状如何变化，以及训练和生成阶段的差异。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;NLP 基础：分词、词向量、语言模型、序列标注、文本分类、问答。&lt;/li&gt;
&lt;li&gt;Tokenization：BPE、WordPiece、Unigram、chat template、特殊 token。&lt;/li&gt;
&lt;li&gt;Transformer：embedding、positional encoding、multi-head attention、FFN、LayerNorm、residual。&lt;/li&gt;
&lt;li&gt;Decoder-only LLM：causal mask、next-token prediction、KV cache、generation decoding。&lt;/li&gt;
&lt;li&gt;生成策略：greedy、beam search、temperature、top-k、top-p、repetition penalty。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 画出 decoder-only Transformer 的数据流。&lt;/li&gt;
&lt;li&gt;[ ] 手写 scaled dot-product attention，并验证输入输出 shape。&lt;/li&gt;
&lt;li&gt;[ ] 解释 causal mask 为什么训练时并行、生成时自回归。&lt;/li&gt;
&lt;li&gt;[ ] 实现一个 mini Transformer language model，哪怕只在小语料上训练。&lt;/li&gt;
&lt;li&gt;[ ] 阅读并整理 Transformer、RAG、LoRA、DPO 相关论文笔记。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;阅读顺序&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先读课程讲义或教程，建立概念图。&lt;/li&gt;
&lt;li&gt;再读论文摘要、方法图和实验设置。&lt;/li&gt;
&lt;li&gt;最后复现最小可运行版本，不一开始追求完整训练规模。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;M3：LLM 推理、Prompt 与工具调用&lt;/h2&gt;
&lt;p&gt;这个阶段会把 Prompt 当成可测试的工程接口，而不是凭感觉写提示词。要学会把任务定义、上下文、约束、输出格式和评估标准说清楚。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chat message 结构：system、user、assistant、tool。&lt;/li&gt;
&lt;li&gt;输出控制：JSON schema、结构化输出、函数调用、错误重试。&lt;/li&gt;
&lt;li&gt;Prompt 设计：任务说明、少样本示例、反例、边界条件、引用上下文。&lt;/li&gt;
&lt;li&gt;推理参数：temperature、top-p、max tokens、stop、seed。&lt;/li&gt;
&lt;li&gt;评估方式：黄金集、人工评分、LLM-as-judge、规则断言、回归测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 为一个真实任务建立 30-100 条测试输入。&lt;/li&gt;
&lt;li&gt;[ ] 为输出定义可机器检查的 schema。&lt;/li&gt;
&lt;li&gt;[ ] 比较至少 3 版 prompt，并记录准确率、稳定性和失败样例。&lt;/li&gt;
&lt;li&gt;[ ] 加入工具调用或函数调用，处理调用失败和空结果。&lt;/li&gt;
&lt;li&gt;[ ] 写出“什么时候该 prompt，什么时候该 RAG，什么时候该微调”的判断规则。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;判断规则&lt;/h3&gt;
&lt;p&gt;| 问题类型 | 我会优先选择 | 原因 |
|------|------|------|
| 输出格式不稳定 | 结构化输出 / 函数调用 | 我先约束接口，不急着训练 |
| 缺少私有知识 | RAG | 知识可更新，成本低于微调 |
| 固定风格或固定流程 | Prompt + eval | 快速迭代，容易回归测试 |
| 领域术语和输出习惯长期稳定 | SFT / LoRA | 让模型学习模式，而不是每次塞长 prompt |
| 人类偏好难以用规则描述 | DPO / RLHF / GRPO | 用偏好数据优化排序或行为 |&lt;/p&gt;
&lt;h2&gt;M4：RAG 与评估体系&lt;/h2&gt;
&lt;p&gt;RAG 是最想先做出闭环的方向。它的难点不是“把文档放进向量库”，而是让检索、重排、生成和评估能互相校验。没有评估的 RAG 很容易变成“看起来能答，实际上不可控”的系统。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文档处理：解析、清洗、切块、去重、metadata、版本管理。&lt;/li&gt;
&lt;li&gt;向量检索：embedding、相似度、召回率、top-k、hybrid search。&lt;/li&gt;
&lt;li&gt;重排：cross encoder reranker、规则 rerank、业务字段加权。&lt;/li&gt;
&lt;li&gt;生成：上下文压缩、引用来源、拒答策略、答案格式控制。&lt;/li&gt;
&lt;li&gt;评估：retrieval recall、faithfulness、answer correctness、latency、成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 选一个知识库，例如 Obsidian、项目文档或部署文档。&lt;/li&gt;
&lt;li&gt;[ ] 构造至少 50 条问题，标注答案来源文档。&lt;/li&gt;
&lt;li&gt;[ ] 分别评估 chunk size、top-k、embedding model、reranker 对结果的影响。&lt;/li&gt;
&lt;li&gt;[ ] 输出每次回答引用的来源片段。&lt;/li&gt;
&lt;li&gt;[ ] 建立失败样例分类：没召回、召回错、上下文冲突、模型幻觉、格式错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;项目设想&lt;/h3&gt;
&lt;p&gt;先做一个“Obsidian 知识库问答”项目：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 Markdown 文档，保留标题层级和路径 metadata。&lt;/li&gt;
&lt;li&gt;将内容切块并写入 FAISS、Milvus 或 pgvector。&lt;/li&gt;
&lt;li&gt;对每个问题返回答案、引用段落和置信度。&lt;/li&gt;
&lt;li&gt;使用固定评估集比较不同切块和检索策略。&lt;/li&gt;
&lt;li&gt;把失败样例写回笔记，形成下一轮优化任务。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;M5：SFT、PEFT 与数据工程&lt;/h2&gt;
&lt;p&gt;进入微调阶段后，要提醒自己：微调的核心不是把训练命令跑起来，而是理解数据为什么能改变模型行为。多数微调失败不是算法失败，而是数据定义、格式、分布和评估出了问题。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据格式：instruction、input、output、conversation、chat template。&lt;/li&gt;
&lt;li&gt;数据质量：去重、清洗、长度分布、标签一致性、泄漏检查。&lt;/li&gt;
&lt;li&gt;SFT：teacher forcing、loss mask、packing、learning rate、warmup、checkpoint。&lt;/li&gt;
&lt;li&gt;PEFT：LoRA、QLoRA、rank、alpha、target modules、merge。&lt;/li&gt;
&lt;li&gt;实验诊断：训练 loss、验证 loss、样例输出、灾难性遗忘、过拟合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 构造一个 500-5000 条的领域指令数据集。&lt;/li&gt;
&lt;li&gt;[ ] 写数据检查脚本，统计长度、空值、重复、异常标签。&lt;/li&gt;
&lt;li&gt;[ ] 使用 LoRA/QLoRA 完成一次 SFT。&lt;/li&gt;
&lt;li&gt;[ ] 设计 baseline：原始模型、prompt-only、SFT 模型对比。&lt;/li&gt;
&lt;li&gt;[ ] 写实验报告，包含配置、指标、样例、失败分析和下一步计划。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实验报告模板&lt;/h3&gt;
&lt;p&gt;| 项目 | 我要记录的内容 |
|------|------|
| 目标 | 这次微调希望改善什么行为 |
| 数据 | 数据来源、规模、格式、清洗规则 |
| 模型 | base model、上下文长度、tokenizer |
| 训练 | batch size、learning rate、epoch、LoRA 参数 |
| 评估 | 自动指标、人工评分、失败样例 |
| 结论 | 是否值得继续扩大数据或训练规模 |&lt;/p&gt;
&lt;h2&gt;M6：偏好优化与后训练&lt;/h2&gt;
&lt;p&gt;这个阶段会学习如何让模型更符合偏好，而不是简单学会答案。它通常用于改善风格、安全性、帮助性、拒答边界、推理过程或多候选答案排序。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;偏好数据：chosen/rejected、pairwise ranking、rubric、标注一致性。&lt;/li&gt;
&lt;li&gt;Reward modeling：奖励模型目标、过优化风险、reward hacking。&lt;/li&gt;
&lt;li&gt;DPO：直接用偏好对优化策略模型，工程上比完整 RLHF 更容易入门。&lt;/li&gt;
&lt;li&gt;GRPO/RLHF：理解 policy、reward、advantage、KL 约束、采样成本。&lt;/li&gt;
&lt;li&gt;安全与边界：拒答、敏感问题、越权工具调用、幻觉控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 从一个任务中采集至少 200 组 chosen/rejected 样例。&lt;/li&gt;
&lt;li&gt;[ ] 定义偏好标注规则，并记录难判样例。&lt;/li&gt;
&lt;li&gt;[ ] 跑通 DPO 或 GRPO 的最小实验。&lt;/li&gt;
&lt;li&gt;[ ] 比较 SFT 模型与偏好优化模型的输出差异。&lt;/li&gt;
&lt;li&gt;[ ] 写出偏好优化带来的收益、代价和风险。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;需要注意&lt;/h3&gt;
&lt;p&gt;偏好优化不适合拿来修复所有问题。如果模型缺少知识，优先做 RAG；如果输出格式不稳定，优先做结构化输出；如果领域任务样例不足，优先补 SFT 数据；只有当“多个可行答案之间的偏好”本身很重要时，再考虑 DPO、RLHF 或 GRPO。&lt;/p&gt;
&lt;h2&gt;M7：推理服务与性能优化&lt;/h2&gt;
&lt;p&gt;模型效果只是上线的一半。真实业务还会关心吞吐、首 token 延迟、总延迟、显存、并发、成本、稳定性和降级策略。这个阶段要从“模型能跑”推进到“服务可用”。&lt;/p&gt;
&lt;h3&gt;需要掌握&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;推理引擎：vLLM、SGLang、TGI、llama.cpp 的定位和差异。&lt;/li&gt;
&lt;li&gt;性能概念：prefill、decode、KV cache、continuous batching、tensor parallel。&lt;/li&gt;
&lt;li&gt;压缩优化：量化、蒸馏、speculative decoding、上下文压缩。&lt;/li&gt;
&lt;li&gt;服务接口：OpenAI-compatible API、鉴权、限流、超时、重试。&lt;/li&gt;
&lt;li&gt;可观测性：请求日志、trace、token 用量、延迟分位数、错误分类。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;验收清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 使用 vLLM 或同类引擎部署一个 OpenAI-compatible endpoint。&lt;/li&gt;
&lt;li&gt;[ ] 对不同并发、输入长度、输出长度做 benchmark。&lt;/li&gt;
&lt;li&gt;[ ] 记录 TTFT、TPOT、吞吐、显存占用和错误率。&lt;/li&gt;
&lt;li&gt;[ ] 设计限流、超时、fallback 和日志脱敏策略。&lt;/li&gt;
&lt;li&gt;[ ] 写一份“模型上线前检查清单”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Benchmark 记录表&lt;/h3&gt;
&lt;p&gt;| 模型 | 引擎 | 并发 | 输入 tokens | 输出 tokens | TTFT | 总延迟 P50/P95 | 吞吐 tokens/s | 显存 | 备注 |
|------|------|------|------|------|------|------|------|------|------|
|      |      |      |      |      |      |      |      |      |      |&lt;/p&gt;
&lt;h2&gt;M8：作品集与面试闭环&lt;/h2&gt;
&lt;p&gt;作品集不需要堆很多 demo。希望每个项目都能讲清楚“问题、方案、指标、失败、取舍”。如果一个项目只能演示，不能解释取舍，它对长期成长的帮助就有限。&lt;/p&gt;
&lt;h3&gt;作品集规划&lt;/h3&gt;
&lt;p&gt;| 项目 | 我想展示的能力 | 最低验收标准 |
|------|------|------|
| mini Transformer 复现 | 模型基础 | 能解释 attention、mask、loss 和生成过程 |
| RAG 知识库问答 | 应用算法 | 有评估集、引用、失败分类和优化记录 |
| 领域 LoRA 微调 | 后训练入门 | 有数据报告、baseline 对比和误差分析 |
| 偏好优化实验 | 对齐理解 | 有 chosen/rejected 数据和 DPO/GRPO 对比 |
| vLLM 推理服务 | 工程交付 | 有 benchmark、限流、日志和部署说明 |&lt;/p&gt;
&lt;h3&gt;需要能回答的问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Transformer 中 Q/K/V 的作用是什么？为什么需要 multi-head？&lt;/li&gt;
&lt;li&gt;causal mask 解决什么问题？训练和生成阶段有什么不同？&lt;/li&gt;
&lt;li&gt;RAG 中 chunk size、top-k、reranker 如何影响召回和幻觉？&lt;/li&gt;
&lt;li&gt;什么场景该用 prompt，什么场景该用 RAG，什么场景才需要微调？&lt;/li&gt;
&lt;li&gt;LoRA 的 rank、alpha、target modules 分别影响什么？&lt;/li&gt;
&lt;li&gt;SFT 后效果变差，会如何排查数据、训练和评估问题？&lt;/li&gt;
&lt;li&gt;DPO 和 RLHF 的核心区别是什么？&lt;/li&gt;
&lt;li&gt;vLLM 为什么能提升吞吐？KV cache 对显存有什么影响？&lt;/li&gt;
&lt;li&gt;如何设计一个 LLM 应用的离线评估和线上监控？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;每周学习节奏&lt;/h2&gt;
&lt;p&gt;把每周学习固定成四类动作，避免只输入不输出。&lt;/p&gt;
&lt;p&gt;| 动作 | 时间占比 | 执行要求 |
|------|------|------|
| 学概念 | 25% | 课程、文档、论文，目标是建立概念图 |
| 写代码 | 35% | 每周至少一个可运行实验 |
| 做评估 | 25% | 用固定数据集比较不同方案 |
| 写复盘 | 15% | 记录失败样例、指标变化和下一步 |&lt;/p&gt;
&lt;h2&gt;进度日志模板&lt;/h2&gt;
&lt;p&gt;后续更新学习进度时，直接复制这个模板追加到本节下面。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;### YYYY-MM-DD

- Milestone：
- 本周完成：
- 关键证据：
- 指标变化：
- 主要卡点：
- 下周计划：
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2026-05-20&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Milestone：M0&lt;/li&gt;
&lt;li&gt;本周完成：创建 LLM 算法工程师学习路线和 milestone 进度看板。&lt;/li&gt;
&lt;li&gt;关键证据：本文档。&lt;/li&gt;
&lt;li&gt;指标变化：暂无。&lt;/li&gt;
&lt;li&gt;主要卡点：还需要确认每周可投入时间，并尽快搭建第一个学习仓库。&lt;/li&gt;
&lt;li&gt;下周计划：搭建 &lt;code&gt;llm-learning&lt;/code&gt; 仓库，跑通 tokenizer 和小模型推理基线。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;学习资料路径&lt;/h2&gt;
&lt;p&gt;下面这些资料按“先官方教程，再论文，再工程文档”的顺序使用。&lt;/p&gt;
&lt;p&gt;| 类别 | 资料 | 使用方式 |
|------|------|------|
| 课程 | &lt;a href=&quot;https://web.stanford.edu/class/cs224n/&quot;&gt;Stanford CS224N&lt;/a&gt; | 用来补 NLP、Transformer、LLM 评估和 Agent/RAG 基础 |
| 官方教程 | &lt;a href=&quot;https://huggingface.co/docs/transformers&quot;&gt;Hugging Face Transformers 文档&lt;/a&gt; | 学 tokenizer、模型加载、Trainer、生成和微调 |
| 后训练 | &lt;a href=&quot;https://huggingface.co/docs/trl&quot;&gt;Hugging Face TRL 文档&lt;/a&gt; | 学 SFT、DPO、GRPO、Reward Modeling 等后训练方法 |
| 深度学习工程 | &lt;a href=&quot;https://docs.pytorch.org/tutorials/&quot;&gt;PyTorch Tutorials&lt;/a&gt; | 学训练循环、Transformer、分布式训练和性能优化 |
| Prompt/API | &lt;a href=&quot;https://help.openai.com/en/articles/6654000-playground-and-prompt-engineering&quot;&gt;OpenAI Prompt Engineering Guide&lt;/a&gt; | 学指令、上下文、输出格式和 prompt 评估 |
| 评估 | &lt;a href=&quot;https://platform.openai.com/docs/guides/evals&quot;&gt;OpenAI Evals Guide&lt;/a&gt; | 学如何系统化构建模型输出评估 |
| RAG 评估 | &lt;a href=&quot;https://docs.ragas.io/en/latest/references/evaluate/&quot;&gt;Ragas Evaluation&lt;/a&gt; | 学 RAG 自动评估的指标和接口 |
| 推理服务 | &lt;a href=&quot;https://docs.vllm.ai/en/latest/serving/online_serving/&quot;&gt;vLLM Online Serving&lt;/a&gt; | 学 OpenAI-compatible serving、并发和部署参数 |
| 论文 | &lt;a href=&quot;https://arxiv.org/abs/1706.03762&quot;&gt;Attention Is All You Need&lt;/a&gt; | Transformer 原始论文 |
| 论文 | &lt;a href=&quot;https://arxiv.org/abs/2005.11401&quot;&gt;Retrieval-Augmented Generation&lt;/a&gt; | RAG 经典论文 |
| 论文 | &lt;a href=&quot;https://arxiv.org/abs/2106.09685&quot;&gt;LoRA&lt;/a&gt; | PEFT 经典论文 |
| 论文 | &lt;a href=&quot;https://arxiv.org/abs/2305.18290&quot;&gt;Direct Preference Optimization&lt;/a&gt; | DPO 经典论文 |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;h3&gt;技术栈总结&lt;/h3&gt;
&lt;p&gt;| 组件 | 版本 | 我会用它做什么 |
|------|------|------|
| Python | 3.11 / 3.12 | 固定项目运行环境 |
| PyTorch | 当前稳定版 | 深度学习训练和推理基础 |
| Transformers | 当前稳定版 | 模型、tokenizer、Trainer 和生成接口 |
| Datasets / Accelerate | 当前稳定版 | 数据集处理和训练加速 |
| PEFT / TRL | 当前稳定版 | LoRA、SFT、DPO、GRPO 等微调与后训练 |
| FAISS / Milvus / pgvector | 任选 | RAG 向量检索 |
| Ragas / 自建 eval | 任选 | RAG 和 LLM 应用评估 |
| vLLM | 当前稳定版 | 高吞吐 LLM 推理服务 |
| LangSmith / OpenTelemetry / 自建日志 | 任选 | trace、评估和线上观测 |&lt;/p&gt;
&lt;h3&gt;核心步骤回顾&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;建立可复现实验环境，不急着训练大模型。&lt;/li&gt;
&lt;li&gt;补齐 PyTorch、NLP 和 Transformer 基础。&lt;/li&gt;
&lt;li&gt;用 Prompt、RAG 和 eval 做出可交付应用。&lt;/li&gt;
&lt;li&gt;用 SFT、LoRA 和偏好优化提升模型行为。&lt;/li&gt;
&lt;li&gt;用 vLLM、benchmark 和可观测性完成工程闭环。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;下一步优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 确认主攻方向：应用算法、微调后训练、推理部署或研究评测。&lt;/li&gt;
&lt;li&gt;[ ] 创建 &lt;code&gt;llm-learning&lt;/code&gt; 仓库并补充环境说明。&lt;/li&gt;
&lt;li&gt;[ ] 建立第一版评估集。&lt;/li&gt;
&lt;li&gt;[ ] 用 Obsidian 文档做一个 RAG baseline。&lt;/li&gt;
&lt;li&gt;[ ] 每周更新一次 milestone 进度看板和进度日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;更新时间&lt;/strong&gt;: 2026-05-20&lt;br&gt;
&lt;strong&gt;阅读时间&lt;/strong&gt;: 18 分钟&lt;br&gt;
&lt;strong&gt;适用场景&lt;/strong&gt;: LLM 算法工程师学习规划、转岗准备、学习进度管理、作品集建设&lt;/p&gt;
</content:encoded><category>LLM</category><category>算法工程师</category><category>学习日志</category><category>学习规划</category><category>Transformer</category><category>RAG</category><category>Fine-tuning</category></item><item><title>Obsidian 完全指南：从入门到精通</title><link>https://heyedwardchen.com/blog/obsidian-complete-guide/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/obsidian-complete-guide/</guid><description>深入探索 Obsidian 笔记应用，掌握高级用法、高效插件和工作流，打造你的第二大脑</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Obsidian 完全指南：从入门到精通&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;摘要&lt;/strong&gt;：Obsidian 是一款基于本地 Markdown 文件的强大笔记应用，通过双向链接、图谱视图和丰富的插件生态，帮助用户构建个人知识体系。本文将详细介绍 Obsidian 的核心功能、高级用法、高效插件以及如何将其融入实际工作流。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;什么是 Obsidian&lt;/h2&gt;
&lt;p&gt;Obsidian 是一款&lt;strong&gt;以本地 Markdown 文件为基础&lt;/strong&gt;的双向链接笔记应用，由 Shida Li 和 Erica Xu 于 2020 年推出。它的核心理念是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据本地化&lt;/strong&gt;：所有笔记都是本地 Markdown 文件，完全掌控你的数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双向链接&lt;/strong&gt;：通过 &lt;code&gt;[[链接]]&lt;/code&gt; 语法建立笔记间的关联，形成知识网络&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可插拔架构&lt;/strong&gt;：丰富的社区插件生态系统，按需扩展功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨平台同步&lt;/strong&gt;：支持 Windows、macOS、Linux、iOS 和 Android&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;为什么选择 Obsidian&lt;/h3&gt;
&lt;p&gt;| 特性 | Obsidian | Notion | Roam Research |
|------|----------|--------|---------------|
| 数据存储 | 本地 Markdown | 云端 | 云端 |
| 离线可用 | ✅ | ❌ | ❌ |
| 双向链接 | ✅ | ✅ | ✅ |
| 插件生态 | 🌟🌟🌟🌟🌟 | 🌟🌟🌟 | 🌟🌟 |
| 启动速度 | ⚡ 极快 | 🐢 较慢 | 🐢 较慢 |
| 自定义程度 | 极高 | 中等 | 中等 |
| 价格 | 免费 | 收费 | 收费 |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;核心功能详解&lt;/h2&gt;
&lt;h3&gt;1. 双向链接 (Backlinks)&lt;/h3&gt;
&lt;p&gt;双向链接是 Obsidian 的核心功能，让你轻松建立笔记间的关联。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本语法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;这是普通文本，链接到 [[另一篇笔记]] 的内容。

自动创建链接：[[新笔记]]（如果不存在会自动创建）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;高级用法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# 显示不同文本
[[目标笔记 | 自定义显示文本]]

# 链接到特定锚点
[[目标笔记#锚点]]

# 嵌入笔记内容
![[嵌入的笔记]]

# 嵌入特定块
![[笔记#^block-id]]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 图谱视图 (Graph View)&lt;/h3&gt;
&lt;p&gt;图谱视图可视化展示笔记间的连接关系，帮助你发现知识间的隐藏联系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拖拽节点重新布局&lt;/li&gt;
&lt;li&gt;点击节点高亮相关连接&lt;/li&gt;
&lt;li&gt;使用过滤器按标签、文件夹筛选&lt;/li&gt;
&lt;li&gt;调整连接距离和节点大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 反向链接面板&lt;/h3&gt;
&lt;p&gt;右侧面板显示所有链接到当前笔记的内容，分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;反向链接&lt;/strong&gt;：直接链接到当前笔记&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;未链接的提及&lt;/strong&gt;：提到但未建立链接的内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双向链接&lt;/strong&gt;：当前笔记链接到的内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 模板系统&lt;/h3&gt;
&lt;p&gt;通过模板快速创建标准化笔记结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基础模板示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
title: &quot;{{title}}&quot;
date: {{date}}
tags: []
---

# {{title}}

## 概述

## 详细内容

## 参考
- 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 标签系统&lt;/h3&gt;
&lt;p&gt;标签用于跨文件夹组织内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;tags: [开发，教程，#工具/obsidian]

# 层级标签
#工作/项目/博客
#学习/编程/python
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;高级用法与技巧&lt;/h2&gt;
&lt;h3&gt;1. 属性 (Frontmatter)&lt;/h3&gt;
&lt;p&gt;每篇笔记顶部可以添加 YAML frontmatter，用于元数据管理：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
title: &quot;文章标题&quot;
date: 2025-05-18
updated: 2025-05-20
status: draft
tags: [obsidian, 教程]
aliases: [别名 1, 别名 2]
icon: 📝
cover: &quot;path/to/image.jpg&quot;
---
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 转义字符与特殊语法&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# 转义链接
\[\[这不是链接\]\]

# 数学公式
行内公式：$E = mc^2$
块级公式：
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$

# 任务列表
- [x] 已完成任务
- [ ] 待办任务
- [ ] 子任务
  - [ ] 子子任务

# 折叠内容
&gt; [!collapse] 点击展开
&gt; 这里是折叠的内容
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 代码块高亮&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```python hl_lines=&quot;3 5&quot;
def hello():
    name = &quot;Obsidian&quot;
    if name:
        print(f&quot;Hello, {name}!&quot;)
    return True
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 调用笔记 (Transclusion)&lt;/h3&gt;
&lt;p&gt;将笔记内容嵌入到其他笔记中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# 嵌入整篇笔记
![[相关笔记]]

# 嵌入特定部分（需要 Dataview）
```dataview
TABLE WITHOUT ID
  field1 as &quot;字段 1&quot;,
  field2 as &quot;字段 2&quot;
FROM &quot;文件夹&quot;
WHERE status = &quot;active&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 块引用与块 ID&lt;/h3&gt;
&lt;p&gt;为段落添加唯一 ID，实现精确引用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;这是一个重要概念^#concept-001

在另一篇笔记中引用：![[当前笔记#^concept-001]]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;必备高效插件&lt;/h2&gt;
&lt;h3&gt;核心插件（内置）&lt;/h3&gt;
&lt;h4&gt;1. Templates（模板）&lt;/h4&gt;
&lt;p&gt;快速插入预设模板，标准化笔记结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配置建议&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;模板文件夹：/Templates
日期格式：YYYY-MM-DD
时间格式：HH:mm
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Daily Notes（每日笔记）&lt;/h4&gt;
&lt;p&gt;自动创建每日日志，记录日常思考和任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模板示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# 📅 {{date}}

## 🎯 今日目标
- 

## 📝 工作记录

## 💡 想法与灵感

## ✅ 完成事项
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. Advanced Tables（高级表格）&lt;/h4&gt;
&lt;p&gt;提供类似 Excel 的表格编辑体验，支持快捷键操作。&lt;/p&gt;
&lt;h4&gt;4. Command Palette（命令面板）&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Ctrl/Cmd + P&lt;/code&gt; 快速访问所有功能。&lt;/p&gt;
&lt;h3&gt;社区插件推荐&lt;/h3&gt;
&lt;h4&gt;1. Dataview ⭐⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：查询和展示笔记数据，将 Obsidian 变成数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dataview&quot;&gt;LIST FROM &quot;博客&quot; 
WHERE status = &quot;published&quot; 
SORT file.ctime DESC
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-dataview&quot;&gt;TABLE date as &quot;日期&quot;, tags as &quot;标签&quot;
FROM &quot;日记&quot;
WHERE contains(tags, &quot;重要&quot;)
GROUP BY month(date)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Kanban ⭐⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：创建看板，管理项目和任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kanban&quot;&gt;列标题 1
- [ ] 任务 1
- [x] 任务 2

列标题 2
- [ ] 任务 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. Calendar ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：可视化日历视图，快速访问每日笔记。&lt;/p&gt;
&lt;h4&gt;4. Outline ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：显示当前文档的目录结构，快速导航。&lt;/p&gt;
&lt;h4&gt;5. Word Count ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：统计字数、字符数、阅读时间。&lt;/p&gt;
&lt;h4&gt;6. Slides ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：将笔记转换为幻灯片演示。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
slide: true
---

# 第一页

---

# 第二页
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;7. Excalidraw ⭐⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：在笔记中绘制手绘风格的图表和思维导图。&lt;/p&gt;
&lt;h4&gt;8. Linter ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：自动格式化笔记，统一风格。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配置示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;rule-1-heading-bullet&quot;: true,
  &quot;rule-2-space-after-list-markers&quot;: true,
  &quot;rule-11-footnote-after-punctuation&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;9. Auto Note Mover ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：根据规则自动移动笔记到对应文件夹。&lt;/p&gt;
&lt;h4&gt;10. Hot Reload ⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：修改 CSS 和插件后自动刷新，无需重启。&lt;/p&gt;
&lt;h4&gt;11. QuickAdd ⭐⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：快速添加内容到指定位置，支持宏和脚本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例宏&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建新笔记&lt;/li&gt;
&lt;li&gt;插入模板&lt;/li&gt;
&lt;li&gt;添加标签&lt;/li&gt;
&lt;li&gt;打开笔记&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;12. Tasks ⭐⭐⭐⭐⭐&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：强大的任务管理，支持跨笔记查询任务。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```tasks
NOT DONE
PATH 包含 &quot;工作&quot;
GROUP BY heading
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;13. Admonition（已内置为 Callouts）&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;功能&lt;/strong&gt;：创建可折叠的提示框。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&gt; [!INFO] 信息
&gt; 这是信息提示框

&gt; [!TIP] 提示
&gt; 这是提示信息

&gt; [!WARNING] 警告
&gt; 这是警告信息

&gt; [!QUOTE] 引用
&gt; 这是引用内容
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;与工作流结合&lt;/h2&gt;
&lt;h3&gt;1. 博客工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Obsidian → Astro 博客&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 目录结构
Vault/
├── blog/          # 博客文章
├── Templates/        # 模板
├── Assets/          # 资源文件
└── sync-and-commit.sh  # 同步脚本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;同步脚本示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# 同步 Obsidian 博客到 GitHub

OBSIDIAN_PATH=&quot;/path/to/blog&quot;
REPO_PATH=&quot;/path/to/blog-repo/content&quot;

# 复制文件
cp -r &quot;$OBSIDIAN_PATH&quot;/* &quot;$REPO_PATH/&quot;

# 提交更改
cd &quot;$REPO_PATH&quot;
git add .
git commit -m &quot;更新博客文章 $(date +%Y-%m-%d)&quot;
git push
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 项目管理工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;使用 Kanban + Dataview 管理项目&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;项目看板&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kanban&quot;&gt;待办
- [[任务 1]] 📅 2025-05-20
- [[任务 2]]

进行中
- [[任务 3]] 👤 Edward

已完成
- [[任务 4]] ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;项目统计&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-dataview&quot;&gt;TASK FROM &quot;项目&quot;
GROUP BY status
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 学习工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Zettelkasten（卡片盒）方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fleeting Notes&lt;/strong&gt;：快速记录想法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Literature Notes&lt;/strong&gt;：整理参考资料&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Permanent Notes&lt;/strong&gt;：形成永久知识卡片&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;文件夹结构&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00-Inbox/          # 临时笔记
10-Areas/          # 长期关注领域
20-Projects/       # 正在进行的项目
30-Resources/      # 参考资料
40-Archives/       # 归档内容
99-Templates/      # 模板
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 会议记录工作流&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;会议模板&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
title: &quot;{{meeting_name}}&quot;
date: {{date}}
attendees: []
project: []
---

# 📋 {{meeting_name}}

&gt; 📅 {{date}} | 👥 {{attendees}}

## 🎯 目标

## 📝 讨论要点

## ✅ 行动项
- [ ] @责任人 任务描述 📅 截止日期

## 📎 参考资料
- 
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;1. 命名规范&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用有意义的文件名&lt;/li&gt;
&lt;li&gt;避免特殊字符&lt;/li&gt;
&lt;li&gt;保持一致的命名风格&lt;/li&gt;
&lt;li&gt;示例：&lt;code&gt;YYYY-MM-DD-会议主题.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 标签策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用层级标签：&lt;code&gt;#类别/子类别&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;控制标签数量（建议&amp;#x3C;50 个）&lt;/li&gt;
&lt;li&gt;定期清理无用标签&lt;/li&gt;
&lt;li&gt;使用 Dataview 查询标签&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 链接策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;链接概念而非关键词&lt;/li&gt;
&lt;li&gt;使用描述性链接文本&lt;/li&gt;
&lt;li&gt;定期整理孤立笔记&lt;/li&gt;
&lt;li&gt;利用反向链接发现关联&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 备份与同步&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;推荐方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;官方同步&lt;/strong&gt;：Obsidian Sync（付费，最方便）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git 同步&lt;/strong&gt;：适合开发者，版本控制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;云盘同步&lt;/strong&gt;：iCloud、Dropbox、OneDrive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三方工具&lt;/strong&gt;：Remotely Save、Syncthing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 性能优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;控制库大小（建议&amp;#x3C;10,000 笔记）&lt;/li&gt;
&lt;li&gt;定期归档旧笔记&lt;/li&gt;
&lt;li&gt;使用资源文件夹管理媒体&lt;/li&gt;
&lt;li&gt;禁用不用的插件&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Obsidian 不仅仅是一个笔记应用，它是一个&lt;strong&gt;知识管理系统&lt;/strong&gt;，帮助你：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;建立知识网络&lt;/strong&gt;：通过双向链接连接想法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提升工作效率&lt;/strong&gt;：模板、快捷键、插件自动化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长期知识积累&lt;/strong&gt;：本地存储，永久可用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个性化定制&lt;/strong&gt;：CSS、插件、主题无限可能&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;学习路线建议&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一周&lt;/strong&gt;：掌握基本操作、双向链接、模板&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第一月&lt;/strong&gt;：学习 Dataview、配置常用插件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三月&lt;/strong&gt;：建立自己的工作流，探索高级功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续&lt;/strong&gt;：关注社区插件，优化个人系统&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://help.obsidian.md/&quot;&gt;Obsidian 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forum.obsidian.md/&quot;&gt;Obsidian 论坛&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://obsidian.md/plugins&quot;&gt;Obsidian 插件库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/@obsidianmd&quot;&gt;Obsidian YouTube 频道&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;最后更新：2025-05-18&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>obsidian</category></item><item><title>Ansible 自动化运维完整指南</title><link>https://heyedwardchen.com/blog/ansible-complete-guide/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/ansible-complete-guide/</guid><description>从零掌握 Ansible 自动化运维工具，涵盖基础概念、Playbook 编写、角色管理、最佳实践和高级应用。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Ansible 自动化运维完整指南&lt;/h1&gt;
&lt;p&gt;本文将全面介绍 Ansible 自动化运维工具，从基础概念到高级应用，帮助你构建高效、可维护的基础设施自动化体系。&lt;/p&gt;
&lt;h2&gt;为什么选择 Ansible？&lt;/h2&gt;
&lt;h3&gt;配置管理工具对比&lt;/h3&gt;
&lt;p&gt;| 工具 | 架构 | Agent | 学习曲线 | 适用场景 |
|------|------|-------|----------|----------|
| &lt;strong&gt;Ansible&lt;/strong&gt; | Push | 无需 | 低 | 快速部署、简单运维 |
| Puppet | Pull | 需要 | 高 | 大型复杂环境 |
| Chef | Pull | 需要 | 高 | 企业级配置管理 |
| SaltStack | Push/Pull | 可选 | 中 | 高性能需求 |&lt;/p&gt;
&lt;h3&gt;Ansible 的核心优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无代理架构&lt;/strong&gt; — 通过 SSH 连接，无需安装 Agent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单易学&lt;/strong&gt; — YAML 语法，直观易懂&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等性&lt;/strong&gt; — 多次执行结果一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;丰富模块&lt;/strong&gt; — 3000+ 内置模块&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社区活跃&lt;/strong&gt; — 大量社区角色和插件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双向通信&lt;/strong&gt; — 支持 ad-hoc 命令和 Playbook&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;h3&gt;1. 控制节点 (Control Node)&lt;/h3&gt;
&lt;p&gt;运行 Ansible 的机器，可以是任何 Linux/Mac 系统。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装 Ansible
sudo apt update
sudo apt install ansible

# 验证安装
ansible --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 管理主机 (Managed Nodes)&lt;/h3&gt;
&lt;p&gt;被 Ansible 管理的目标服务器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;要求：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装 OpenSSH Server&lt;/li&gt;
&lt;li&gt;与控制节点网络可达&lt;/li&gt;
&lt;li&gt;支持 SSH 密钥认证（推荐）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 库存 (Inventory)&lt;/h3&gt;
&lt;p&gt;定义被管理主机的配置文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;静态库存示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# /etc/ansible/hosts 或 inventory.ini

[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11 ansible_user=ubuntu

[dbservers]
db1 ansible_host=192.168.1.20 ansible_user=root
db2 ansible_host=192.168.1.21 ansible_user=root

[loadbalancers]
lb1 ansible_host=192.168.1.5 ansible_user=admin

# 组合组
[web:children]
webservers
loadbalancers

# 变量定义
[webservers:vars]
http_port=80
max_clients=200

# 所有服务器
[all:vars]
ansible_ssh_common_args=&apos;-o StrictHostKeyChecking=no&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;动态库存示例（JSON 格式）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;webservers&quot;: {
    &quot;hosts&quot;: [&quot;web1.example.com&quot;, &quot;web2.example.com&quot;],
    &quot;vars&quot;: {
      &quot;http_port&quot;: 80
    }
  },
  &quot;dbservers&quot;: {
    &quot;hosts&quot;: [&quot;db1.example.com&quot;, &quot;db2.example.com&quot;],
    &quot;vars&quot;: {
      &quot;db_port&quot;: 3306
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 模块 (Modules)&lt;/h3&gt;
&lt;p&gt;Ansible 的基本执行单元，完成特定任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用模块：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 模块 | 用途 | 示例 |
|------|------|------|
| &lt;code&gt;command&lt;/code&gt; | 执行命令 | &lt;code&gt;command: /usr/bin/uptime&lt;/code&gt; |
| &lt;code&gt;shell&lt;/code&gt; | Shell 命令 | &lt;code&gt;shell: ls -la \| grep log&lt;/code&gt; |
| &lt;code&gt;copy&lt;/code&gt; | 复制文件 | &lt;code&gt;copy: src=/local/file dest=/remote/file&lt;/code&gt; |
| &lt;code&gt;template&lt;/code&gt; | 模板渲染 | &lt;code&gt;template: src=config.j2 dest=/etc/app.conf&lt;/code&gt; |
| &lt;code&gt;yum&lt;/code&gt;/&lt;code&gt;apt&lt;/code&gt; | 包管理 | &lt;code&gt;yum: name=nginx state=present&lt;/code&gt; |
| &lt;code&gt;service&lt;/code&gt; | 服务管理 | &lt;code&gt;service: name=nginx state=started&lt;/code&gt; |
| &lt;code&gt;user&lt;/code&gt; | 用户管理 | &lt;code&gt;user: name=app useradd=yes&lt;/code&gt; |
| &lt;code&gt;file&lt;/code&gt; | 文件管理 | &lt;code&gt;file: path=/var/log/app state=directory&lt;/code&gt; |
| &lt;code&gt;git&lt;/code&gt; | Git 操作 | &lt;code&gt;git: repo=URL dest=/app version=main&lt;/code&gt; |
| &lt;code&gt;debug&lt;/code&gt; | 调试输出 | &lt;code&gt;debug: msg=&quot;Hello&quot;&lt;/code&gt; |&lt;/p&gt;
&lt;h3&gt;5. Playbook&lt;/h3&gt;
&lt;p&gt;YAML 格式的任务清单，定义自动化流程。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 配置 Web 服务器
  hosts: webservers
  become: yes  # 使用 sudo
  vars:
    nginx_version: &quot;1.24&quot;
    
  tasks:
    - name: 安装 Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
    
    - name: 启动 Nginx 服务
      service:
        name: nginx
        state: started
        enabled: yes
    
    - name: 复制配置文件
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: 重启 Nginx
      
  handlers:
    - name: 重启 Nginx
      service:
        name: nginx
        state: restarted
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;快速开始&lt;/h2&gt;
&lt;h3&gt;1. 配置 SSH 免密登录&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 生成 SSH 密钥（如果没有）
ssh-keygen -t ed25519 -C &quot;ansible@control&quot;

# 复制公钥到目标主机
ssh-copy-id ubuntu@192.168.1.10
ssh-copy-id ubuntu@192.168.1.11

# 测试连接
ssh ubuntu@192.168.1.10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 创建库存文件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建目录结构
mkdir -p ~/ansible/{inventory,playbooks,roles,templates}

# 创建 inventory.ini
cat &gt; ~/ansible/inventory.ini &amp;#x3C;&amp;#x3C; &apos;EOF&apos;
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11 ansible_user=ubuntu

[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 测试连接&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Ping 所有主机
ansible all -i inventory.ini -m ping

# 输出示例：
# web1 | SUCCESS =&gt; { &quot;ping&quot;: &quot;pong&quot; }
# web2 | SUCCESS =&gt; { &quot;ping&quot;: &quot;pong&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 执行 Ad-hoc 命令&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 检查系统信息
ansible all -i inventory.ini -m setup -a &apos;filter=ansible_facts&apos;

# 执行命令
ansible webservers -i inventory.ini -m command -a &apos;uptime&apos;

# 安装软件包
ansible all -i inventory.ini -m apt -a &apos;name=htop state=present&apos;

# 复制文件
ansible webservers -i inventory.ini -m copy -a &apos;src=/local/file.txt dest=/tmp/file.txt&apos;

# 管理服务
ansible all -i inventory.ini -m service -a &apos;name=nginx state=restarted&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Playbook 深入&lt;/h2&gt;
&lt;h3&gt;1. 基本结构&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: Play 名称
  hosts: 目标主机组
  become: true  # 是否提权
  become_method: sudo  # 提权方式
  vars_files:
    - vars/main.yml  # 外部变量文件
  vars:
    var1: value1  # 内联变量
  environment:  # 环境变量
    KEY1: value1
  connection: ssh  # 连接方式
  serial: 10%  # 分批执行
  max_fail_percentage: 10  # 最大失败百分比
  
  pre_tasks:  # 前置任务
    - name: 前置任务
      debug:
        msg: &quot;准备开始&quot;
  
  roles:  # 应用角色
    - common
    - nginx
    
  tasks:  # 任务列表
    - name: 任务名称
      module: 参数
      
  post_tasks:  # 后置任务
    - name: 后置任务
      debug:
        msg: &quot;完成&quot;
  
  handlers:  # 处理器
    - name: 处理器名称
      service:
        name: nginx
        state: restarted
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 变量管理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;变量优先级（低到高）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;角色默认变量 (&lt;code&gt;defaults/main.yml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;角色变量 (&lt;code&gt;vars/main.yml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Playbook 变量 (&lt;code&gt;vars:&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;库存变量 (&lt;code&gt;group_vars/&lt;/code&gt;, &lt;code&gt;host_vars/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;额外变量 (&lt;code&gt;-e&lt;/code&gt; 参数)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;变量文件示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# group_vars/webservers.yml
http_port: 80
max_clients: 200
nginx_worker_processes: auto
nginx_worker_connections: 1024

# host_vars/web1.yml
http_port: 8080
custom_domain: &quot;web1.example.com&quot;

# vars/app_config.yml
app_name: &quot;myapp&quot;
app_version: &quot;1.0.0&quot;
app_port: 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在 Playbook 中使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 使用变量
  debug:
    msg: &quot;应用 {{ app_name }} 版本 {{ app_version }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 条件执行&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 条件安装
  apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop:
    - nginx
    - mysql-server
  when: install_nginx | bool

- name: 检查操作系统
  debug:
    msg: &quot;这是 Ubuntu 系统&quot;
  when: ansible_os_family == &quot;Debian&quot;

- name: 检查变量存在
  debug:
    msg: &quot;变量已定义&quot;
  when: my_var is defined

- name: 复杂条件
  debug:
    msg: &quot;生产环境且需要 SSL&quot;
  when: environment == &quot;production&quot; and ssl_enabled | bool
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 循环处理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 安装多个软件包
  apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop:
    - nginx
    - mysql-server
    - php

- name: 创建多个用户
  user:
    name: &quot;{{ item.name }}&quot;
    groups: &quot;{{ item.groups }}&quot;
    shell: /bin/bash
  loop:
    - { name: &apos;user1&apos;, groups: &apos;developers&apos; }
    - { name: &apos;user2&apos;, groups: &apos;admins&apos; }

- name: 从文件读取列表
  debug:
    msg: &quot;{{ item }}&quot;
  loop: &quot;{{ lookup(&apos;file&apos;, &apos;packages.txt&apos;).split(&apos;\n&apos;) }}&quot;

- name: 嵌套循环
  debug:
    msg: &quot;{{ item.key }} = {{ item.value }}&quot;
  loop: &quot;{{ my_dict | dict2items }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 错误处理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 可能失败的任务
  command: /usr/bin/risky-command
  ignore_errors: yes  # 忽略错误继续执行

- name: 带重试的任务
  command: /usr/bin/flaky-command
  retries: 5
  delay: 10
  until: last_rc == 0

- name: 失败时执行
  block:
    - name: 任务 1
      command: /usr/bin/task1
    - name: 任务 2
      command: /usr/bin/task2
  rescue:
    - name: 错误处理
      debug:
        msg: &quot;任务失败，执行恢复操作&quot;
  always:
    - name: 始终执行
      debug:
        msg: &quot;无论成功失败都执行&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 模板渲染 (Jinja2)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;模板文件示例 (nginx.conf.j2)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jinja2&quot;&gt;user www-data;
worker_processes {{ nginx_worker_processes | default(&apos;auto&apos;) }};
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections | default(1024) }};
}

http {
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    {% for server in virtual_hosts %}
    server {
        listen {{ server.port | default(80) }};
        server_name {{ server.domain }};
        root {{ server.root | default(&apos;/var/www/html&apos;) }};
        
        {% if server.ssl_enabled | default(false) %}
        ssl on;
        ssl_certificate {{ server.ssl_cert }};
        ssl_key {{ server.ssl_key }};
        {% endif %}
        
        location / {
            try_files $uri $uri/ =404;
        }
    }
    {% endfor %}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Playbook 中使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 渲染配置文件
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: &apos;0644&apos;
    backup: yes  # 备份原文件
    validate: &apos;nginx -t -c %s&apos;  # 验证语法
  notify: 重启 Nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;角色 (Roles)&lt;/h2&gt;
&lt;h3&gt;1. 角色结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;roles/
└── nginx/
    ├── defaults/
    │   └── main.yml          # 默认变量（优先级最低）
    ├── vars/
    │   └── main.yml          # 角色变量
    ├── files/                # 静态文件
    │   └── custom.conf
    ├── templates/            # Jinja2 模板
    │   └── nginx.conf.j2
    ├── handlers/
    │   └── main.yml          # 处理器
    ├── tasks/
    │   ├── main.yml          # 主任务
    │   ├── install.yml       # 安装任务
    │   └── configure.yml     # 配置任务
    ├── meta/
    │   └── main.yml          # 元数据（依赖等）
    └── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 创建角色&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 使用 ansible-galaxy 创建角色结构
ansible-galaxy init nginx

# 或手动创建
mkdir -p roles/nginx/{tasks,handlers,templates,files,vars,defaults}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 角色内容示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;defaults/main.yml：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
nginx_version: &quot;1.24&quot;
nginx_state: present
nginx_enabled: true
nginx_listen_port: 80
nginx_worker_processes: &quot;auto&quot;
nginx_worker_connections: 1024
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;tasks/main.yml：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 包含安装任务
  include_tasks: install.yml

- name: 包含配置任务
  include_tasks: configure.yml

- name: 确保 Nginx 运行
  service:
    name: nginx
    state: &quot;{{ &apos;started&apos; if nginx_enabled else &apos;stopped&apos; }}&quot;
    enabled: &quot;{{ nginx_enabled | bool }}&quot;
  notify: 重启 Nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;tasks/install.yml：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 安装 Nginx (Debian)
  apt:
    name: &quot;nginx={{ nginx_version }}&quot;
    state: &quot;{{ nginx_state }}&quot;
    update_cache: yes
  when: ansible_os_family == &quot;Debian&quot;

- name: 安装 Nginx (RHEL)
  yum:
    name: &quot;nginx-{{ nginx_version }}&quot;
    state: &quot;{{ nginx_state }}&quot;
  when: ansible_os_family == &quot;RedHat&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;handlers/main.yml：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 重启 Nginx
  service:
    name: nginx
    state: restarted

- name: 重载 Nginx
  service:
    name: nginx
    state: reloaded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;meta/main.yml：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
dependencies:
  - role: common
    tags: [&apos;common&apos;]
  
  - role:
    name: firewall
    when: enable_firewall | default(false)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 使用角色&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 部署 Web 服务器
  hosts: webservers
  become: yes
  
  roles:
    - role: common
      tags: [&apos;common&apos;]
      
    - role: nginx
      nginx_listen_port: 8080  # 覆盖默认变量
      tags: [&apos;web&apos;]
      
    - role: monitoring
      when: enable_monitoring | default(false)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;高级特性&lt;/h2&gt;
&lt;h3&gt;1. 自定义模块&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Python 模块示例 (modules/custom_facts.py)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/env python
ANSIBLE_METADATA = {
    &apos;metadata_version&apos;: &apos;1.1&apos;,
    &apos;status&apos;: [&apos;preview&apos;],
    &apos;supported_by&apos;: &apos;community&apos;
}

DOCUMENTATION = &apos;&apos;&apos;
---
module: custom_facts
short_description: 收集自定义系统信息
version_added: &quot;1.0&quot;
options:
    check_disk:
        description: 是否检查磁盘空间
        required: false
        type: bool
        default: true
&apos;&apos;&apos;

EXAMPLES = &apos;&apos;&apos;
- name: 收集自定义 facts
  custom_facts:
    check_disk: yes
&apos;&apos;&apos;

RETURN = &apos;&apos;&apos;
ansible_facts:
    description: 返回自定义 facts
    type: dict
&apos;&apos;&apos;

from ansible.module_utils.basic import AnsibleModule

def main():
    module = AnsibleModule(
        argument_spec=dict(
            check_disk=dict(type=&apos;bool&apos;, default=True)
        )
    )
    
    facts = {}
    
    if module.params[&apos;check_disk&apos;]:
        import os
        disk_usage = os.statvfs(&apos;/&apos;)
        total = disk_usage.f_frsize * disk_usage.f_blocks
        free = disk_usage.f_frsize * disk_usage.f_bfree
        facts[&apos;disk_total&apos;] = total
        facts[&apos;disk_free&apos;] = free
        facts[&apos;disk_percent_used&apos;] = ((total - free) / total) * 100
    
    module.exit_json(changed=False, ansible_facts=facts)

if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用自定义模块：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 收集自定义 facts
  custom_facts:
    check_disk: yes
    
- name: 显示磁盘使用率
  debug:
    msg: &quot;磁盘使用率：{{ disk_percent_used | round(2) }}%&quot;
  when: disk_percent_used &gt; 80
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 自定义插件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Filter 插件示例 (filter_plugins/custom_filters.py)：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def to_bytes(size_mb):
    &quot;&quot;&quot;将 MB 转换为字节&quot;&quot;&quot;
    return size_mb * 1024 * 1024

def truncate_middle(text, length=50):
    &quot;&quot;&quot;截断文本中间部分&quot;&quot;&quot;
    if len(text) &amp;#x3C;= length:
        return text
    half = length // 2
    return text[:half] + &quot;...&quot; + text[-half:]

class FilterModule(object):
    def filters(self):
        return {
            &apos;to_bytes&apos;: to_bytes,
            &apos;truncate_middle&apos;: truncate_middle
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用自定义过滤器：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 使用自定义过滤器
  debug:
    msg: &quot;{{ 100 | to_bytes }}&quot;
    
- name: 截断长文本
  debug:
    msg: &quot;{{ long_text | truncate_middle(30) }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 动态库存&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AWS EC2 动态库存脚本示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;#!/usr/bin/env python3
import boto3
import json
import sys

def get_inventory():
    ec2 = boto3.client(&apos;ec2&apos;, region_name=&apos;us-east-1&apos;)
    
    response = ec2.describe_instances(
        Filters=[
            {&apos;Name&apos;: &apos;tag:Environment&apos;, &apos;Values&apos;: [&apos;Production&apos;]}
        ]
    )
    
    inventory = {
        &apos;webservers&apos;: {&apos;hosts&apos;: [], &apos;vars&apos;: {}},
        &apos;dbservers&apos;: {&apos;hosts&apos;: [], &apos;vars&apos;: {}},
        &apos;_meta&apos;: {&apos;hostvars&apos;: {}}
    }
    
    for reservation in response[&apos;Reservations&apos;]:
        for instance in reservation[&apos;Instances&apos;]:
            if instance[&apos;State&apos;][&apos;Name&apos;] != &apos;running&apos;:
                continue
                
            hostname = instance[&apos;Tags&apos;][0][&apos;Value&apos;]
            ip = instance[&apos;PrivateIpAddress&apos;]
            instance_type = instance[&apos;InstanceType&apos;]
            
            # 根据标签分组
            for tag in instance.get(&apos;Tags&apos;, []):
                if tag[&apos;Key&apos;] == &apos;Role&apos;:
                    if tag[&apos;Value&apos;] in inventory:
                        inventory[tag[&apos;Value&apos;]][&apos;hosts&apos;].append(hostname)
                        inventory[&apos;_meta&apos;][&apos;hostvars&apos;][hostname] = {
                            &apos;ansible_host&apos;: ip,
                            &apos;ansible_user&apos;: &apos;ubuntu&apos;,
                            &apos;instance_type&apos;: instance_type
                        }
    
    return inventory

if __name__ == &apos;__main__&apos;:
    if sys.argv[1] == &apos;--list&apos;:
        print(json.dumps(get_inventory()))
    elif sys.argv[1] == &apos;--host&apos;:
        print(json.dumps({}))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 异步任务和轮询&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 执行长时间任务
  async: 3600  # 最多运行 1 小时
  poll: 0      # 不等待，立即返回
  command: /usr/bin/long-running-script.sh
  register: async_result

- name: 检查任务状态
  async_status:
    jid: &quot;{{ async_result.ansible_job_id }}&quot;
  register: job_status
  until: job_status.finished
  retries: 30
  delay: 10

- name: 显示结果
  debug:
    msg: &quot;{{ job_status.results }}&quot;
  when: job_status.finished
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 策略 (Strategies)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 使用不同策略
  hosts: all
  strategy: linear  # 默认，同步执行
  
  # 其他策略选项:
  # - free: 主机独立执行，不等待
  # - host_pinned: 类似 free，但保持主机顺序
  
  tasks:
    - name: 任务 1
      debug:
        msg: &quot;任务 1&quot;
    
    - name: 任务 2
      debug:
        msg: &quot;任务 2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 滚动更新&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 滚动更新 Web 服务
  hosts: webservers
  serial: 1  # 每次只更新一台
  
  tasks:
    - name: 从负载均衡移除
      uri:
        url: &quot;http://lb-api/remove/{{ inventory_hostname }}&quot;
        method: POST
      delegate_to: localhost
      
    - name: 停止服务
      service:
        name: myapp
        state: stopped
        
    - name: 部署新版本
      git:
        repo: https://github.com/app/repo.git
        dest: /opt/myapp
        version: &quot;{{ app_version }}&quot;
        
    - name: 启动服务
      service:
        name: myapp
        state: started
        
    - name: 健康检查
      uri:
        url: &quot;http://localhost:8080/health&quot;
        status_code: 200
      register: health_result
      retries: 5
      delay: 10
      until: health_result.status == 200
      
    - name: 添加回负载均衡
      uri:
        url: &quot;http://lb-api/add/{{ inventory_hostname }}&quot;
        method: POST
      delegate_to: localhost
      when: health_result.status == 200
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;1. 项目结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ansible-project/
├── inventory/
│   ├── production/
│   │   ├── hosts
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   └── webservers.yml
│   │   └── host_vars/
│   └── staging/
├── group_vars/
│   ├── all.yml
│   └── webservers.yml
├── host_vars/
├── playbooks/
│   ├── site.yml          # 主 Playbook
│   ├── webserver.yml
│   ├── database.yml
│   └── backup.yml
├── roles/
│   ├── common/
│   ├── nginx/
│   ├── mysql/
│   └── monitoring/
├── templates/            # 全局模板
├── files/                # 全局文件
├── scripts/              # 辅助脚本
├── ansible.cfg           # 配置文件
├── requirements.yml      # 角色依赖
└── README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Ansible 配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ansible.cfg：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[defaults]
inventory = ./inventory/production/hosts
remote_user = ubuntu
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400
roles_path = ./roles
library = ./library
filter_plugins = ./filter_plugins
stdout_callback = yaml
callback_whitelist = profile_tasks, timer

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 变量管理最佳实践&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# group_vars/all.yml - 全局变量
ansible_python_interpreter: /usr/bin/python3
timezone: Asia/Shanghai
ntp_server: ntp.example.com

# group_vars/webservers.yml - 组变量
http_port: 80
max_clients: 200

# host_vars/web1.yml - 主机变量
custom_domain: web1.example.com

# vars/secrets.yml - 敏感信息（需加密）
db_password: &quot;{{ vault_db_password }}&quot;
api_key: &quot;{{ vault_api_key }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 安全实践&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;使用 Ansible Vault：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建加密文件
ansible-vault create group_vars/secrets.yml

# 编辑加密文件
ansible-vault edit group_vars/secrets.yml

# 查看加密文件
ansible-vault view group_vars/secrets.yml

# 加密普通文件
ansible-vault encrypt secrets.yml

# 解密文件
ansible-vault decrypt secrets.yml

# 运行 Playbook（输入密码）
ansible-playbook -i inventory site.yml --ask-vault-pass

# 使用密码文件
ansible-playbook -i inventory site.yml --vault-password-file=~/.vault_pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Playbook 中使用 Vault：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 使用加密变量
  hosts: all
  vars_files:
    - group_vars/secrets.yml
    
  tasks:
    - name: 创建数据库用户
      mysql_user:
        name: app_user
        password: &quot;{{ vault_db_password }}&quot;  # 加密变量
        host: &apos;%&apos;
        priv: &apos;{{ db_name }}.*:ALL&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 错误处理和日志&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 带完整错误处理的任务
  block:
    - name: 执行关键任务
      command: /usr/bin/critical-task
      register: result
      
    - name: 验证结果
      assert:
        that:
          - result.rc == 0
          - &quot;success&quot; in result.stdout
        fail_msg: &quot;任务执行失败&quot;
        success_msg: &quot;任务成功完成&quot;
        
  rescue:
    - name: 记录错误
      debug:
        msg: &quot;任务失败：{{ result.stderr }}&quot;
        
    - name: 发送告警
      uri:
        url: &quot;{{ alert_webhook_url }}&quot;
        method: POST
        body: &quot;任务失败：{{ inventory_hostname }}&quot;
        status_code: 200
        
    - name: 回滚操作
      command: /usr/bin/rollback-script
      
  always:
    - name: 记录执行日志
      lineinfile:
        path: /var/log/ansible-execution.log
        line: &quot;{{ ansible_date_time.iso8601 }} - {{ inventory_hostname }} - {{ result.rc if result is defined else &apos;N/A&apos; }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 性能优化&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# 启用 SSH 管道和连接复用
[ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

# 使用 serial 分批执行
- name: 分批部署
  hosts: all
  serial:
    - 10%
    - 25%
    - 100%
    
# 使用 max_fail_percentage 控制失败容忍度
- name: 容错部署
  hosts: all
  max_fail_percentage: 10
  
# 使用 tags 选择性执行
ansible-playbook site.yml --tags &quot;nginx,mysql&quot;

# 使用 limit 限制主机
ansible-playbook site.yml --limit &quot;web1,web2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实战案例&lt;/h2&gt;
&lt;h3&gt;案例 1：部署 LAMP 栈&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 部署 LAMP 栈
  hosts: webservers
  become: yes
  
  roles:
    - common
    - { role: mysql, tags: [&apos;database&apos;] }
    - { role: php, tags: [&apos;php&apos;] }
    - { role: nginx, tags: [&apos;web&apos;] }
    
  tasks:
    - name: 创建应用目录
      file:
        path: /var/www/myapp
        state: directory
        owner: www-data
        group: www-data
        mode: &apos;0755&apos;
        
    - name: 部署应用代码
      git:
        repo: https://github.com/myapp/repo.git
        dest: /var/www/myapp
        version: main
        
    - name: 配置虚拟主机
      template:
        src: nginx-vhost.conf.j2
        dest: &quot;/etc/nginx/sites-available/{{ app_name }}&quot;
      notify: 重载 Nginx
      
    - name: 启用虚拟主机
      file:
        src: &quot;/etc/nginx/sites-available/{{ app_name }}&quot;
        dest: &quot;/etc/nginx/sites-enabled/{{ app_name }}&quot;
        state: link
      notify: 重载 Nginx
      
  handlers:
    - name: 重载 Nginx
      service:
        name: nginx
        state: reloaded
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;案例 2：Docker 环境部署&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 部署 Docker 环境
  hosts: docker_hosts
  become: yes
  
  tasks:
    - name: 安装依赖包
      apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - software-properties-common
        state: present
        update_cache: yes
        
    - name: 添加 Docker GPG 密钥
      apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present
        
    - name: 添加 Docker 仓库
      apt_repository:
        repo: &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable&quot;
        state: present
        
    - name: 安装 Docker
      apt:
        name: docker-ce
        state: present
        update_cache: yes
        
    - name: 启动 Docker 服务
      service:
        name: docker
        state: started
        enabled: yes
        
    - name: 安装 Docker Compose
      get_url:
        url: &quot;https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-Linux-x86_64&quot;
        dest: /usr/local/bin/docker-compose
        mode: &apos;0755&apos;
        
    - name: 添加用户到 docker 组
      user:
        name: &quot;{{ ansible_user_id }}&quot;
        groups: docker
        append: yes
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ansible 与 Kubernetes 集成&lt;/h2&gt;
&lt;h3&gt;1. 使用 Ansible 部署 K8s 集群&lt;/h3&gt;
&lt;p&gt;Ansible 是部署 Kubernetes 集群的经典工具，比 kubeadm 更灵活，适合复杂环境。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用 kubeadm 部署 K8s：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 使用 kubeadm 部署 Kubernetes 集群
  hosts: all
  become: yes
  gather_facts: yes
  
  vars:
    k8s_version: &quot;1.28&quot;
    pod_network_cidr: &quot;10.244.0.0/16&quot;
    service_network_cidr: &quot;10.96.0.0/12&quot;
    
  pre_tasks:
    - name: 禁用 Swap
      command: swapoff -a
      changed_when: yes
      
    - name: 永久禁用 Swap
      lineinfile:
        path: /etc/fstab
        regexp: &apos;^.*swap.*&apos;
        state: absent
        
    - name: 加载内核模块
      modprobe:
        name: &quot;{{ item }}&quot;
        state: present
      loop:
        - overlay
        - br_netfilter
        
    - name: 配置 sysctl 参数
      sysctl:
        name: &quot;{{ item.key }}&quot;
        value: &quot;{{ item.value }}&quot;
        state: present
        reload: yes
      loop:
        - { key: &apos;net.bridge.bridge-nf-call-iptables&apos;, value: &apos;1&apos; }
        - { key: &apos;net.bridge.bridge-nf-call-ip6tables&apos;, value: &apos;1&apos; }
        - { key: &apos;net.ipv4.ip_forward&apos;, value: &apos;1&apos; }
        
  tasks:
    - name: 安装 containerd
      block:
        - name: 安装 containerd 依赖
          apt:
            name:
              - containerd.io
              - runc
            state: present
            update_cache: yes
            
        - name: 配置 containerd
          copy:
            content: |
              [plugins.&quot;io.containerd.grpc.v1.cri&quot;]
                [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd]
                  default_runtime_name = &quot;runc&quot;
                  [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc]
                    runtime_type = &quot;io.containerd.runc.v2&quot;
                    [plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd.runtimes.runc.options]
                      SystemdCgroup = true
            dest: /etc/containerd/config.toml
            
        - name: 重启 containerd
          service:
            name: containerd
            state: restarted
            enabled: yes
            
      rescue:
        - name: 记录 containerd 安装失败
          debug:
            msg: &quot;containerd 安装失败，请检查系统兼容性&quot;
            
    - name: 安装 Kubernetes 组件
      apt:
        name: 
          - &quot;kubeadm={{ k8s_version }}.*&quot;
          - &quot;kubelet={{ k8s_version }}.*&quot;
          - &quot;kubectl={{ k8s_version }}.*&quot;
        state: present
        update_cache: yes
        
    - name: 锁定 kubelet 版本
      command: apt-mark hold kubelet
      args:
        creates: /var/lib/kubelet-held
        
  post_tasks:
    - name: 验证 kubelet 版本
      command: kubelet version
      register: kubelet_version
      changed_when: no
      
    - name: 显示 kubelet 版本
      debug:
        msg: &quot;{{ kubelet_version.stdout_lines }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;初始化 Control Plane：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 初始化 Kubernetes Control Plane
  hosts: control_plane
  become: yes
  
  vars:
    k8s_version: &quot;1.28&quot;
    apiserver_address: &quot;{{ hostvars[&apos;k8s-master-01&apos;][&apos;ansible_default_ipv4&apos;][&apos;address&apos;] }}&quot;
    pod_network_cidr: &quot;10.244.0.0/16&quot;
    
  tasks:
    - name: 初始化集群
      command: &gt;
        kubeadm init
        --apiserver-advertise-address={{ apiserver_address }}
        --pod-network-cidr={{ pod_network_cidr }}
        --cri-socket=unix:///var/run/containerd/containerd.sock
        --ignore-preflight-errors=Swap
      register: kubeadm_init
      changed_when: &quot;&apos;initialized successfully&apos; in kubeadm_init.stdout&quot;
      
    - name: 创建 .kube 目录
      file:
        path: &quot;{{ ansible_user_dir }}/.kube&quot;
        state: directory
        mode: &apos;0700&apos;
        
    - name: 复制 kubeconfig
      copy:
        src: /etc/kubernetes/admin.conf
        dest: &quot;{{ ansible_user_dir }}/.kube/config&quot;
        remote_src: yes
        mode: &apos;0600&apos;
        
    - name: 保存 join 命令
      template:
        src: kubeadm-join-command.sh.j2
        dest: /tmp/kubeadm-join-worker.sh
        mode: &apos;0755&apos;
      when: kubeadm_init is changed
      
    - name: 显示 join 命令
      debug:
        msg: &quot;请运行以下命令将 worker 节点加入集群:\n{{ kubeadm_init.stdout | regex_search(&apos;kubeadm join.*&apos;) }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;加入 Worker 节点：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 将 Worker 节点加入集群
  hosts: workers
  become: yes
  
  tasks:
    - name: 从 master 获取 join 命令
      fetch:
        src: /tmp/kubeadm-join-worker.sh
        dest: /tmp/kubeadm-join-worker.sh
        flat: yes
      delegate_to: k8s-master-01
      run_once: yes
      
    - name: 复制 join 脚本到 worker
      copy:
        src: /tmp/kubeadm-join-worker.sh
        dest: /tmp/kubeadm-join-worker.sh
        mode: &apos;0755&apos;
        
    - name: 执行 join 命令
      command: /tmp/kubeadm-join-worker.sh
      async: 300
      poll: 10
      register: join_result
      
    - name: 验证节点加入
      command: kubectl get nodes -o json
      delegate_to: k8s-master-01
      run_once: yes
      register: nodes_status
      retries: 5
      delay: 30
      until: nodes_status.stdout | contains(inventory_hostname)
      
    - name: 显示节点状态
      debug:
        msg: &quot;{{ inventory_hostname }} 已成功加入集群&quot;
      when: nodes_status.stdout is defined and inventory_hostname in nodes_status.stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 安装 CNI 网络插件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 安装 Calico 网络插件
  hosts: control_plane
  become: yes
  vars:
    calico_version: &quot;v3.26.1&quot;
    
  tasks:
    - name: 下载 Calico 配置
      get_url:
        url: &quot;https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/tigera-operator.yaml&quot;
        dest: /tmp/tigera-operator.yaml
        
    - name: 应用 Calico 配置
      command: kubectl apply -f /tmp/tigera-operator.yaml
      register: calico_result
      changed_when: calico_result.rc == 0
      
    - name: 等待 Calico 就绪
      command: kubectl get pods -n kube-system -l k8s-app=calico-node -o jsonpath=&apos;{.items[*].status.phase}&apos;
      register: calico_status
      retries: 30
      delay: 10
      until: &apos;&quot;Running&quot;&apos; in calico_status.stdout
      
    - name: 显示 Calico 状态
      debug:
        msg: &quot;Calico 网络插件已就绪&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 使用 k8s 模块管理资源&lt;/h3&gt;
&lt;p&gt;Ansible 提供了丰富的 Kubernetes 模块，可以直接管理 K8s 资源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安装 kubernetes-python：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 安装 Python Kubernetes 库
  pip:
    name:
      - kubernetes
      - openshift
    state: present
  delegate_to: localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;管理 Deployment：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 部署应用
  hosts: control_plane
  become: no
  
  tasks:
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: myapp
            
    - name: 部署应用
      k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: myapp
            namespace: myapp
            labels:
              app: myapp
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: myapp
            template:
              metadata:
                labels:
                  app: myapp
              spec:
                containers:
                  - name: myapp
                    image: nginx:{{ nginx_version | default(&apos;1.24&apos;) }}
                    ports:
                      - containerPort: 80
                    resources:
                      requests:
                        memory: &quot;64Mi&quot;
                        cpu: &quot;250m&quot;
                      limits:
                        memory: &quot;128Mi&quot;
                        cpu: &quot;500m&quot;
                    livenessProbe:
                      httpGet:
                        path: /
                        port: 80
                      initialDelaySeconds: 30
                      periodSeconds: 10
                    readinessProbe:
                      httpGet:
                        path: /
                        port: 80
                      initialDelaySeconds: 5
                      periodSeconds: 5
                        
    - name: 创建 Service
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Service
          metadata:
            name: myapp-service
            namespace: myapp
          spec:
            selector:
              app: myapp
            ports:
              - port: 80
                targetPort: 80
                protocol: TCP
            type: ClusterIP
            
    - name: 验证部署
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: myapp
        label_selectors:
          - app=myapp
      register: pods
      
    - name: 显示 Pod 状态
      debug:
        msg: &quot;部署了 {{ pods.resources | length }} 个 Pod&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;滚动更新：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 滚动更新应用
  hosts: control_plane
  
  vars:
    app_name: myapp
    namespace: myapp
    new_version: &quot;1.25&quot;
    
  tasks:
    - name: 更新 Deployment 镜像
      k8s:
        state: patched
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: &quot;{{ app_name }}&quot;
            namespace: &quot;{{ namespace }}&quot;
          spec:
            template:
              spec:
                containers:
                  - name: &quot;{{ app_name }}&quot;
                    image: nginx:{{ new_version }}
        merge_type: strategic
        
    - name: 等待滚动更新完成
      k8s_info:
        api_version: apps/v1
        kind: Deployment
        namespace: &quot;{{ namespace }}&quot;
        name: &quot;{{ app_name }}&quot;
      register: deployment
      retries: 60
      delay: 10
      until: 
        - deployment.resources[0].status.readyReplicas == deployment.resources[0].spec.replicas
        
    - name: 显示更新状态
      debug:
        msg: |
          更新完成!
          期望副本数：{{ deployment.resources[0].spec.replicas }}
          就绪副本数：{{ deployment.resources[0].status.readyReplicas }}
          可用副本数：{{ deployment.resources[0].status.availableReplicas }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;管理 ConfigMap 和 Secret：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 管理配置和密钥
  hosts: control_plane
  
  tasks:
    - name: 创建 ConfigMap
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-config
            namespace: myapp
          data:
            APP_ENV: production
            LOG_LEVEL: info
            MAX_CONNECTIONS: &quot;100&quot;
            
    - name: 从文件创建 ConfigMap
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: app-config-files
            namespace: myapp
          data:
            app.conf: &quot;{{ lookup(&apos;file&apos;, &apos;files/app.conf&apos;) }}&quot;
            nginx.conf: &quot;{{ lookup(&apos;template&apos;, &apos;templates/nginx.conf.j2&apos;) }}&quot;
            
    - name: 创建 Secret
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Secret
          metadata:
            name: app-secret
            namespace: myapp
          type: Opaque
          stringData:
            DB_PASSWORD: &quot;{{ vault_db_password }}&quot;
            API_KEY: &quot;{{ vault_api_key }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Helm 集成&lt;/h3&gt;
&lt;p&gt;使用 Ansible 管理 Helm Charts。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安装 Helm：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 安装 Helm
  hosts: control_plane
  
  tasks:
    - name: 下载 Helm
      get_url:
        url: &quot;https://get.helm.sh/helm-v{{ helm_version | default(&apos;3.12.0&apos;) }}-linux-amd64.tar.gz&quot;
        dest: /tmp/helm.tar.gz
        
    - name: 解压 Helm
      unarchive:
        src: /tmp/helm.tar.gz
        dest: /tmp
        remote_src: yes
        
    - name: 安装 Helm
      copy:
        src: /tmp/linux-amd64/helm
        dest: /usr/local/bin/helm
        mode: &apos;0755&apos;
        
    - name: 验证 Helm 安装
      command: helm version --short
      register: helm_version_output
      changed_when: no
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用 Helm 部署应用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 使用 Helm 部署应用
  hosts: control_plane
  
  vars:
    releases:
      - name: prometheus
        chart: prometheus
        repo: https://prometheus-community.github.io/helm-charts
        namespace: monitoring
        version: &quot;25.18.0&quot;
        values:
          server:
            persistentVolume:
              enabled: false
          alertmanager:
            persistentVolume:
              enabled: false
              
      - name: grafana
        chart: grafana
        repo: https://grafana.github.io/helm-charts
        namespace: monitoring
        version: &quot;7.0.0&quot;
        values:
          adminPassword: &quot;{{ vault_grafana_password }}&quot;
          persistence:
            enabled: false
            size: 10Gi
            
  tasks:
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: &quot;{{ item.namespace }}&quot;
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;{{ item.namespace }}&quot;
        
    - name: 添加 Helm 仓库
      command: &gt;
        helm repo add {{ item.name }}
        {{ item.repo }}
        --force-update
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;添加仓库 {{ item.name }}&quot;
      register: helm_repo_add
      changed_when: &quot;&apos;has been added&apos; in helm_repo_add.stdout&quot;
      
    - name: 部署 Helm Chart
      helm:
        name: &quot;{{ item.name }}&quot;
        chart_name: &quot;{{ item.chart }}&quot;
        release_namespace: &quot;{{ item.namespace }}&quot;
        repo_url: &quot;{{ item.repo }}&quot;
        version: &quot;{{ item.version }}&quot;
        values: &quot;{{ item.values }}&quot;
        state: present
      loop: &quot;{{ releases }}&quot;
      loop_control:
        label: &quot;部署 {{ item.name }}&quot;
        
    - name: 验证部署
      command: helm list -n {{ item.namespace }}
      loop: &quot;{{ releases }}&quot;
      register: helm_list
      changed_when: no
      
    - name: 显示部署状态
      debug:
        msg: &quot;{{ item.name }} 部署成功&quot;
      loop: &quot;{{ releases }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Helm 升级和回滚：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: Helm 升级和回滚管理
  hosts: control_plane
  
  vars:
    release_name: prometheus
    namespace: monitoring
    new_version: &quot;25.19.0&quot;
    
  tasks:
    - name: 升级 Helm Release
      helm:
        name: &quot;{{ release_name }}&quot;
        chart_name: prometheus
        release_namespace: &quot;{{ namespace }}&quot;
        repo_url: https://prometheus-community.github.io/helm-charts
        version: &quot;{{ new_version }}&quot;
        state: present
        update_deps: yes
        wait: yes
        timeout: 300
      register: helm_upgrade
      
    - name: 显示升级历史
      command: helm history {{ release_name }} -n {{ namespace }}
      register: helm_history
      changed_when: no
      
    - name: 回滚到上一版本（如果升级失败）
      helm:
        name: &quot;{{ release_name }}&quot;
        release_namespace: &quot;{{ namespace }}&quot;
        state: present
        revision: &quot;{{ helm_history.stdout_lines[-2].split()[0] }}&quot;
      when: helm_upgrade.failed | default(false)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Ingress 管理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 配置 Ingress
  hosts: control_plane
  
  vars:
    ingress_host: &quot;myapp.example.com&quot;
    tls_enabled: true
    tls_secret: &quot;myapp-tls&quot;
    
  tasks:
    - name: 安装 Nginx Ingress Controller
      helm:
        name: nginx-ingress
        chart_name: ingress-nginx
        repo_url: https://kubernetes.github.io/ingress-nginx
        release_namespace: ingress-nginx
        create_namespace: yes
        values:
          controller:
            service:
              type: LoadBalancer
        state: present
        
    - name: 等待 Ingress Controller 就绪
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: ingress-nginx
        label_selectors:
          - app.kubernetes.io/component=controller
      register: ingress_pods
      retries: 30
      delay: 10
      until: 
        - ingress_pods.resources | length &gt; 0
        - ingress_pods.resources[0].status.phase == &apos;Running&apos;
        
    - name: 创建 Ingress
      k8s:
        state: present
        definition:
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: myapp-ingress
            namespace: myapp
            annotations:
              nginx.ingress.kubernetes.io/rewrite-target: /
              nginx.ingress.kubernetes.io/ssl-redirect: &quot;{{ &apos;true&apos; if tls_enabled else &apos;false&apos; }}&quot;
          spec:
            {% if tls_enabled %}
            tls:
              - hosts:
                  - &quot;{{ ingress_host }}&quot;
                secretName: &quot;{{ tls_secret }}&quot;
            {% endif %}
            rules:
              - host: &quot;{{ ingress_host }}&quot;
                http:
                  paths:
                    - path: /
                      pathType: Prefix
                      backend:
                        service:
                          name: myapp-service
                          port:
                            number: 80
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 完整 K8s 运维 Playbook&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 完整的 Kubernetes 应用部署和运维
  hosts: control_plane
  become: no
  
  vars_prompt:
    - name: environment
      prompt: &quot;选择环境 (dev/staging/production)&quot;
      private: no
      
  vars_files:
    - vars/k8s_config.yml
    - vars/secrets.yml
    
  tasks:
    # 1. 环境检查
    - name: 检查 kubectl 连接
      command: kubectl cluster-info
      register: cluster_info
      changed_when: no
      
    - name: 显示集群信息
      debug:
        msg: &quot;集群已就绪&quot;
      when: cluster_info.rc == 0
      
    # 2. 准备环境
    - name: 创建命名空间
      k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: &quot;{{ app_namespace }}&quot;
            labels:
              environment: &quot;{{ environment }}&quot;
              
    # 3. 部署基础设施
    - name: 部署 Prometheus 监控
      include_tasks: tasks/deploy-prometheus.yml
      when: enable_monitoring | default(false) | bool
      
    # 4. 部署应用
    - name: 部署主应用
      k8s:
        state: present
        src: &quot;files/{{ app_name }}-deployment.yaml&quot;
        
    - name: 部署服务
      k8s:
        state: present
        src: &quot;files/{{ app_name }}-service.yaml&quot;
        
    # 5. 配置 Ingress
    - name: 配置入口
      include_tasks: tasks/configure-ingress.yml
      when: environment == &apos;production&apos;
      
    # 6. 健康检查
    - name: 等待应用就绪
      k8s_info:
        api_version: v1
        kind: Pod
        namespace: &quot;{{ app_namespace }}&quot;
        label_selectors:
          - app={{ app_name }}
      register: app_pods
      retries: 60
      delay: 10
      until: 
        - app_pods.resources | length &gt;= app_replicas | default(1)
        - app_pods.resources[0].status.phase == &apos;Running&apos;
        
    # 7. 验证部署
    - name: 验证服务访问
      uri:
        url: &quot;http://{{ ingress_host }}/{{ app_name }}/health&quot;
        status_code: 200
      register: health_check
      retries: 10
      delay: 5
      until: health_check.status == 200
      when: environment != &apos;dev&apos;
      
    # 8. 清理旧版本
    - name: 清理旧版本资源
      k8s:
        state: absent
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: &quot;{{ app_name }}&quot;
            namespace: &quot;{{ app_namespace }}&quot;
            labels:
              version: &quot;{{ old_version }}&quot;
      when: cleanup_old_versions | default(false) | bool
      
  handlers:
    - name: 发送部署成功通知
      uri:
        url: &quot;{{ webhook_url }}&quot;
        method: POST
        body_format: json
        body:
          text: &quot;✅ 部署成功 - {{ app_name }} ({{ environment }})&quot;
          username: Ansible
          icon_emoji: &quot;:rocket:&quot;
      listen: &quot;部署完成&quot;
      
    - name: 发送部署失败通知
      uri:
        url: &quot;{{ webhook_url }}&quot;
        method: POST
        body_format: json
        body:
          text: &quot;❌ 部署失败 - {{ app_name }} ({{ environment }})&quot;
          username: Ansible
          icon_emoji: &quot;:warning:&quot;
      listen: &quot;部署失败&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. 使用 Ansible 管理多集群&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: 管理多个 Kubernetes 集群
  hosts: all
  
  vars:
    clusters:
      production:
        kubeconfig: &quot;~/.kube/config-production&quot;
        namespace: production
      staging:
        kubeconfig: &quot;~/.kube/config-staging&quot;
        namespace: staging
      development:
        kubeconfig: &quot;~/.kube/config-dev&quot;
        namespace: development
        
  tasks:
    - name: 遍历所有集群
      block:
        - name: 设置 kubeconfig
          set_fact:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
          
        - name: 检查集群状态
          command: kubectl cluster-info
          environment:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
          register: cluster_status
          changed_when: no
          
        - name: 部署应用到集群
          k8s:
            state: present
            definition:
              apiVersion: apps/v1
              kind: Deployment
              metadata:
                name: myapp
                namespace: &quot;{{ item.value.namespace }}&quot;
              spec:
                replicas: &quot;{{ item.value.replicas | default(1) }}&quot;
                selector:
                  matchLabels:
                    app: myapp
                template:
                  metadata:
                    labels:
                      app: myapp
                  spec:
                    containers:
                      - name: myapp
                        image: myapp:{{ app_version }}
          environment:
            KUBECONFIG: &quot;{{ item.value.kubeconfig }}&quot;
            
      loop: &quot;{{ clusters | dict2items }}&quot;
      loop_control:
        label: &quot;{{ item.key }}&quot;
        
      rescue:
        - name: 记录集群部署失败
          debug:
            msg: &quot;集群 {{ item.key }} 部署失败&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8. K8s 资源监控和告警&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: K8s 资源监控和清理
  hosts: control_plane
  
  tasks:
    - name: 收集资源使用率
      command: kubectl top nodes
      register: node_resources
      changed_when: no
      
    - name: 检查资源使用率
      set_fact:
        high_usage_nodes: &gt;-
          {{ node_resources.stdout 
          | regex_findall(&apos;(\S+)\s+\d+\s+(\d+)/\d+&apos;) 
          | select(&apos;match&apos;, &apos;80|90|100&apos;) 
          | list }}
          
    - name: 告警：节点资源使用率高
      debug:
        msg: &quot;警告：节点资源使用率超过阈值&quot;
      when: high_usage_nodes | length &gt; 0
      changed_when: no
      
    - name: 清理失败 Pod
      command: &gt;
        kubectl delete pods --field-selector=status.phase=Failed
        -n {{ item }}
      loop:
        - default
        - kube-system
        - monitoring
      ignore_errors: yes
      register: cleanup_result
      
    - name: 清理完成 Pod
      command: &gt;
        kubectl delete pods --field-selector=status.phase=Succeeded
        -n {{ item }}
      loop:
        - batch
        - jobs
      ignore_errors: yes
      
    - name: 显示清理结果
      debug:
        msg: &quot;已清理 {{ cleanup_result | length }} 个命名空间中的异常 Pod&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9. 备份和恢复&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
- name: Kubernetes 资源备份和恢复
  hosts: control_plane
  
  vars:
    backup_dir: &quot;/backup/k8s&quot;
    backup_date: &quot;{{ ansible_date_time.iso8601_basic_short }}&quot;
    
  tasks:
    - name: 创建备份目录
      file:
        path: &quot;{{ backup_dir }}/{{ backup_date }}&quot;
        state: directory
        mode: &apos;0755&apos;
        
    - name: 备份所有命名空间
      command: &gt;
        kubectl get all --all-namespaces -o yaml
        &gt; {{ backup_dir }}/{{ backup_date }}/all-resources.yaml
      register: backup_all
      
    - name: 备份 ConfigMaps 和 Secrets
      command: &gt;
        kubectl get configmaps,secrets --all-namespaces -o yaml
        &gt; {{ backup_dir }}/{{ backup_date }}/configmaps-secrets.yaml
      register: backup_configs
      
    - name: 压缩备份文件
      command: &gt;
        tar -czf {{ backup_dir }}/backup-{{ backup_date }}.tar.gz
        -C {{ backup_dir }} {{ backup_date }}
        
    - name: 清理旧备份（保留 7 天）
      find:
        paths: &quot;{{ backup_dir }}&quot;
        patterns: &quot;*.tar.gz&quot;
        age: &quot;7d&quot;
      register: old_backups
      
    - name: 删除旧备份
      file:
        path: &quot;{{ item.path }}&quot;
        state: absent
      loop: &quot;{{ old_backups.files }}&quot;
      when: old_backups.files | length &gt; 0
      
    - name: 显示备份状态
      debug:
        msg: &quot;备份完成：{{ backup_dir }}/backup-{{ backup_date }}.tar.gz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调试和排错&lt;/h2&gt;
&lt;h3&gt;1. 调试技巧&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 检查模式（不实际执行）
ansible-playbook site.yml --check

# 差异模式（显示文件变化）
ansible-playbook site.yml --diff

# 提高详细程度
ansible-playbook site.yml -v    # 一般信息
ansible-playbook site.yml -vv   # 更多细节
ansible-playbook site.yml -vvv  # 详细输出
ansible-playbook site.yml -vvvv # 调试级别

# 在特定任务后中断
ansible-playbook site.yml --step

# 执行到特定标签
ansible-playbook site.yml --start-at-task=&quot;任务名称&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 常用调试任务&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: 调试变量
  debug:
    var: my_variable
    
- name: 显示主机 facts
  setup: {}
  register: facts
  
- name: 显示 facts
  debug:
    var: facts.ansible_facts.ansible_distribution

- name: 失败并显示消息
  fail:
    msg: &quot;调试中断点：{{ some_variable }}&quot;
    
- name: 等待用户输入
  pause:
    prompt: &quot;按回车继续...&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 性能分析&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 启用性能回调插件
[defaults]
stdout_callback = profile_tasks
callback_whitelist = profile_tasks

# 运行 Playbook 查看每个任务耗时
ansible-playbook site.yml

# 输出示例：
# PLAY RECAP *****************************************************************
# ...
# 
# TASK PROFILE (time in seconds):
# - 安装 Nginx: 12.345
# - 配置 Nginx: 2.123
# - 启动服务：1.456
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;h3&gt;核心要点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;无代理架构&lt;/strong&gt; — 通过 SSH 连接，简化部署&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幂等性&lt;/strong&gt; — 确保多次执行结果一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YAML 语法&lt;/strong&gt; — 直观易读的 Playbook&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;角色管理&lt;/strong&gt; — 模块化、可复用的代码组织&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变量优先级&lt;/strong&gt; — 灵活配置管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误处理&lt;/strong&gt; — block/rescue/always 机制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全实践&lt;/strong&gt; — Ansible Vault 加密敏感信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;K8s 集成&lt;/strong&gt; — 原生支持 Kubernetes 资源管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helm 管理&lt;/strong&gt; — 自动化 Helm Charts 部署和升级&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多集群管理&lt;/strong&gt; — 统一管理多个 K8s 集群&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Ansible + Kubernetes 最佳实践&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 使用 &lt;code&gt;k8s&lt;/code&gt; 模块直接管理资源，避免 YAML 文件管理&lt;/li&gt;
&lt;li&gt;✅ 使用 Helm 管理复杂应用（如 Prometheus、Grafana）&lt;/li&gt;
&lt;li&gt;✅ 实现滚动更新和健康检查&lt;/li&gt;
&lt;li&gt;✅ 配置 Ingress 和 TLS 自动化&lt;/li&gt;
&lt;li&gt;✅ 定期备份 K8s 资源&lt;/li&gt;
&lt;li&gt;✅ 监控资源使用率和异常 Pod&lt;/li&gt;
&lt;li&gt;✅ 使用多集群管理实现环境隔离&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;学习路径&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;✅ 掌握基础概念和 ad-hoc 命令&lt;/li&gt;
&lt;li&gt;✅ 编写简单 Playbook&lt;/li&gt;
&lt;li&gt;✅ 学习变量、模板、条件执行&lt;/li&gt;
&lt;li&gt;✅ 创建和使用角色&lt;/li&gt;
&lt;li&gt;✅ 掌握高级特性（异步、滚动更新等）&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;学习 K8s 集成&lt;/strong&gt; — 部署集群、管理资源&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;学习 Helm 集成&lt;/strong&gt; — 自动化应用部署&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;实践多集群管理&lt;/strong&gt; — 生产环境运维&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;下一步&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 学习 Ansible Galaxy 使用&lt;/li&gt;
&lt;li&gt;[ ] 掌握动态库存编写&lt;/li&gt;
&lt;li&gt;[ ] 自定义模块和插件开发&lt;/li&gt;
&lt;li&gt;[ ] 集成 CI/CD 流水线（GitLab CI + Ansible）&lt;/li&gt;
&lt;li&gt;[ ] 学习 Ansible Tower/Automation Controller&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;深入学习 K8s Operator 开发&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;实践 GitOps 工作流（ArgoCD + Ansible）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;[ ] 参与开源角色贡献&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;更新时间&lt;/strong&gt;: 2026-05-14&lt;br&gt;
&lt;strong&gt;阅读时间&lt;/strong&gt;: 45 分钟&lt;br&gt;
&lt;strong&gt;适用场景&lt;/strong&gt;: 自动化运维、配置管理、批量部署、Kubernetes 运维、基础设施即代码&lt;/p&gt;
&lt;h3&gt;参考资源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/&quot;&gt;Ansible 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://galaxy.ansible.com/&quot;&gt;Ansible Galaxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/user_guide/best_practices.html&quot;&gt;Ansible 最佳实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ansible.com.cn/&quot;&gt;Ansible 中文社区&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/kubernetes/core/k8s_module.html&quot;&gt;Ansible Kubernetes 模块&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ansible.com/ansible/latest/collections/community/general/helm_module.html&quot;&gt;Ansible Helm 模块&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/&quot;&gt;Kubernetes 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Ansible</category><category>DevOps</category></item><item><title>从零搭建 Kubernetes 集群 (ARM64 Linux 离线版)</title><link>https://heyedwardchen.com/blog/k8s-cluster-setup-arm64/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/k8s-cluster-setup-arm64/</guid><description>在 ARM64 架构的离线 Linux 服务器上从零搭建生产级 Kubernetes 集群，使用本地源完成 kubeadm 高可用部署。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;从零搭建 Kubernetes 集群 (ARM64 Linux 离线版)&lt;/h1&gt;
&lt;p&gt;本文将详细介绍如何在&lt;strong&gt;无外网访问&lt;/strong&gt;的 ARM64 架构 Linux 服务器上从零搭建 Kubernetes 集群，包括离线资源准备、本地源配置、节点配置、集群初始化和高可用部署等完整流程。&lt;/p&gt;
&lt;h2&gt;为什么选择离线部署？&lt;/h2&gt;
&lt;p&gt;离线部署在内网环境、安全要求高的场景中非常重要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt; — 避免外部网络攻击&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合规性&lt;/strong&gt; — 满足等保、金融等行业要求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;稳定性&lt;/strong&gt; — 不受外网波动影响&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可控性&lt;/strong&gt; — 完全掌控软件版本和来源&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;h3&gt;硬件要求&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;最小配置：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;控制节点 (Control Plane)&lt;/strong&gt;：2 核 CPU, 4GB RAM, 50GB 磁盘&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作节点 (Worker Node)&lt;/strong&gt;：2 核 CPU, 4GB RAM, 50GB 磁盘&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳板机/资源机&lt;/strong&gt;（可选）：4 核 CPU, 8GB RAM, 200GB 磁盘（用于下载离线包）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;推荐配置（生产环境）：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;控制节点&lt;/strong&gt;：4 核 CPU, 8GB RAM, 100GB 磁盘&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作节点&lt;/strong&gt;：4 核 CPU, 16GB RAM, 200GB 磁盘&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;软件要求&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;操作系统&lt;/strong&gt;：Ubuntu 22.04 LTS / Debian 11+ / Rocky Linux 9+ (ARM64)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核版本&lt;/strong&gt;：5.15+&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes 版本&lt;/strong&gt;：v1.28+&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器运行时&lt;/strong&gt;：containerd 1.6+&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;网络规划&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;节点角色        IP 地址           主机名
─────────────────────────────────────────
Control Plane   192.168.1.100     k8s-master-01
Worker Node 1   192.168.1.101     k8s-worker-01
Worker Node 2   192.168.1.102     k8s-worker-02
跳板机/资源机   192.168.1.200     k8s-jump-host  (可上网)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第一步：离线资源准备（在有网的机器上）&lt;/h2&gt;
&lt;h3&gt;1.1 准备环境&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在可以上网的机器上执行
mkdir -p /k8s-offline/{packages,images,certs}
cd /k8s-offline
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 下载 Kubernetes 组件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 下载 kubeadm, kubelet, kubectl (ARM64)
VERSION=1.28.0
ARCH=arm64

# 方法 1：从 k8s 官方下载
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubeadm/kubeadm_${VERSION}-00_arm64.deb
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubelet/kubelet_${VERSION}-00_arm64.deb
wget https://pkgs.k8s.io/core:/stable:/v${VERSION}/deb/pool/main/k/kubectl/kubectl_${VERSION}-00_arm64.deb

# 方法 2：使用 apt download（推荐）
sudo apt update
sudo apt download kubeadm=${VERSION}-00 kubelet=${VERSION}-00 kubectl=${VERSION}-00

# 下载依赖包
sudo apt download -d --no-install kubeadm=${VERSION}-00 kubelet=${VERSION}-00 kubectl=${VERSION}-00

# 移动到离线目录
sudo cp /var/cache/apt/archives/*.deb ./packages/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 下载容器运行时&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 下载 containerd 及其依赖
sudo apt download -d --no-install containerd.io

# 或者手动下载
wget https://github.com/containerd/containerd/releases/download/v1.6.22/containerd-1.6.22-linux-arm64.tar.gz

# 移动所有 deb 包
sudo cp /var/cache/apt/archives/*.deb ./packages/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4 下载 CNI 网络插件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 下载 Calico YAML 文件（离线部署需要）
wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml -O calico-operator.yaml
wget https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml -O calico-config.yaml

# 或者下载 Flannel
wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml -O flannel.yaml

# 下载所需镜像（需要预先 pull）
# 注意：这些镜像需要在有 Docker 的环境中 pull
docker pull calico/operator:v1.30.3
docker pull calico/node:v3.26.1
docker pull calico/cni:v3.26.1
docker pull calico/kube-controllers:v3.26.1
docker pull calico/pod2daemon-flexvol:v3.26.1

# 导出镜像
docker save calico/operator:v1.30.3 -o calico-operator.tar
docker save calico/node:v3.26.1 -o calico-node.tar
docker save calico/cni:v3.26.1 -o calico-cni.tar
docker save calico/kube-controllers:v3.26.1 -o calico-kube-controllers.tar
docker save calico/pod2daemon-flexvol:v3.26.1 -o calico-pod2daemon-flexvol.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.5 打包离线资源&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建离线安装包
cd /k8s-offline
tar -czvf k8s-offline-packages.tar.gz \
    packages/ \
    calico-operator.yaml \
    calico-config.yaml \
    *.tar

# 传输到离线服务器
# 方法 1：使用 USB 盘
cp k8s-offline-packages.tar.gz /media/usb/

# 方法 2：使用 scp（如果跳板机能访问内网）
scp k8s-offline-packages.tar.gz root@192.168.1.100:/root/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第二步：离线环境配置（在所有节点上）&lt;/h2&gt;
&lt;h3&gt;2.1 系统初始化&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 更新系统（如果有本地源）
sudo apt update &amp;#x26;&amp;#x26; sudo apt upgrade -y

# 安装必要工具（离线）
cd /root/k8s-offline/packages
sudo dpkg -i apt-transport-https*.deb ca-certificates*.deb curl*.deb gnupg*.deb lsb-release*.deb

# 禁用交换分区（Kubernetes 要求）
sudo swapoff -a
sudo sed -i &apos;/ swap / s/^\(.*\)$/#\1/g&apos; /etc/fstab

# 配置主机名和 hosts
sudo hostnamectl set-hostname k8s-master-01  # 根据实际节点修改
echo &quot;192.168.1.100 k8s-master-01&quot; | sudo tee -a /etc/hosts
echo &quot;192.168.1.101 k8s-worker-01&quot; | sudo tee -a /etc/hosts
echo &quot;192.168.1.102 k8s-worker-02&quot; | sudo tee -a /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 配置内核参数&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 加载必要的内核模块
cat &amp;#x3C;&amp;#x3C;EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 设置所需的 sysctl 参数
cat &amp;#x3C;&amp;#x3C;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 应用配置
sudo sysctl --system
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 安装容器运行时（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装 containerd 依赖
cd /root/k8s-offline/packages
sudo dpkg -i libseccomp2*.deb runc*.deb containerd.io*.deb

# 或者使用 tar 包安装
tar -xzf containerd-1.6.22-linux-arm64.tar.gz -C /usr/local
sudo ln -s /usr/local/bin/containerd /usr/local/bin/
sudo ln -s /usr/local/bin/ctr /usr/local/bin/
sudo ln -s /usr/local/bin/containerd-shim-runc-v2 /usr/local/bin/

# 配置 containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# 修改 sysctl 配置以支持 overlay
sudo sed -i &apos;s/SystemdCgroup = false/SystemdCgroup = true/g&apos; /etc/containerd/config.toml

# 创建 systemd 服务
cat &amp;#x3C;&amp;#x3C;EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
EOF

# 启动 containerd
sudo systemctl daemon-reload
sudo systemctl enable containerd
sudo systemctl restart containerd
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 安装 Kubernetes 组件（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装 kubeadm, kubelet, kubectl
cd /root/k8s-offline/packages
sudo dpkg -i kubelet*.deb kubeadm*.deb kubectl*.deb

# 如果有依赖问题，安装所有包
sudo dpkg -i *.deb

# 验证安装
kubeadm version
kubelet version
kubectl version --client

# 锁定版本（防止误升级）
sudo apt-mark hold kubeadm kubelet kubectl
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第三步：初始化 Control Plane（离线）&lt;/h2&gt;
&lt;h3&gt;3.1 在控制节点上执行&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 初始化集群（使用本地 CRI socket）
sudo kubeadm init \
    --apiserver-advertise-address=192.168.1.100 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket=unix:///var/run/containerd/containerd.sock \
    --ignore-preflight-errors=Swap,CRI

# 保存 join 命令（后续需要在 worker 节点执行）
# 输出示例：
# kubeadm join 192.168.1.100:6443 --token &amp;#x3C;token&gt; \
#     --discovery-token-ca-cert-hash sha256:&amp;#x3C;hash&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 配置 kubectl&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建 .kube 目录
mkdir -p $HOME/.kube

# 复制配置文件
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 验证集群状态
kubectl get nodes
# 输出：NAME            STATUS     ROLES           AGE   VERSION
#       k8s-master-01   NotReady   control-plane   2m    v1.28.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 导入 CNI 镜像（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 导入 Calico 镜像
cd /root/k8s-offline
sudo ctr -n k8s.io images import calico-operator.tar
sudo ctr -n k8s.io images import calico-node.tar
sudo ctr -n k8s.io images import calico-cni.tar
sudo ctr -n k8s.io images import calico-kube-controllers.tar
sudo ctr -n k8s.io images import calico-pod2daemon-flexvol.tar

# 验证镜像
sudo ctr -n k8s.io images ls | grep calico
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 安装 CNI 网络插件（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 应用 Calico 配置（使用本地文件）
kubectl apply -f /root/k8s-offline/calico-operator.yaml
kubectl apply -f /root/k8s-offline/calico-config.yaml

# 或者使用 Flannel
# kubectl apply -f /root/k8s-offline/flannel.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 验证节点状态&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 等待节点就绪（约 2-3 分钟）
watch kubectl get nodes

# 输出：
# NAME            STATUS   ROLES           AGE   VERSION
# k8s-master-01   Ready    control-plane   5m    v1.28.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第四步：加入 Worker 节点（离线）&lt;/h2&gt;
&lt;h3&gt;4.1 在 Worker 节点上安装组件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在所有 worker 节点上执行相同的安装步骤
# 参考第二步：离线环境配置

# 安装 containerd
cd /root/k8s-offline/packages
sudo dpkg -i containerd.io*.deb
sudo systemctl enable containerd
sudo systemctl restart containerd

# 安装 kubernetes 组件
sudo dpkg -i kubelet*.deb kubeadm*.deb kubectl*.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 导入必要镜像&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在 worker 节点上导入 Calico 镜像
cd /root/k8s-offline
sudo ctr -n k8s.io images import calico-node.tar
sudo ctr -n k8s.io images import calico-cni.tar
sudo ctr -n k8s.io images import calico-pod2daemon-flexvol.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 加入集群&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在 k8s-worker-01 和 k8s-worker-02 上分别执行
sudo kubeadm join 192.168.1.100:6443 \
    --token &amp;#x3C;token&gt; \
    --discovery-token-ca-cert-hash sha256:&amp;#x3C;hash&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 验证集群&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在控制节点上查看所有节点
kubectl get nodes -o wide

# 输出：
# NAME            STATUS   ROLES           AGE   VERSION   INTERNAL-IP    OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
# k8s-master-01   Ready    control-plane   10m   v1.28.0   192.168.1.100 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
# k8s-worker-01   Ready    &amp;#x3C;none&gt;          5m    v1.28.0   192.168.1.101 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
# k8s-worker-02   Ready    &amp;#x3C;none&gt;          3m    v1.28.0   192.168.1.102 Ubuntu 22.04.3 LTS   5.15.0-91-generic   containerd://1.6.22
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第五步：部署测试应用（离线）&lt;/h2&gt;
&lt;h3&gt;5.1 准备应用镜像&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上下载 nginx 镜像
docker pull nginx:alpine
docker save nginx:alpine -o nginx-alpine.tar

# 传输到 k8s 集群
scp nginx-alpine.tar root@192.168.1.100:/root/

# 在所有节点上导入镜像
sudo ctr -n k8s.io images import nginx-alpine.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 部署 Nginx&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建 nginx 部署
kubectl create deployment nginx --image=nginx:alpine --replicas=3

# 创建服务
kubectl expose deployment nginx --type=NodePort --port=80 --target-port=80

# 查看部署状态
kubectl get pods -o wide
kubectl get svc
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 验证服务&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 测试服务访问
curl http://192.168.1.100:&amp;#x3C;NodePort&gt;

# 查看 Pod 日志
kubectl logs -l app=nginx

# 进入 Pod 容器
kubectl exec -it &amp;#x3C;pod-name&gt; -- /bin/sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第六步：离线安装常用工具&lt;/h2&gt;
&lt;h3&gt;6.1 安装 Helm（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上下载 Helm
wget https://get.helm.sh/helm-v3.12.0-linux-arm64.tar.gz
tar -xzf helm-v3.12.0-linux-arm64.tar.gz
cp linux-arm64/helm /tmp/

# 传输到离线服务器
scp /tmp/helm root@192.168.1.100:/usr/local/bin/

# 在所有节点上安装
sudo chmod +x /usr/local/bin/helm
helm version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 安装 k9s（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上下载 k9s
wget https://github.com/derailed/k9s/releases/download/v0.30.9/k9s_Linux_arm64.tar.gz
tar -xzf k9s_Linux_arm64.tar.gz
cp k9s /tmp/

# 传输到离线服务器
scp /tmp/k9s root@192.168.1.100:/usr/local/bin/

# 在所有节点上安装
sudo chmod +x /usr/local/bin/k9s
k9s version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 部署 Portainer（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上下载 Portainer 镜像
helm repo add portainer https://portainer.github.io/k8s
helm pull portainer/portainer --version 1.8.0
tar -xzf portainer-1.8.0.tgz

# 导出所需镜像
docker pull portainer/agent:2.19.0
docker pull portainer/kube-state-metrics:2.8.0
docker save portainer/agent:2.19.0 -o portainer-agent.tar
docker save portainer/kube-state-metrics:2.8.0 -o portainer-kube-state-metrics.tar

# 传输到离线环境
scp portainer/ root@192.168.1.100:/root/
scp portainer-*.tar root@192.168.1.100:/root/

# 在离线环境导入镜像并部署
sudo ctr -n k8s.io images import portainer-agent.tar
sudo ctr -n k8s.io images import portainer-kube-state-metrics.tar

kubectl create namespace portainer
cd /root/portainer/portainer
kubectl apply -f templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见问题排查&lt;/h2&gt;
&lt;h3&gt;问题 1: dpkg 依赖错误&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 错误：dpkg: 处理包 xxx 时出错：依赖问题
# 解决：按顺序安装所有依赖包

cd /root/k8s-offline/packages
# 先安装基础依赖
sudo dpkg -i lib*.*.deb
# 再安装运行时
sudo dpkg -i runc*.deb
# 最后安装主包
sudo dpkg -i containerd.io*.deb

# 或者使用 --force-depends（不推荐）
sudo dpkg -i --force-depends package.deb
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;问题 2: 节点状态为 NotReady&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 查看 kubelet 日志
sudo journalctl -u kubelet -f

# 查看容器状态
sudo crictl ps -a

# 检查 CRI socket
ls -la /var/run/containerd/containerd.sock

# 检查镜像是否导入
sudo ctr -n k8s.io images ls
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;问题 3: Pod 镜像拉取失败&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 错误：ImagePullBackOff / ErrImagePull
# 原因：离线环境没有镜像

# 解决：确保镜像已导入所有节点
# 在控制节点检查
sudo ctr -n k8s.io images ls

# 在 worker 节点导入
sudo ctr -n k8s.io images import &amp;#x3C;image&gt;.tar

# 重启 pod
kubectl delete pod &amp;#x3C;pod-name&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;问题 4: CNI 插件失败&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 检查 Calico 状态
kubectl get pods -n kube-system | grep calico

# 查看 Calico 日志
kubectl logs -n kube-system &amp;#x3C;calico-pod&gt;

# 检查镜像是否导入
sudo ctr -n k8s.io images ls | grep calico
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;离线镜像管理最佳实践&lt;/h2&gt;
&lt;h3&gt;7.1 镜像清单管理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建镜像清单文件
cat &amp;#x3C;&amp;#x3C;EOF &gt; image-list.txt
nginx:alpine
calico/operator:v1.30.3
calico/node:v3.26.1
calico/cni:v3.26.1
calico/kube-controllers:v3.26.1
calico/pod2daemon-flexvol:v3.26.1
EOF

# 批量导出镜像
while read image; do
    docker save &quot;$image&quot; -o &quot;${image//\//_}.tar&quot;
done &amp;#x3C; image-list.txt

# 批量导入镜像
for img in *.tar; do
    sudo ctr -n k8s.io images import &quot;$img&quot;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 本地镜像仓库（可选）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上搭建本地 Harbor 或 registry
docker run -d -p 5000:5000 --name registry registry:2

# 重新标记镜像
docker tag nginx:alpine 192.168.1.200:5000/nginx:alpine
docker push 192.168.1.200:5000/nginx:alpine

# 在 k8s 中配置 insecureRegistry
cat &amp;#x3C;&amp;#x3C;EOF | sudo tee /etc/containerd/config.toml | grep -A5 hosts
[plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry]
  [plugins.&quot;io.containerd.grpc.v1.cri&quot;.registry.configs.&quot;192.168.1.200:5000&quot;.tls]
    insecure_skip_verify = true
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;高可用集群扩展&lt;/h2&gt;
&lt;h3&gt;8.1 添加第二个 Control Plane（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在第一个控制节点上获取控制平面 join 命令
sudo kubeadm token create --print-join-command --control-plane

# 在第二个控制节点上执行相同的安装步骤后加入
sudo &amp;#x3C;join-command&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.2 部署 HA Proxy（离线）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 在有网的机器上下载 HA Proxy
sudo apt download -d --no-install haproxy
scp /var/cache/apt/archives/haproxy*.deb root@192.168.1.200:/tmp/

# 在所有控制节点前部署 HA Proxy
cd /tmp
sudo dpkg -i haproxy*.deb

cat &amp;#x3C;&amp;#x3C;EOF &gt; /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    maxconn 256

defaults
    log global
    mode tcp
    option dontlognull
    timeout connect 5000
    timeout client 50000
    timeout server 50000

frontend kubernetes-api
    bind *:6443
    default_backend kubernetes-api-servers

backend kubernetes-api-servers
    balance roundrobin
    server k8s-master-01 192.168.1.100:6443 check
    server k8s-master-02 192.168.1.103:6443 check
EOF

sudo systemctl enable haproxy
sudo systemctl restart haproxy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过以上步骤，我们成功在&lt;strong&gt;完全离线&lt;/strong&gt;的 ARM64 架构 Linux 服务器上搭建了一个完整的 Kubernetes 集群。&lt;/p&gt;
&lt;h3&gt;技术栈总结&lt;/h3&gt;
&lt;p&gt;| 组件 | 版本 | 说明 | 离线方式 |
|------|------|------|----------|
| 操作系统 | Ubuntu 22.04 LTS | ARM64 架构 | - |
| Kubernetes | v1.28.0 | 容器编排平台 | deb 包离线安装 |
| 容器运行时 | containerd 1.6.22 | 容器运行时 | tar 包/deb 包 |
| 网络插件 | Calico v3.26.1 | CNI 网络 | YAML + 镜像导入 |
| 包管理器 | Helm v3.12 | K8s 包管理 | 二进制分发 |&lt;/p&gt;
&lt;h3&gt;核心步骤回顾&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;✅ &lt;strong&gt;离线资源准备&lt;/strong&gt; — 在有网机器下载所有包和镜像&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;传输离线包&lt;/strong&gt; — 使用 USB 或跳板机传输&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;系统初始化&lt;/strong&gt; — 配置内核参数，禁用 Swap&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;离线安装运行时&lt;/strong&gt; — containerd 离线安装&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;离线安装 K8s&lt;/strong&gt; — deb 包离线安装&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;初始化 Control Plane&lt;/strong&gt; — kubeadm init&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;导入 CNI 镜像&lt;/strong&gt; — 离线导入 Calico 镜像&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;安装网络插件&lt;/strong&gt; — 本地 YAML 文件&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;加入 Worker 节点&lt;/strong&gt; — kubeadm join&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;部署测试应用&lt;/strong&gt; — 离线镜像导入&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;离线部署关键点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;镜像管理&lt;/strong&gt; — 提前规划所需镜像清单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖处理&lt;/strong&gt; — 使用 &lt;code&gt;apt download -d&lt;/code&gt; 获取完整依赖&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本锁定&lt;/strong&gt; — 确保所有节点版本一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地源&lt;/strong&gt; — 可选配置本地 apt 源加速部署&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文档记录&lt;/strong&gt; — 记录所有版本号和配置参数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;下一步优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 配置本地 apt 源（apt-ftparchive）&lt;/li&gt;
&lt;li&gt;[ ] 部署本地镜像仓库（Harbor/registry）&lt;/li&gt;
&lt;li&gt;[ ] 配置持久化存储（Local Path Provisioner）&lt;/li&gt;
&lt;li&gt;[ ] 部署 Ingress Controller（Nginx Ingress）&lt;/li&gt;
&lt;li&gt;[ ] 设置监控告警（Prometheus + Grafana 离线版）&lt;/li&gt;
&lt;li&gt;[ ] 实现备份恢复（Velero 离线配置）&lt;/li&gt;
&lt;li&gt;[ ] 配置 GitLab CI 离线流水线&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;离线资源清单模板&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 保存此清单用于未来部署
K8s 版本：v1.28.0
架构：arm64
日期：2026-05-14

必需包:
- kubeadm_1.28.0-00_arm64.deb
- kubelet_1.28.0-00_arm64.deb
- kubectl_1.28.0-00_arm64.deb
- containerd.io_1.6.22_arm64.deb

必需镜像:
- calico/operator:v1.30.3
- calico/node:v3.26.1
- calico/cni:v3.26.1
- calico/kube-controllers:v3.26.1
- calico/pod2daemon-flexvol:v3.26.1
- nginx:alpine (测试用)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;更新时间&lt;/strong&gt;: 2026-05-14&lt;br&gt;
&lt;strong&gt;阅读时间&lt;/strong&gt;: 20 分钟&lt;br&gt;
&lt;strong&gt;适用场景&lt;/strong&gt;: 内网环境、等保要求、金融系统、离线服务器、ARM64 架构&lt;/p&gt;
</content:encoded><category>Kubernetes</category><category>DevOps</category></item><item><title>LangChain 与 LangGraph 深度解析</title><link>https://heyedwardchen.com/blog/langchain-langgraph-tutorial/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/langchain-langgraph-tutorial/</guid><description>从基础到进阶，全面掌握 LangChain 框架和 LangGraph 状态机，构建复杂的 AI Agent 工作流。</description><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;LangChain 与 LangGraph 深度解析&lt;/h1&gt;
&lt;p&gt;本文将深入探讨 LangChain 框架和 LangGraph 状态机，从基础概念到高级应用，帮助你构建复杂、可维护的 AI Agent 系统。&lt;/p&gt;
&lt;h2&gt;为什么需要 LangChain 和 LangGraph？&lt;/h2&gt;
&lt;h3&gt;传统 LLM 应用的局限性&lt;/h3&gt;
&lt;p&gt;直接使用 LLM 面临以下挑战：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态管理困难&lt;/strong&gt; — 多轮对话难以维护上下文&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作流复杂&lt;/strong&gt; — 多步骤任务编排混乱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测性差&lt;/strong&gt; — 难以调试和监控&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺乏循环&lt;/strong&gt; — 无法实现迭代优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人类介入难&lt;/strong&gt; — 难以实现人机协作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;LangChain 的解决方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;LangChain&lt;/strong&gt; 提供了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;模块化组件&lt;/strong&gt; — 链、提示词、记忆、工具&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;编排能力&lt;/strong&gt; — 将多个组件串联&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;记忆管理&lt;/strong&gt; — 维护对话历史&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;工具集成&lt;/strong&gt; — 连接外部 API 和数据源&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;LangGraph 的进阶能力&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;LangGraph&lt;/strong&gt; 在 LangChain 基础上增加了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🔄 &lt;strong&gt;循环工作流&lt;/strong&gt; — 支持迭代和重试&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;状态机&lt;/strong&gt; — 明确的状态转换&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;人机协作&lt;/strong&gt; — 人类审核和干预&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;可视化&lt;/strong&gt; — 工作流图可视化&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;持久化&lt;/strong&gt; — 检查点和恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;LangChain 核心概念&lt;/h2&gt;
&lt;h3&gt;1. 链 (Chains)&lt;/h3&gt;
&lt;p&gt;链是 LangChain 的基本构建块，将多个组件串联起来。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

# 创建提示模板
prompt = PromptTemplate(
    input_variables=[&quot;topic&quot;],
    template=&quot;请写一篇关于 {topic} 的短文，不超过 200 字。&quot;
)

# 创建 LLM
llm = OpenAI(temperature=0.7)

# 创建链
chain = LLMChain(llm=llm, prompt=prompt)

# 执行
result = chain.run(&quot;人工智能&quot;)
print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 提示词管理 (Prompts)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

# 聊天提示模板
prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;你是一个有帮助的助手。&quot;),
    MessagesPlaceholder(&quot;chat_history&quot;),  # 对话历史
    (&quot;human&quot;, &quot;{input}&quot;)  # 用户输入
])

# 格式化提示
messages = prompt.format_messages(
    chat_history=[...],
    input=&quot;你好&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 记忆 (Memory)&lt;/h3&gt;
&lt;p&gt;记忆组件维护对话历史，实现多轮对话。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 创建带记忆的链
memory = ConversationBufferMemory(
    memory_key=&quot;chat_history&quot;,
    return_messages=True
)

conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True
)

# 多轮对话
conversation.predict(input=&quot;我叫 Edward&quot;)
conversation.predict(input=&quot;我做什么工作的？&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;记忆类型对比：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;| 类型                             | 说明        | 适用场景        |
| ------------------------------ | --------- | ----------- |
| ConversationBufferMemory       | 存储所有历史消息  | 简单对话        |
| ConversationBufferWindowMemory | 只保留最近 N 条 | 长对话节省 token |
| ConversationSummaryMemory      | 摘要历史对话    | 超长对话        |
| EntityMemory                   | 提取实体信息    | 需要记住事实的对话   |&lt;/p&gt;
&lt;h3&gt;4. 工具 (Tools)&lt;/h3&gt;
&lt;p&gt;工具让 LLM 能够调用外部函数和 API。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.agents import tool

@tool
def get_current_weather(city: str) -&gt; str:
    &quot;&quot;&quot;获取当前天气信息&quot;&quot;&quot;
    # 模拟天气数据
    return f&quot;{city} 当前温度 25°C，晴天&quot;

@tool
def search_web(query: str) -&gt; str:
    &quot;&quot;&quot;搜索网络信息&quot;&quot;&quot;
    # 调用搜索引擎 API
    return f&quot;搜索结果：{query} 的相关信息&quot;

tools = [get_current_weather, search_web]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 代理 (Agents)&lt;/h3&gt;
&lt;p&gt;代理能够自主选择和使用工具。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.agents import initialize_agent, AgentType

# 初始化代理
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    handle_parsing_errors=True,
    verbose=True
)

# 执行任务
response = agent.run(&quot;北京的天气怎么样？&quot;)
print(response)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;LangGraph 核心概念&lt;/h2&gt;
&lt;h3&gt;什么是 LangGraph？&lt;/h3&gt;
&lt;p&gt;LangGraph 是基于 LangChain 的&lt;strong&gt;有向图&lt;/strong&gt;框架，用于构建多 Agent、多步骤的 AI 应用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心特性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态机&lt;/strong&gt; — 明确定义状态和转换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环&lt;/strong&gt; — 支持迭代和重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人机协作&lt;/strong&gt; — 人类审核节点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久化&lt;/strong&gt; — 检查点和恢复&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可视化&lt;/strong&gt; — 自动生成流程图&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. 图的基本结构&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import StateGraph, END

# 定义状态
class State(TypedDict):
    input: str
    output: str
    iterations: int
    approval: bool

# 创建图
workflow = StateGraph(State)

# 添加节点
workflow.add_node(&quot;agent&quot;, agent_node)
workflow.add_node(&quot;human&quot;, human_approval_node)
workflow.add_node(&quot;refine&quot;, refinement_node)

# 添加边
workflow.add_edge(&quot;agent&quot;, &quot;human&quot;)
workflow.add_conditional_edges(
    &quot;human&quot;,
    should_refine,  # 条件函数
    {
        &quot;yes&quot;: &quot;refine&quot;,
        &quot;no&quot;: END
    }
)

# 设置入口点
workflow.set_entry_point(&quot;agent&quot;)

# 编译图
app = workflow.compile()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 状态管理&lt;/h3&gt;
&lt;p&gt;状态是 LangGraph 的核心，在节点间传递。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import TypedDict, Annotated, Sequence
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage

# 定义状态
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]
    context: dict
    loop_count: int
    human_approved: bool

# 节点函数接收和返回状态
def agent_node(state: AgentState) -&gt; AgentState:
    # 访问状态
    messages = state[&quot;messages&quot;]
    
    # 处理逻辑
    response = llm.invoke(messages)
    
    # 返回更新的状态
    return {&quot;messages&quot;: [response]}

def human_approval_node(state: AgentState) -&gt; AgentState:
    # 等待人类审批
    approval = wait_for_human_approval(state[&quot;messages&quot;])
    
    return {&quot;human_approved&quot;: approval}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 条件边&lt;/h3&gt;
&lt;p&gt;条件边根据状态决定下一步走向。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def should_continue(state: AgentState) -&gt; Literal[&quot;tools&quot;, &quot;end&quot;]:
    &quot;&quot;&quot;决定是否继续执行工具&quot;&quot;&quot;
    last_message = state[&quot;messages&quot;][-1]
    
    if last_message.tool_calls:
        return &quot;tools&quot;
    return &quot;end&quot;

def check_approval(state: AgentState) -&gt; Literal[&quot;approve&quot;, &quot;reject&quot;, &quot;revise&quot;]:
    &quot;&quot;&quot;检查人类审批结果&quot;&quot;&quot;
    if state[&quot;human_approved&quot;]:
        return &quot;approve&quot;
    elif state[&quot;loop_count&quot;] &gt; 3:
        return &quot;reject&quot;
    return &quot;revise&quot;

# 添加条件边
workflow.add_conditional_edges(
    &quot;agent&quot;,
    should_continue,
    {
        &quot;tools&quot;: &quot;tool_executor&quot;,
        &quot;end&quot;: END
    }
)

workflow.add_conditional_edges(
    &quot;human&quot;,
    check_approval,
    {
        &quot;approve&quot;: END,
        &quot;reject&quot;: &quot;end_with_error&quot;,
        &quot;revise&quot;: &quot;agent&quot;
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 循环和迭代&lt;/h3&gt;
&lt;p&gt;LangGraph 天然支持循环，通过条件边实现。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 定义最大迭代次数
MAX_ITERATIONS = 5

def iterative_refinement(state: AgentState) -&gt; AgentState:
    &quot;&quot;&quot;迭代优化答案&quot;&quot;&quot;
    if state[&quot;loop_count&quot;] &gt;= MAX_ITERATIONS:
        return {&quot;loop_count&quot;: state[&quot;loop_count&quot;]}
    
    # 生成改进版本
    improved = refine_answer(state[&quot;messages&quot;])
    
    return {
        &quot;messages&quot;: [improved],
        &quot;loop_count&quot;: state[&quot;loop_count&quot;] + 1
    }

# 添加循环边
workflow.add_edge(&quot;refine&quot;, &quot;agent&quot;)  # 回到 agent 节点
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 持久化和检查点&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.checkpoint.memory import MemorySaver

# 创建检查点存储器
checkpointer = MemorySaver()

# 编译时传入
app = workflow.compile(
    checkpointer=checkpointer,
    interrupt_before=[&quot;human&quot;]  # 在 human 节点前中断
)

# 创建线程
config = {&quot;configurable&quot;: {&quot;thread_id&quot;: &quot;1&quot;}}

# 执行并保存状态
result = app.invoke({&quot;messages&quot;: [&quot;你好&quot;]}, config)

# 恢复状态继续执行
result = app.invoke({&quot;messages&quot;: [&quot;继续&quot;]}, config)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;实战案例&lt;/h2&gt;
&lt;h3&gt;案例 1：带人类审核的内容生成&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langchain_core.messages import HumanMessage, AIMessage

class ContentState(TypedDict):
    topic: str
    draft: str
    feedback: str
    approved: bool
    iteration: int

def generate_draft(state: ContentState) -&gt; ContentState:
    &quot;&quot;&quot;生成内容草稿&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    请写一篇关于 {state[&apos;topic&apos;]} 的文章。
    {f&apos;根据反馈改进：{state[&quot;feedback&quot;]}&apos; if state.get(&apos;feedback&apos;) else &apos;&apos;}
    &quot;&quot;&quot;
    
    response = llm.invoke(prompt)
    return {&quot;draft&quot;: response.content, &quot;iteration&quot;: state.get(&quot;iteration&quot;, 0) + 1}

def human_review(state: ContentState) -&gt; ContentState:
    &quot;&quot;&quot;人类审核&quot;&quot;&quot;
    print(f&quot;当前草稿：{state[&apos;draft&apos;]}&quot;)
    print(f&quot;迭代次数：{state[&apos;iteration&apos;]}&quot;)
    
    # 模拟人类输入
    approval = input(&quot;批准？(y/n/r 修订): &quot;)
    
    if approval == &apos;y&apos;:
        return {&quot;approved&quot;: True}
    elif approval == &apos;n&apos;:
        return {&quot;approved&quot;: False}
    else:
        feedback = input(&quot;提供反馈：&quot;)
        return {&quot;feedback&quot;: feedback, &quot;approved&quot;: False}

def decide_next(state: ContentState) -&gt; str:
    &quot;&quot;&quot;决定下一步&quot;&quot;&quot;
    if state[&quot;approved&quot;]:
        return &quot;end&quot;
    elif state[&quot;iteration&quot;] &gt;= 3:
        return &quot;end&quot;  # 达到最大迭代
    return &quot;revise&quot;

# 构建图
workflow = StateGraph(ContentState)

workflow.add_node(&quot;generate&quot;, generate_draft)
workflow.add_node(&quot;review&quot;, human_review)

workflow.set_entry_point(&quot;generate&quot;)
workflow.add_edge(&quot;generate&quot;, &quot;review&quot;)

workflow.add_conditional_edges(
    &quot;review&quot;,
    decide_next,
    {
        &quot;revise&quot;: &quot;generate&quot;,
        &quot;end&quot;: END
    }
)

app = workflow.compile()

# 执行
result = app.invoke({
    &quot;topic&quot;: &quot;人工智能的未来&quot;,
    &quot;iteration&quot;: 0
})

print(f&quot;最终内容：{result[&apos;draft&apos;]}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;案例 2：多 Agent 协作系统&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from typing import Literal

class MultiAgentState(TypedDict):
    query: str
    research: str
    analysis: str
    draft: str
    final_answer: str

# 研究 Agent
def researcher(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;收集信息&quot;&quot;&quot;
    research_tools = [search_web, get_wikipedia, read_document]
    
    agent = create_agent(research_tools, &quot;研究专家&quot;)
    result = agent.run(f&quot;研究主题：{state[&apos;query&apos;]}&quot;)
    
    return {&quot;research&quot;: result}

# 分析 Agent
def analyst(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;分析信息&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    基于以下研究结果进行分析：
    {state[&apos;research&apos;]}
    
    请提供：
    1. 关键发现
    2. 数据支持
    3. 潜在问题
    &quot;&quot;&quot;
    
    analysis = llm.invoke(prompt)
    return {&quot;analysis&quot;: analysis.content}

# 写作 Agent
def writer(state: MultiAgentState) -&gt; MultiAgentState:
    &quot;&quot;&quot;撰写答案&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    综合以下信息撰写最终答案：
    
    研究：{state[&apos;research&apos;]}
    分析：{state[&apos;analysis&apos;]}
    
    要求：
    - 结构清晰
    - 论据充分
    - 语言流畅
    &quot;&quot;&quot;
    
    draft = llm.invoke(prompt)
    return {&quot;draft&quot;: draft.content}

# 评审 Agent
def reviewer(state: MultiAgentState) -&gt; Literal[&quot;approve&quot;, &quot;revise&quot;]:
    &quot;&quot;&quot;评审答案&quot;&quot;&quot;
    prompt = f&quot;&quot;&quot;
    评审以下答案质量：
    {state[&apos;draft&apos;]}
    
    评分标准：
    - 准确性
    - 完整性
    - 逻辑性
    
    如果满意返回 &apos;approve&apos;，否则返回 &apos;revise&apos;
    &quot;&quot;&quot;
    
    review = llm.invoke(prompt)
    return &quot;approve&quot; if &quot;满意&quot; in review.content else &quot;revise&quot;

# 构建多 Agent 图
workflow = StateGraph(MultiAgentState)

workflow.add_node(&quot;researcher&quot;, researcher)
workflow.add_node(&quot;analyst&quot;, analyst)
workflow.add_node(&quot;writer&quot;, writer)
workflow.add_node(&quot;reviewer&quot;, reviewer)

# 定义流程
workflow.set_entry_point(&quot;researcher&quot;)
workflow.add_edge(&quot;researcher&quot;, &quot;analyst&quot;)
workflow.add_edge(&quot;analyst&quot;, &quot;writer&quot;)
workflow.add_edge(&quot;writer&quot;, &quot;reviewer&quot;)

# 添加条件边
workflow.add_conditional_edges(
    &quot;reviewer&quot;,
    (lambda state: &quot;revise&quot; if reviewer(state) == &quot;revise&quot; else &quot;end&quot;),
    {
        &quot;revise&quot;: &quot;writer&quot;,
        &quot;end&quot;: END
    }
)

app = workflow.compile()

# 执行
result = app.invoke({&quot;query&quot;: &quot;量子计算的发展现状&quot;})
print(result[&quot;draft&quot;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;案例 3：ReAct 模式实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.prebuilt import ToolNode
from langchain.agents import AgentExecutor

class ReActState(TypedDict):
    messages: Annotated[list, add_messages]
    tool_output: str

def model_node(state: ReActState) -&gt; ReActState:
    &quot;&quot;&quot;LLM 决策节点&quot;&quot;&quot;
    messages = state[&quot;messages&quot;]
    
    # 使用能够进行工具调用的模型
    response = llm_with_tools.invoke(messages)
    
    return {&quot;messages&quot;: [response]}

def tool_node(state: ReActState) -&gt; ReActState:
    &quot;&quot;&quot;工具执行节点&quot;&quot;&quot;
    # 执行工具调用
    tool_calls = state[&quot;messages&quot;][-1].tool_calls
    
    outputs = []
    for tool_call in tool_calls:
        tool = tools_by_name[tool_call[&quot;name&quot;]]
        response = tool.invoke(tool_call[&quot;args&quot;])
        outputs.append(response)
    
    return {&quot;tool_output&quot;: &quot;\n&quot;.join(outputs)}

def should_continue(state: ReActState) -&gt; Literal[&quot;tools&quot;, &quot;end&quot;]:
    &quot;&quot;&quot;判断是否需要调用工具&quot;&quot;&quot;
    messages = state[&quot;messages&quot;]
    last_message = messages[-1]
    
    if last_message.tool_calls:
        return &quot;tools&quot;
    return &quot;end&quot;

# 构建 ReAct 图
workflow = StateGraph(ReActState)

workflow.add_node(&quot;agent&quot;, model_node)
workflow.add_node(&quot;tools&quot;, ToolNode(tools))

workflow.set_entry_point(&quot;agent&quot;)

workflow.add_conditional_edges(
    &quot;agent&quot;,
    should_continue,
    {
        &quot;tools&quot;: &quot;tools&quot;,
        &quot;end&quot;: END
    }
)

workflow.add_edge(&quot;tools&quot;, &quot;agent&quot;)

app = workflow.compile()

# 执行
result = app.invoke({
    &quot;messages&quot;: [HumanMessage(content=&quot;北京今天的天气如何？&quot;)]
})

print(result[&quot;messages&quot;][-1].content)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;高级模式&lt;/h2&gt;
&lt;h3&gt;1. 子图 (Subgraphs)&lt;/h3&gt;
&lt;p&gt;将复杂工作流分解为子图。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 创建子图
research_workflow = StateGraph(ResearchState)
research_workflow.add_node(&quot;search&quot;, search_node)
research_workflow.add_node(&quot;analyze&quot;, analyze_node)
research_workflow.compile()

# 在主图中使用子图
main_workflow = StateGraph(MainState)
main_workflow.add_node(&quot;research&quot;, research_workflow)
main_workflow.add_node(&quot;write&quot;, write_node)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 并发执行&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.graph import START

# 并行启动多个节点
workflow.add_edge(START, &quot;researcher&quot;)
workflow.add_edge(START, &quot;data_collector&quot;)
workflow.add_edge(START, &quot;context_loader&quot;)

# 汇合点
workflow.add_edge(&quot;researcher&quot;, &quot;synthesizer&quot;)
workflow.add_edge(&quot;data_collector&quot;, &quot;synthesizer&quot;)
workflow.add_edge(&quot;context_loader&quot;, &quot;synthesizer&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 动态图&lt;/h3&gt;
&lt;p&gt;根据运行时状态动态修改图结构。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def dynamic_router(state: State) -&gt; str:
    &quot;&quot;&quot;动态决定执行路径&quot;&quot;&quot;
    if state[&quot;complexity&quot;] &gt; 0.8:
        return &quot;complex_workflow&quot;
    elif state[&quot;complexity&quot;] &gt; 0.5:
        return &quot;medium_workflow&quot;
    return &quot;simple_workflow&quot;

workflow.add_conditional_edges(
    START,
    dynamic_router,
    {
        &quot;simple_workflow&quot;: &quot;simple_agent&quot;,
        &quot;medium_workflow&quot;: &quot;medium_agent&quot;,
        &quot;complex_workflow&quot;: &quot;complex_agent&quot;
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 超时和重试&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.pregel import RetryPolicy

app = workflow.compile(
    checkpointer=checkpointer,
    retry_policy=RetryPolicy(
        max_attempts=3,
        interval=1.0,
        backoff=2.0
    ),
    timeout=300  # 5 分钟超时
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调试和监控&lt;/h2&gt;
&lt;h3&gt;1. 可视化&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 生成流程图
app.get_graph().draw_mermaid_png(output_path=&quot;workflow.png&quot;)

# 或生成 Mermaid 代码
mermaid = app.get_graph().draw_mermaid()
print(mermaid)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 日志记录&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langgraph.prebuilt import InjectedState

def logged_node(state: AgentState) -&gt; AgentState:
    import logging
    logging.info(f&quot;进入节点，状态：{state}&quot;)
    
    # 处理逻辑
    result = process(state)
    
    logging.info(f&quot;节点完成，返回：{result}&quot;)
    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 追踪&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from langchain.tracers import LangChainTracer

with LangChainTracer(project=&quot;langgraph-demo&quot;) as tracer:
    result = app.invoke(input_data)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最佳实践&lt;/h2&gt;
&lt;h3&gt;1. 状态设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 使用 TypedDict 定义明确的状态结构&lt;/li&gt;
&lt;li&gt;✅ 保持状态不可变，返回新状态而非修改&lt;/li&gt;
&lt;li&gt;✅ 使用 Annotated 和 reducer 函数管理复杂状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 节点设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;✅ 单一职责：每个节点只做一件事&lt;/li&gt;
&lt;li&gt;✅ 无副作用：节点不应修改外部状态&lt;/li&gt;
&lt;li&gt;✅ 可测试：节点函数应易于单元测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 错误处理&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def safe_node(state: State) -&gt; State:
    try:
        result = risky_operation(state)
        return {&quot;result&quot;: result}
    except Exception as e:
        return {
            &quot;error&quot;: str(e),
            &quot;fallback&quot;: default_value
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 性能优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用异步节点处理 I/O 密集型任务&lt;/li&gt;
&lt;li&gt;缓存重复计算结果&lt;/li&gt;
&lt;li&gt;合理设置超时和重试策略&lt;/li&gt;
&lt;li&gt;使用检查点避免重复计算&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;LangChain vs LangGraph 对比&lt;/h2&gt;
&lt;p&gt;| 特性 | LangChain | LangGraph |
|------|-----------|-----------|
| 工作流类型 | 线性链 | 有向图 |
| 循环支持 | 有限 | 原生支持 |
| 状态管理 | 简单 | 复杂状态机 |
| 人机协作 | 困难 | 内置支持 |
| 持久化 | 基础 | 检查点机制 |
| 可视化 | 简单 | 完整流程图 |
| 适用场景 | 简单任务 | 复杂 Agent 系统 |&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;h3&gt;核心要点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;LangChain&lt;/strong&gt; 提供了构建 LLM 应用的基础组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LangGraph&lt;/strong&gt; 在 LangChain 之上增加了图结构和状态机&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态管理&lt;/strong&gt; 是 LangGraph 的核心，确保数据在节点间正确传递&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件边&lt;/strong&gt; 实现动态流程控制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查点&lt;/strong&gt; 支持持久化和恢复&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;人机协作&lt;/strong&gt; 实现人类审核和干预&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;学习路径&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;掌握 LangChain 基础：链、提示词、记忆、工具&lt;/li&gt;
&lt;li&gt;理解 LangGraph 核心：状态、节点、边&lt;/li&gt;
&lt;li&gt;实践常见模式：ReAct、人类审核、多 Agent&lt;/li&gt;
&lt;li&gt;探索高级特性：子图、并发、动态图&lt;/li&gt;
&lt;li&gt;学习调试和监控技巧&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;下一步&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 阅读官方文档：https://langchain-ai.github.io/langgraph/&lt;/li&gt;
&lt;li&gt;[ ] 尝试构建自己的 Agent 系统&lt;/li&gt;
&lt;li&gt;[ ] 学习 LangSmith 追踪和调试&lt;/li&gt;
&lt;li&gt;[ ] 探索与其他框架的集成&lt;/li&gt;
&lt;li&gt;[ ] 参与开源社区贡献&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;更新时间&lt;/strong&gt;: 2026-05-14&lt;br&gt;
&lt;strong&gt;阅读时间&lt;/strong&gt;: 25 分钟&lt;br&gt;
&lt;strong&gt;适用场景&lt;/strong&gt;: LLM 应用开发、Agent 系统构建、工作流编排&lt;/p&gt;
&lt;h3&gt;参考资源&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://python.langchain.com/&quot;&gt;LangChain 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://langchain-ai.github.io/langgraph/&quot;&gt;LangGraph 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/langchain&quot;&gt;LangChain GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/langchain-ai/langgraph&quot;&gt;LangGraph GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LangChain</category><category>LangGraph</category><category>LLM</category><category>Agent</category><category>工作流</category><category>AI 工程</category></item><item><title>从零搭建 Astro 个人博客</title><link>https://heyedwardchen.com/blog/astro-blog-setup/</link><guid isPermaLink="true">https://heyedwardchen.com/blog/astro-blog-setup/</guid><description>使用 Astro + TailwindCSS 构建现代化静态博客，实现内容集合、置顶排序、时间线导航等最佳实践。</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;从零搭建 Astro 个人博客&lt;/h1&gt;
&lt;p&gt;本文记录使用 Astro 构建个人博客的完整流程，涵盖项目初始化、内容管理、交互优化、组件抽象等核心环节，并总结实际开发中的最佳实践。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;一、技术选型&lt;/h2&gt;
&lt;h3&gt;为什么选择 Astro？&lt;/h3&gt;
&lt;p&gt;Astro 是专为内容驱动型网站设计的现代 Web 框架，核心优势包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;零 JavaScript 输出&lt;/strong&gt; — 默认不发送 JS，按需 hydration，性能极致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态生成优先&lt;/strong&gt; — 预渲染 HTML，SEO 友好&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多框架兼容&lt;/strong&gt; — 可混用 React、Vue、Svelte 组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown 原生支持&lt;/strong&gt; — Content Collections 提供类型安全的内容管理&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;技术栈&lt;/h3&gt;
&lt;p&gt;| 模块 | 技术选型 | 理由 |
|------|----------|------|
| 框架 | Astro 4.x | 静态生成，零 JS 输出 |
| 样式 | TailwindCSS | 原子化 CSS，开发效率高 |
| 内容 | Markdown + Content Collections | 类型安全，写作体验佳 |
| 部署 | Cloudflare Pages | 全球 CDN，自动 HTTPS，免费 |
| 版本控制 | GitHub | 免费托管，CI/CD 集成 |&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;二、项目初始化&lt;/h2&gt;
&lt;h3&gt;创建项目&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm create astro@latest my-blog
# 或
npx create-astro@latest my-blog
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐选项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用空模板（Empty）&lt;/li&gt;
&lt;li&gt;启用 TypeScript&lt;/li&gt;
&lt;li&gt;添加 TailwindCSS 集成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安装 TailwindCSS&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -D @astrojs/tailwind tailwindcss
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;astro.config.mjs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { defineConfig } from &apos;astro/config&apos;;
import tailwind from &apos;@astrojs/tailwind&apos;;

export default defineConfig({
  integrations: [tailwind()],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;三、内容集合（Content Collections）&lt;/h2&gt;
&lt;h3&gt;1. 定义 Schema&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/content/config.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { defineCollection, z } from &apos;astro:content&apos;;

const blogCollection = defineCollection({
  type: &apos;content&apos;,
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    tags: z.array(z.string()).default([]),
    pinned: z.boolean().default(false),
  }),
});

export const collections = {
  &apos;blog&apos;: blogCollection,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Schema 设计要点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt; 和 &lt;code&gt;description&lt;/code&gt; 为必填字段&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pubDate&lt;/code&gt; 使用 &lt;code&gt;z.date()&lt;/code&gt; 自动解析 ISO 字符串&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tags&lt;/code&gt; 默认为空数组，避免未定义错误&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pinned&lt;/code&gt; 控制文章置顶，默认 &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 创建文章&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/content/blog/your-post.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;---
title: &quot;文章标题&quot;
description: &quot;文章摘要，用于列表页展示&quot;
pubDate: 2026-05-14
tags: [&quot;技术&quot;, &quot;笔记&quot;]
pinned: true
---

# 正文

这里是文章内容...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Frontmatter 规范：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日期格式：&lt;code&gt;YYYY-MM-DD&lt;/code&gt;（ISO 8601）&lt;/li&gt;
&lt;li&gt;标签数组：使用双引号，避免解析错误&lt;/li&gt;
&lt;li&gt;置顶标识：&lt;code&gt;pinned: true&lt;/code&gt; 即可&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;四、核心功能实现&lt;/h2&gt;
&lt;h3&gt;4.1 Tag 展示与筛选&lt;/h3&gt;
&lt;h4&gt;问题描述&lt;/h4&gt;
&lt;p&gt;初始实现中，tag 标签挤在一起显示（如 &quot;AI 自动化生产力&quot;），缺乏间距和交互反馈。&lt;/p&gt;
&lt;h4&gt;设计目标&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;视觉：Stone 色系，适当间距，悬停微交互&lt;/li&gt;
&lt;li&gt;交互：点击筛选，URL 同步，浏览器导航支持&lt;/li&gt;
&lt;li&gt;无障碍：&lt;code&gt;aria-label&lt;/code&gt;，键盘可访问&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;实现方案&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;单篇文章页&lt;/strong&gt; (&lt;code&gt;src/pages/blog/[slug].astro&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;#x3C;div class=&quot;row gap-2 flex-wrap&quot;&gt;
  {post.data.tags.map((tag) =&gt; (
    &amp;#x3C;a href={`/blog?tag=${encodeURIComponent(tag)}`} 
       class=&quot;pill text-xs text-stone-600 bg-stone-100 hover:bg-stone-200 hover:text-stone-900 transition-colors px-2.5 py-0.5&quot;
       aria-label={`查看 ${tag} 标签的文章`}&gt;
      #{tag}
    &amp;#x3C;/a&gt;
  ))}
&amp;#x3C;/div&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键设计：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gap-2 flex-wrap&lt;/code&gt;：间距 + 换行&lt;/li&gt;
&lt;li&gt;Stone 色系：&lt;code&gt;text-stone-600 bg-stone-100&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;微交互：&lt;code&gt;hover:bg-stone-200 transition-colors&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;标签前缀：&lt;code&gt;#&lt;/code&gt; 符号&lt;/li&gt;
&lt;li&gt;可点击：&lt;code&gt;&amp;#x3C;a&gt;&lt;/code&gt; 标签跳转列表页&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;博客列表页&lt;/strong&gt; (&lt;code&gt;src/pages/blog.astro&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;#x3C;!-- Tag 筛选按钮 --&gt;
&amp;#x3C;div class=&quot;tags&quot;&gt;
  &amp;#x3C;button class=&quot;tag&quot; data-tag=&quot;all&quot;&gt;
    全部 &amp;#x3C;span class=&quot;ct&quot;&gt;({posts.length})&amp;#x3C;/span&gt;
  &amp;#x3C;/button&gt;
  {allTags.map((tag) =&gt; (
    &amp;#x3C;button class=&quot;tag&quot; data-tag={tag}&gt;
      {tag} &amp;#x3C;span class=&quot;ct&quot;&gt;({tagCounts[tag]})&amp;#x3C;/span&gt;
    &amp;#x3C;/button&gt;
  ))}
&amp;#x3C;/div&gt;

&amp;#x3C;!-- 文章列表 --&gt;
&amp;#x3C;ol class=&quot;posts&quot; id=&quot;posts-list&quot;&gt;
  {posts.map((post) =&gt; (
    &amp;#x3C;li class=&quot;post&quot; data-tags={post.data.tags.join(&apos;,&apos;)}&gt;
      &amp;#x3C;!-- 文章内容 --&gt;
    &amp;#x3C;/li&gt;
  ))}
&amp;#x3C;/ol&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;客户端筛选逻辑&lt;/strong&gt; (&lt;code&gt;&amp;#x3C;script is:inline&gt;&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;(function() {
  const tagButtons = document.querySelectorAll(&apos;.tag&apos;);
  const posts = document.querySelectorAll(&apos;#posts-list .post&apos;);
  const urlParams = new URLSearchParams(window.location.search);
  const selectedTag = urlParams.get(&apos;tag&apos;);

  // 初始化状态
  if (selectedTag) {
    tagButtons.forEach(btn =&gt; {
      btn.classList.toggle(&apos;on&apos;, btn.dataset.tag === selectedTag);
    });
    filterPosts(selectedTag);
  } else {
    tagButtons[0]?.classList.add(&apos;on&apos;);
  }

  // 点击筛选
  tagButtons.forEach(btn =&gt; {
    btn.addEventListener(&apos;click&apos;, () =&gt; {
      const tag = btn.dataset.tag;
      
      // 更新按钮状态
      tagButtons.forEach(b =&gt; b.classList.remove(&apos;on&apos;));
      btn.classList.add(&apos;on&apos;);

      // 更新 URL（不刷新）
      const newUrl = tag === &apos;all&apos; 
        ? &apos;/blog&apos; 
        : &apos;/blog?tag=&apos; + encodeURIComponent(tag);
      history.pushState({ tag: tag }, &apos;&apos;, newUrl);

      // 筛选文章
      filterPosts(tag);
    });
  });

  // 筛选函数
  function filterPosts(tag) {
    posts.forEach(post =&gt; {
      const postTags = post.dataset.tags.split(&apos;,&apos;);
      post.style.display = (tag === &apos;all&apos; || postTags.includes(tag)) ? &apos;&apos; : &apos;none&apos;;
    });
  }

  // 浏览器前进后退
  window.addEventListener(&apos;popstate&apos;, function(e) {
    var tag = (e.state &amp;#x26;&amp;#x26; e.state.tag) || &apos;all&apos;;
    tagButtons.forEach(function(btn) {
      btn.classList.toggle(&apos;on&apos;, btn.dataset.tag === tag);
    });
    filterPosts(tag);
  });
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;踩坑记录&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题 1：Script 组件压缩错误&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;错误写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;import Script from &apos;astro:script&apos;;
&amp;#x3C;Script&gt;
  const x = 1;
&amp;#x3C;/Script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正确写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;#x3C;script is:inline&gt;
  const x = 1;
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题 2：模板字符串兼容&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;错误写法（压缩后出错）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const url = `/blog?tag=${encodeURIComponent(tag)}`;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正确写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const url = &apos;/blog?tag=&apos; + encodeURIComponent(tag);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题 3：现代语法兼容&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;错误写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const tag = e.state?.tag || &apos;all&apos;;
tagButtons.forEach(btn =&gt; { ... });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正确写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var tag = (e.state &amp;#x26;&amp;#x26; e.state.tag) || &apos;all&apos;;
tagButtons.forEach(function(btn) { ... });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;最佳实践：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;&amp;#x3C;script is:inline&gt;&lt;/code&gt; 而非 &lt;code&gt;&amp;#x3C;Script&gt;&lt;/code&gt; 组件&lt;/li&gt;
&lt;li&gt;避免模板字符串，使用字符串拼接&lt;/li&gt;
&lt;li&gt;避免可选链 &lt;code&gt;?.&lt;/code&gt; 和箭头函数，使用传统语法&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;var&lt;/code&gt; 而非 &lt;code&gt;const/let&lt;/code&gt; 增强兼容性&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4.2 置顶文章功能&lt;/h3&gt;
&lt;h4&gt;需求分析&lt;/h4&gt;
&lt;p&gt;支持文章置顶，置顶文章在列表和首页优先展示，并带有视觉标识。&lt;/p&gt;
&lt;h4&gt;实现方案&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;排序逻辑：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分离置顶和普通文章&lt;/li&gt;
&lt;li&gt;各自按日期倒序排序&lt;/li&gt;
&lt;li&gt;合并：置顶在前，普通在后&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;代码实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const pinnedPosts = allPosts.filter(post =&gt; post.data.pinned);
const normalPosts = allPosts.filter(post =&gt; !post.data.pinned);

const sortedPinned = pinnedPosts.sort((a, b) =&gt; {
  const dateA = a.data.pubDate || new Date(0);
  const dateB = b.data.pubDate || new Date(0);
  return dateB.getTime() - dateA.getTime();
});

const sortedNormal = normalPosts.sort((a, b) =&gt; {
  const dateA = a.data.pubDate || new Date(0);
  const dateB = b.data.pubDate || new Date(0);
  return dateB.getTime() - dateA.getTime();
});

const posts = [...sortedPinned, ...sortedNormal];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;视觉标识：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;#x3C;time class=&quot;date&quot;&gt;
  {post.data.pubDate?.toLocaleDateString(&apos;zh-CN&apos;)}
  {post.data.pinned &amp;#x26;&amp;#x26; (
    &amp;#x3C;span class=&quot;pinned-badge&quot; title=&quot;置顶&quot;&gt;📌&amp;#x3C;/span&gt;
  )}
&amp;#x3C;/time&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;CSS 样式：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.post.pinned .date {
  color: var(--accent);
  font-weight: 500;
}

.post.pinned .pinned-badge {
  display: inline-block;
  margin-left: 0.5rem;
  font-size: 14px;
}

.post.pinned .date::after {
  content: &apos;• 置顶&apos;;
  display: block;
  color: var(--accent);
  font-size: 10px;
  margin-top: 2px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日期高亮（主题色）&lt;/li&gt;
&lt;li&gt;图钉图标 📌&lt;/li&gt;
&lt;li&gt;&quot;• 置顶&quot; 文字说明&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4.3 排序逻辑抽象化&lt;/h3&gt;
&lt;h4&gt;问题&lt;/h4&gt;
&lt;p&gt;排序代码在首页、博客列表页重复，维护成本高。&lt;/p&gt;
&lt;h4&gt;解决方案&lt;/h4&gt;
&lt;p&gt;创建工具函数库，统一排序逻辑。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/utils/posts.ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export interface BlogPost {
  data: {
    pubDate?: Date;
    pinned?: boolean;
    [key: string]: unknown;
  };
  slug: string;
  [key: string]: unknown;
}

/**
 * 按置顶和日期排序文章
 */
export function sortPostsByPinnedAndDate(posts: BlogPost[]): BlogPost[] {
  const pinnedPosts = posts.filter(post =&gt; post.data.pinned);
  const normalPosts = posts.filter(post =&gt; !post.data.pinned);

  const sortedPinned = pinnedPosts.sort((a, b) =&gt; {
    const dateA = a.data.pubDate || new Date(0);
    const dateB = b.data.pubDate || new Date(0);
    return dateB.getTime() - dateA.getTime();
  });

  const sortedNormal = normalPosts.sort((a, b) =&gt; {
    const dateA = a.data.pubDate || new Date(0);
    const dateB = b.data.pubDate || new Date(0);
    return dateB.getTime() - dateA.getTime();
  });

  return [...sortedPinned, ...sortedNormal];
}

/**
 * 按年月分组文章
 */
export function groupPostsByYearMonth(posts: BlogPost[]): Record&amp;#x3C;string, BlogPost[]&gt; {
  const groups: Record&amp;#x3C;string, BlogPost[]&gt; = {};

  posts.forEach(post =&gt; {
    if (!post.data.pubDate) {
      if (!groups[&apos;未发布&apos;]) groups[&apos;未发布&apos;] = [];
      groups[&apos;未发布&apos;].push(post);
      return;
    }

    const year = post.data.pubDate.getFullYear();
    const month = String(post.data.pubDate.getMonth() + 1).padStart(2, &apos;0&apos;);
    const key = `${year}-${month}`;

    if (!groups[key]) groups[key] = [];
    groups[key].push(post);
  });

  return groups;
}

/**
 * 获取年月显示文本
 */
export function getYearMonthLabel(key: string): string {
  if (key === &apos;未发布&apos;) return &apos;未发布&apos;;
  const [year, month] = key.split(&apos;-&apos;);
  return `${year}年${parseInt(month)}月`;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;复用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/pages/index.astro&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;import { sortPostsByPinnedAndDate } from &apos;../utils/posts&apos;;

const allPostsRaw = await getCollection(&apos;blog&apos;);
const allPosts = sortPostsByPinnedAndDate(allPostsRaw);
const latestPosts = allPosts.slice(0, 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码复用，减少重复&lt;/li&gt;
&lt;li&gt;统一排序逻辑，避免不一致&lt;/li&gt;
&lt;li&gt;类型安全（TypeScript）&lt;/li&gt;
&lt;li&gt;易于测试和维护&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4.4 时间线组件&lt;/h3&gt;
&lt;h4&gt;需求&lt;/h4&gt;
&lt;p&gt;在博客列表页右侧添加时间线，按年月分组展示文章，支持滚动跟随和折叠。&lt;/p&gt;
&lt;h4&gt;设计要点&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;布局&lt;/strong&gt;：右侧固定宽度（280px），不挤占列表空间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定位&lt;/strong&gt;：&lt;code&gt;position: sticky&lt;/code&gt;，滚动时跟随&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分组&lt;/strong&gt;：按 &lt;code&gt;2026 年 5 月&lt;/code&gt; 格式分组，显示文章数量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交互&lt;/strong&gt;：可折叠，悬停效果，置顶标识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应式&lt;/strong&gt;：移动端隐藏&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;实现&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;src/components/Timeline.astro&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;---
import { sortPostsByPinnedAndDate, groupPostsByYearMonth, getYearMonthLabel } from &apos;../utils/posts&apos;;

interface Props {
  posts: {
    data: {
      pubDate?: Date;
      title: string;
      pinned?: boolean;
    };
    slug: string;
  }[];
}

const { posts } = Astro.props;
const sortedPosts = sortPostsByPinnedAndDate(posts);
const groupedPosts = groupPostsByYearMonth(sortedPosts);

const yearMonths = Object.keys(groupedPosts).sort((a, b) =&gt; {
  if (a === &apos;未发布&apos; || b === &apos;未发布&apos;) return a === &apos;未发布&apos; ? 1 : -1;
  return b.localeCompare(a);
});
---

&amp;#x3C;aside class=&quot;timeline&quot;&gt;
  &amp;#x3C;div class=&quot;timeline-header&quot;&gt;
    &amp;#x3C;h3&gt;时间线&amp;#x3C;/h3&gt;
    &amp;#x3C;div class=&quot;timeline-toggle&quot; title=&quot;收起/展开&quot;&gt;
      &amp;#x3C;svg width=&quot;16&quot; height=&quot;16&quot; viewBox=&quot;0 0 16 16&quot; fill=&quot;none&quot;&gt;
        &amp;#x3C;path d=&quot;M4 6L8 10L12 6&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; stroke-linecap=&quot;round&quot;/&gt;
      &amp;#x3C;/svg&gt;
    &amp;#x3C;/div&gt;
  &amp;#x3C;/div&gt;

  &amp;#x3C;div class=&quot;timeline-content&quot;&gt;
    {yearMonths.map((yearMonth) =&gt; (
      &amp;#x3C;div class=&quot;timeline-group&quot;&gt;
        &amp;#x3C;div class=&quot;timeline-year&quot;&gt;
          &amp;#x3C;span class=&quot;year-label&quot;&gt;{getYearMonthLabel(yearMonth)}&amp;#x3C;/span&gt;
          &amp;#x3C;span class=&quot;year-count&quot;&gt;({groupedPosts[yearMonth].length})&amp;#x3C;/span&gt;
        &amp;#x3C;/div&gt;
        
        &amp;#x3C;ul class=&quot;timeline-list&quot;&gt;
          {groupedPosts[yearMonth].map((post) =&gt; (
            &amp;#x3C;li class={`timeline-item ${post.data.pinned ? &apos;pinned&apos; : &apos;&apos;}`}&gt;
              &amp;#x3C;div class=&quot;timeline-dot&quot;&gt;&amp;#x3C;/div&gt;
              &amp;#x3C;div class=&quot;timeline-content-item&quot;&gt;
                &amp;#x3C;time class=&quot;timeline-date&quot;&gt;
                  {post.data.pubDate?.toLocaleDateString(&apos;zh-CN&apos;, { month: &apos;numeric&apos;, day: &apos;numeric&apos; })}
                &amp;#x3C;/time&gt;
                &amp;#x3C;a href={`/blog/${post.slug}`} class=&quot;timeline-title&quot;&gt;
                  {post.data.title}
                  {post.data.pinned &amp;#x26;&amp;#x26; &amp;#x3C;span class=&quot;pinned-icon&quot;&gt;📌&amp;#x3C;/span&gt;}
                &amp;#x3C;/a&gt;
              &amp;#x3C;/div&gt;
            &amp;#x3C;/li&gt;
          ))}
        &amp;#x3C;/ul&gt;
      &amp;#x3C;/div&gt;
    ))}
  &amp;#x3C;/div&gt;
&amp;#x3C;/aside&gt;

&amp;#x3C;style&gt;
  .timeline {
    position: sticky;
    top: 5rem;
    max-height: calc(100vh - 6rem);
    overflow-y: auto;
    border-left: 1px solid var(--border);
    padding-left: 1.5rem;
    margin-left: 2rem;
  }

  .timeline-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 0;
    border-bottom: 1px solid var(--border);
  }

  .timeline-content.collapsed {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
  }

  .timeline-item {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.5rem 0;
    transition: transform 0.2s;
  }

  .timeline-item:hover {
    transform: translateX(4px);
  }

  .timeline-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-muted);
    margin-top: 0.5rem;
    transition: all 0.2s;
  }

  .timeline-item:hover .timeline-dot {
    background: var(--accent);
    transform: scale(1.3);
  }

  @media (max-width: 768px) {
    .timeline {
      display: none;
    }
  }
&amp;#x3C;/style&gt;

&amp;#x3C;script is:inline&gt;
  (function() {
    const toggle = document.querySelector(&apos;.timeline-toggle&apos;);
    const content = document.querySelector(&apos;.timeline-content&apos;);
    
    if (toggle &amp;#x26;&amp;#x26; content) {
      toggle.addEventListener(&apos;click&apos;, () =&gt; {
        content.classList.toggle(&apos;collapsed&apos;);
        const svg = toggle.querySelector(&apos;svg path&apos;);
        if (svg) {
          const isCollapsed = content.classList.contains(&apos;collapsed&apos;);
          svg.setAttribute(&apos;d&apos;, isCollapsed ? &apos;M4 10L8 6L12 10&apos; : &apos;M4 6L8 10L12 6&apos;);
        }
      });
    }
  })();
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;布局集成：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/pages/blog.astro&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;#x3C;section class=&quot;sec&quot;&gt;
  &amp;#x3C;div class=&quot;posts-container&quot;&gt;
    &amp;#x3C;ol class=&quot;posts&quot; id=&quot;posts-list&quot;&gt;
      &amp;#x3C;!-- 文章列表 --&gt;
    &amp;#x3C;/ol&gt;
    &amp;#x3C;Timeline posts={posts} /&gt;
  &amp;#x3C;/div&gt;
&amp;#x3C;/section&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/styles/global.css&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.posts-container {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 2rem;
  align-items: start;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;交互效果：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;悬停时文章右移 4px&lt;/li&gt;
&lt;li&gt;圆点放大并高亮&lt;/li&gt;
&lt;li&gt;置顶文章圆点始终高亮&lt;/li&gt;
&lt;li&gt;点击标题收起/展开&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;五、部署与优化&lt;/h2&gt;
&lt;h3&gt;5.1 Cloudflare Pages 部署&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构建项目&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 Cloudflare Pages&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接 GitHub 仓库&lt;/li&gt;
&lt;li&gt;构建命令：&lt;code&gt;npm run build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;输出目录：&lt;code&gt;dist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;框架预设：Astro&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义域名&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 Cloudflare Dashboard 添加自定义域名&lt;/li&gt;
&lt;li&gt;自动配置 HTTPS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;5.2 性能优化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;构建优化：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 SSG（静态生成），避免 SSR&lt;/li&gt;
&lt;li&gt;图片懒加载&lt;/li&gt;
&lt;li&gt;字体子集化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;运行时优化：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;零 JavaScript 输出（按需 hydration）&lt;/li&gt;
&lt;li&gt;客户端筛选使用原生 DOM 操作，无框架依赖&lt;/li&gt;
&lt;li&gt;避免重排重绘，使用 &lt;code&gt;transform&lt;/code&gt; 动画&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 SEO 优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;语义化 HTML 标签（&lt;code&gt;&amp;#x3C;article&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;time&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;aside&gt;&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;Meta 标签完整（title, description, Open Graph）&lt;/li&gt;
&lt;li&gt;结构化数据（JSON-LD）&lt;/li&gt;
&lt;li&gt;站点地图（sitemap.xml）&lt;/li&gt;
&lt;li&gt;RSS 订阅&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;六、最佳实践总结&lt;/h2&gt;
&lt;h3&gt;6.1 代码组织&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工具函数抽象&lt;/strong&gt;：排序、分组、格式化等通用逻辑放入 &lt;code&gt;src/utils/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组件复用&lt;/strong&gt;：时间线、Tag 等可复用 UI 放入 &lt;code&gt;src/components/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型安全&lt;/strong&gt;：使用 TypeScript，定义清晰的接口&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 内容管理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontmatter 规范&lt;/strong&gt;：统一字段命名和格式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签命名&lt;/strong&gt;：使用有意义的关键词，避免过长&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;置顶策略&lt;/strong&gt;：仅对重要文章使用 &lt;code&gt;pinned: true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.3 交互设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无刷新筛选&lt;/strong&gt;：使用 &lt;code&gt;history.pushState&lt;/code&gt; 更新 URL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;浏览器导航&lt;/strong&gt;：监听 &lt;code&gt;popstate&lt;/code&gt; 事件支持前进后退&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无障碍&lt;/strong&gt;：添加 &lt;code&gt;aria-label&lt;/code&gt;，键盘可访问&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.4 性能优先&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免框架依赖&lt;/strong&gt;：客户端交互使用原生 JavaScript&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少重排&lt;/strong&gt;：使用 &lt;code&gt;transform&lt;/code&gt; 而非 &lt;code&gt;top/left&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按需加载&lt;/strong&gt;：图片、字体懒加载&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;七、后续扩展&lt;/h2&gt;
&lt;h3&gt;已实现功能&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[x] 评论系统（Giscus）&lt;/li&gt;
&lt;li&gt;[x] 全文搜索（FlexSearch）&lt;/li&gt;
&lt;li&gt;[x] RSS 订阅&lt;/li&gt;
&lt;li&gt;[x] Tag 云图&lt;/li&gt;
&lt;li&gt;[x] 相关文章推荐&lt;/li&gt;
&lt;li&gt;[x] 时间线导航&lt;/li&gt;
&lt;li&gt;[x] 暗黑模式（浅色/深色/跟随系统）&lt;/li&gt;
&lt;li&gt;[x] 标签筛选&lt;/li&gt;
&lt;li&gt;[x] 置顶文章&lt;/li&gt;
&lt;li&gt;[x] 自定义面板（主题/字体/密度）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;待实现功能&lt;/h3&gt;
&lt;h4&gt;用户体验&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;[x] &lt;strong&gt;键盘导航&lt;/strong&gt; - &lt;code&gt;j/k&lt;/code&gt; 切换文章，&lt;code&gt;/&lt;/code&gt; 聚焦搜索，&lt;code&gt;Esc&lt;/code&gt; 关闭弹窗 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;图片懒加载 + 缩略图&lt;/strong&gt; - &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; + 响应式 &lt;code&gt;srcset&lt;/code&gt; ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;代码块复制按钮&lt;/strong&gt; - 一键复制代码，带 Toast 反馈 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;阅读进度条&lt;/strong&gt; - 页面顶部显示阅读进度 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;目录导航（TOC）&lt;/strong&gt; - 左侧章节目录，滚动高亮 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;阅读时长估算&lt;/strong&gt; - 根据字数估算阅读时间 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;回到顶部按钮&lt;/strong&gt; - 滚动 300px 后显示 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;分享功能&lt;/strong&gt; - 微信/微博/Twitter/复制链接/二维码 ✅ 2026-05-21&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;性能优化&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;[x] &lt;strong&gt;性能优化&lt;/strong&gt; - Lighthouse 评分目标（性能 95+，SEO 100） ✅ 2026-05-21
&lt;ul&gt;
&lt;li&gt;图片优化：使用 &lt;code&gt;astro/assets&lt;/code&gt;，WebP 转换，响应式图片&lt;/li&gt;
&lt;li&gt;预加载关键资源：&lt;code&gt;&amp;#x3C;link rel=&quot;preconnect&quot;&gt;&lt;/code&gt; + &lt;code&gt;&amp;#x3C;link rel=&quot;preload&quot;&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;减少重排：使用 &lt;code&gt;transform&lt;/code&gt; 而非 &lt;code&gt;top/left&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;字体优化&lt;/strong&gt; - &lt;code&gt;font-display: swap&lt;/code&gt; 防止 FOIT，子集化，本地托管 ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;缓存策略&lt;/strong&gt; - Service Worker + HTTP 缓存头优化 ✅ 2026-05-21&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;SEO 与可访问性&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;SEO 优化&lt;/strong&gt; - 结构化数据（JSON-LD），Open Graph 元标签
&lt;ul&gt;
&lt;li&gt;BlogPosting 结构化数据&lt;/li&gt;
&lt;li&gt;Twitter Cards&lt;/li&gt;
&lt;li&gt;站点地图（sitemap.xml）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;无障碍增强&lt;/strong&gt; - &lt;code&gt;skip to main&lt;/code&gt; 链接，焦点可见性，ARIA 标签&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;高级功能&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;PWA 支持&lt;/strong&gt; - 离线访问，添加到主屏幕&lt;/li&gt;
&lt;li&gt;[x] &lt;strong&gt;多语言切换&lt;/strong&gt; - 中英文切换（i18n） ✅ 2026-05-21&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;网站统计&lt;/strong&gt; - Umami / Plausible（隐私友好）&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;邮件订阅&lt;/strong&gt; - Newsletter 新文章推送&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;更新时间&lt;/strong&gt;: 2026-05-14&lt;br&gt;
&lt;strong&gt;阅读时间&lt;/strong&gt;: 12 分钟&lt;/p&gt;
</content:encoded><category>Astro</category><category>TailwindCSS</category></item></channel></rss>