[{"content":"LangChain和LangGraph 特性 LangChain LangGraph 诞生背景 解决LLM应用开发的零散、重复问题 解决LangChain复杂智能体自定义难、扩展难、生产部署难的问题 核心理念 模块化、可组合的链 “图即工作流”，低抽象、强控制的状态机 核心组件 Components, Chains, LCEL Nodes, Edges, StateGraph, Checkpointer 执行模式 线性串联为主，LLM自主决策为辅 有向图驱动，支持循环、分支与并行处理 生态系统 丰富的社区集成，庞大的工具库 原生LangChain集成，可独立运行 设计目标 快速开发、快速原型 生产可靠、可观测、易扩展 Deep Agents是一个基于LangGraph的代理工具：规划、子代理、文件系统工具和上下文管理。 LangChain是代理框架：模型、工具和代理循环的抽象和集成。 LangGraph是编排运行时：持久执行、流、人在循环和持久性。 LangSmith是用于跨框架跟踪、评估、提示和部署的平台。 Function Calling（函数调用/工具调用） 是大语言模型（LLM）的一项核心能力——它允许模型在生成回复时，不直接输出文本，而是输出一个 结构化的函数调用请求（通常是 JSON），由开发者实际执行该函数，然后将执行结果返回给模型，模型再据此生成最终回复。\n注：本文根据吴恩达AI Agent课程学习整理，模型限制未使用chatgpt，用Deekseek替换。 基于react模式的智能代理 代码\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 import openai import re import httpx import os from dotenv import load_dotenv _ = load_dotenv() from openai import OpenAI # ================= 改动开始 ================= # 使用 DeepSeek 的 API 地址和你的 API Key # 建议在 .env 文件中设置 DEEPSEEK_API_KEY DEEPSEEK_API_KEY = os.getenv(\u0026#34;DEEPSEEK_API_KEY\u0026#34;) # 如果没有设置，会读取 OPENAI_API_KEY 兼容 if not DEEPSEEK_API_KEY: raise ValueError(\u0026#34;请在环境变量中设置 DEEPSEEK_API_KEY\u0026#34;) client = OpenAI( api_key=DEEPSEEK_API_KEY, base_url=\u0026#34;https://api.deepseek.com/v1\u0026#34; # DeepSeek 官方 API 地址 ) # ================= 改动结束 ================= # 测试调用（可选） chat_completion = client.chat.completions.create( model=\u0026#34;deepseek-chat\u0026#34;, # 使用 DeepSeek 模型 messages=[{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Hello world\u0026#34;}] ) print(chat_completion.choices[0].message.content) class Agent: def __init__(self, system=\u0026#34;\u0026#34;): self.system = system self.messages = [] if self.system: self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: system}) def __call__(self, message): self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: message}) result = self.execute() self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: result}) return result def execute(self): completion = client.chat.completions.create( model=\u0026#34;deepseek-chat\u0026#34;, # 统一使用 DeepSeek 模型 temperature=0, messages=self.messages ) return completion.choices[0].message.content prompt = \u0026#34;\u0026#34;\u0026#34; You run in a loop of Thought, Action, PAUSE, Observation. At the end of the loop you output an Answer Use Thought to describe your thoughts about the question you have been asked. Use Action to run one of the actions available to you - then return PAUSE. Observation will be the result of running those actions. Your available actions are: calculate: e.g. calculate: 4 * 7 / 3 Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary average_dog_weight: e.g. average_dog_weight: Collie returns average weight of a dog when given the breed Example session: Question: How much does a Bulldog weigh? Thought: I should look the dogs weight using average_dog_weight Action: average_dog_weight: Bulldog PAUSE You will be called again with this: Observation: A Bulldog weights 51 lbs You then output: Answer: A bulldog weights 51 lbs \u0026#34;\u0026#34;\u0026#34;.strip() def calculate(what): return eval(what) def average_dog_weight(name): if name in \u0026#34;Scottish Terrier\u0026#34;: return(\u0026#34;Scottish Terriers average 20 lbs\u0026#34;) elif name in \u0026#34;Border Collie\u0026#34;: return(\u0026#34;a Border Collies average weight is 37 lbs\u0026#34;) elif name in \u0026#34;Toy Poodle\u0026#34;: return(\u0026#34;a toy poodles average weight is 7 lbs\u0026#34;) else: return(\u0026#34;An average dog weights 50 lbs\u0026#34;) known_actions = { \u0026#34;calculate\u0026#34;: calculate, \u0026#34;average_dog_weight\u0026#34;: average_dog_weight } abot = Agent(prompt) result = abot(\u0026#34;How much does a toy poodle weigh?\u0026#34;) print(result) result = average_dog_weight(\u0026#34;Toy Poodle\u0026#34;) result next_prompt = \u0026#34;Observation: {}\u0026#34;.format(result) abot(next_prompt) abot.messages abot = Agent(prompt) question = \u0026#34;\u0026#34;\u0026#34;I have 2 dogs, a border collie and a scottish terrier. \\ What is their combined weight\u0026#34;\u0026#34;\u0026#34; abot(question) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(average_dog_weight(\u0026#34;Border Collie\u0026#34;)) print(next_prompt) abot(next_prompt) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(average_dog_weight(\u0026#34;Scottish Terrier\u0026#34;)) print(next_prompt) abot(next_prompt) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(eval(\u0026#34;37 + 20\u0026#34;)) print(next_prompt) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(eval(\u0026#34;37 + 20\u0026#34;)) print(next_prompt) abot(next_prompt) action_re = re.compile(\u0026#39;^Action: (\\w+): (.*)$\u0026#39;) # python regular expression to selection action def query(question, max_turns=5): i = 0 bot = Agent(prompt) next_prompt = question while i \u0026lt; max_turns: i += 1 result = bot(next_prompt) print(result) actions = [ action_re.match(a) for a in result.split(\u0026#39;\\n\u0026#39;) if action_re.match(a) ] if actions: # There is an action to run action, action_input = actions[0].groups() if action not in known_actions: raise Exception(\u0026#34;Unknown action: {}: {}\u0026#34;.format(action, action_input)) print(\u0026#34; -- running {} {}\u0026#34;.format(action, action_input)) observation = known_actions[action](action_input) print(\u0026#34;Observation:\u0026#34;, observation) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(observation) else: return question = \u0026#34;\u0026#34;\u0026#34;I have 2 dogs, a border collie and a scottish terrier. \\ What is their combined weight\u0026#34;\u0026#34;\u0026#34; query(question) 这段代码实现了一个基于大语言模型的 ReAct 风格智能代理，能够通过“思考-行动-观察”循环来解决需要多步推理或工具调用的问题。它使用了 DeepSeek 模型（兼容 OpenAI API），并内置了两个简单工具：数学计算和查询狗的平均体重。\n下面分段详细解释代码的功能和工作原理。\n1. 环境准备与 API 客户端初始化 python\n1 2 3 4 5 6 7 8 9 10 from dotenv import load_dotenv _ = load_dotenv() from openai import OpenAI DEEPSEEK_API_KEY = os.getenv(\u0026#34;DEEPSEEK_API_KEY\u0026#34;) if not DEEPSEEK_API_KEY: raise ValueError(\u0026#34;请在环境变量中设置 DEEPSEEK_API_KEY\u0026#34;) client = OpenAI( api_key=DEEPSEEK_API_KEY, base_url=\u0026#34;https://api.deepseek.com/v1\u0026#34; ) 从 .env 文件加载环境变量，获取 DeepSeek 的 API 密钥。\n创建 OpenAI 客户端，并指定 base_url 为 DeepSeek 官方 API 地址。\n后续所有对大模型的请求都通过该客户端发送。\n2. Agent 类：对话管理与模型调用 python\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Agent: def __init__(self, system=\u0026#34;\u0026#34;): self.system = system self.messages = [] if self.system: self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: system}) def __call__(self, message): self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: message}) result = self.execute() self.messages.append({\u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: result}) return result def execute(self): completion = client.chat.completions.create( model=\u0026#34;deepseek-chat\u0026#34;, temperature=0, messages=self.messages ) return completion.choices[0].message.content Agent 维护一个对话历史 self.messages。\n初始化时可以设置系统提示（system prompt）。\n调用实例（__call__）时：\n将用户消息加入 messages。\n调用 execute() 发送当前完整消息列表给模型。\n将模型的回复加入 messages。\n返回回复内容。\ntemperature=0 使输出尽可能确定，利于解析 Action 格式。\n3. 系统 Prompt：定义 ReAct 行为模式 python\n1 2 3 4 prompt = \u0026#34;\u0026#34;\u0026#34; You run in a loop of Thought, Action, PAUSE, Observation... ...（详细内容） \u0026#34;\u0026#34;\u0026#34;.strip() 这个 prompt 告诉模型：\n使用 Thought 描述推理过程。\n使用 Action 选择一个可用工具并给出参数，然后输出 PAUSE。\n之后用户会提供一个 Observation（工具执行结果）。\n模型根据 Observation 继续思考，最终输出 Answer。\n可用工具：\ncalculate：计算数学表达式（用 Python 的 eval）。\naverage_dog_weight：根据狗品种返回平均体重。\n示例会话展示了输入输出格式，保证模型按规则生成。\n4. 工具函数与动作映射 python\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def calculate(what): return eval(what) def average_dog_weight(name): if name in \u0026#34;Scottish Terrier\u0026#34;: return(\u0026#34;Scottish Terriers average 20 lbs\u0026#34;) elif name in \u0026#34;Border Collie\u0026#34;: return(\u0026#34;a Border Collies average weight is 37 lbs\u0026#34;) elif name in \u0026#34;Toy Poodle\u0026#34;: return(\u0026#34;a toy poodles average weight is 7 lbs\u0026#34;) else: return(\u0026#34;An average dog weights 50 lbs\u0026#34;) known_actions = { \u0026#34;calculate\u0026#34;: calculate, \u0026#34;average_dog_weight\u0026#34;: average_dog_weight } calculate：直接 eval 字符串（有安全风险，示例可接受）。\naverage_dog_weight：基于品种名返回写死的体重字符串。\nknown_actions：字典，模型输出 Action: 函数名: 参数 时，可映射到实际函数。\n5. 手动演示交互（非自动循环） 在代码中间部分，用户手动演示了如何一步步调用：\npython\n1 2 3 4 5 6 abot = Agent(prompt) result = abot(\u0026#34;How much does a toy poodle weigh?\u0026#34;) print(result) # 模型输出 Action 和 PAUSE result = average_dog_weight(\u0026#34;Toy Poodle\u0026#34;) # 手工执行工具 next_prompt = \u0026#34;Observation: {}\u0026#34;.format(result) abot(next_prompt) # 将 Observation 喂给 Agent 类似地，后面的代码又手动处理了两只狗的体重计算问题：\n先分别调用 average_dog_weight 获取两只狗的体重。\n再手工计算 37 + 20 得到总和。\n每次将 Observation 作为新消息传给 Agent，最终让模型输出 Answer。\n这种手动方式繁琐且容易被中断，因此代码最后提供了一个自动循环函数 query。\n6. 自动循环函数 query python\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 action_re = re.compile(\u0026#39;^Action: (\\w+): (.*)$\u0026#39;) def query(question, max_turns=5): i = 0 bot = Agent(prompt) next_prompt = question while i \u0026lt; max_turns: i += 1 result = bot(next_prompt) print(result) actions = [ action_re.match(a) for a in result.split(\u0026#39;\\n\u0026#39;) if action_re.match(a) ] if actions: action, action_input = actions[0].groups() if action not in known_actions: raise Exception(\u0026#34;Unknown action: {}: {}\u0026#34;.format(action, action_input)) print(\u0026#34; -- running {} {}\u0026#34;.format(action, action_input)) observation = known_actions[action](action_input) print(\u0026#34;Observation:\u0026#34;, observation) next_prompt = \u0026#34;Observation: {}\u0026#34;.format(observation) else: return 工作流程 初始化：创建新的 Agent 实例（传入系统 prompt），next_prompt 初始为用户问题。\n循环（最多 max_turns 次）：\n调用 bot(next_prompt) 得到模型回复 result。\n打印回复内容。\n用正则表达式 ^Action: (\\w+): (.*)$ 逐行匹配，找到所有形如 Action: 动作名: 参数 的行。\n如果有 Action：\n取第一个匹配的 Action。\n验证动作是否在 known_actions 中。\n执行对应的工具函数，得到 observation。\n打印执行信息。\n构造新的 next_prompt = \u0026quot;Observation: \u0026quot; + observation。\n继续循环（模型会看到 Observation 并继续推理）。\n如果没有 Action（例如模型直接输出 Answer）：循环结束，函数返回。\n结束条件：模型不再输出 Action（即输出了 Answer），或达到最大轮数。\n示例调用 python\n1 2 3 question = \u0026#34;\u0026#34;\u0026#34;I have 2 dogs, a border collie and a scottish terrier. \\ What is their combined weight\u0026#34;\u0026#34;\u0026#34; query(question) 运行时会自动：\n模型输出：Action: average_dog_weight: Border Collie → 工具返回 \u0026quot;a Border Collies average weight is 37 lbs\u0026quot; → 作为 Observation 喂回。\n模型继续输出：Action: average_dog_weight: Scottish Terrier → 工具返回 \u0026quot;Scottish Terriers average 20 lbs\u0026quot; → 再喂回。\n模型输出：Action: calculate: 37 + 20 → 工具返回 57 → 喂回。\n模型最终输出：Answer: The combined weight is 57 lbs（没有 Action），循环结束。\n7. 总结：整体设计思想 ReAct 模式：将推理（Thought）与行动（Action）交错，借助外部工具增强 LLM 的能力。\n模型无关：通过 OpenAI 兼容接口，可替换任意支持该 API 的模型（此处用 DeepSeek）。\n工具可扩展：只需在 known_actions 中添加新函数，并更新系统 prompt 中的描述即可。\n自动循环：query 函数实现了完整的“解析 Action → 执行工具 → 反馈 Observation”循环，直到模型给出最终答案。\n这段代码是一个简洁的教学示例，展示了如何用很少的代码构建一个能计算、查表并进行多步推理的智能代理。在实际应用中，可以替换工具函数为更复杂的 API 调用（如搜索引擎、数据库查询等），从而构建出功能更强大的 Agent。\n————————————————————————————————————————\n基于 LangGraph 的 ReAct Agent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 from dotenv import load_dotenv _ = load_dotenv() from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage from langchain_openai import ChatOpenAI from langchain_tavily import TavilySearch import os # ================= DeepSeek 配置开始 ================= # 从环境变量获取 DeepSeek API Key（建议在 .env 中设置 DEEPSEEK_API_KEY） DEEPSEEK_API_KEY = os.getenv(\u0026#34;DEEPSEEK_API_KEY\u0026#34;) if not DEEPSEEK_API_KEY: raise ValueError(\u0026#34;请在环境变量中设置 DEEPSEEK_API_KEY\u0026#34;) # 创建 ChatOpenAI 实例，指向 DeepSeek 的 API 地址 # 注意：模型名称使用 deepseek-chat 或 deepseek-coder deepseek_model = ChatOpenAI( model=\u0026#34;deepseek-chat\u0026#34;, # DeepSeek 对话模型 openai_api_key=DEEPSEEK_API_KEY, # 注意参数名是 openai_api_key（兼容 OpenAI） openai_api_base=\u0026#34;https://api.deepseek.com/v1\u0026#34;, # DeepSeek 官方地址 temperature=0 # 可选：降低随机性 ) # ================= DeepSeek 配置结束 ================= # 搜索工具（与模型无关） tool = TavilySearch(max_results=4) print(type(tool)) print(tool.name) class AgentState(TypedDict): messages: Annotated[list[AnyMessage], operator.add] class Agent: def __init__(self, model, tools, system=\u0026#34;\u0026#34;): self.system = system graph = StateGraph(AgentState) graph.add_node(\u0026#34;llm\u0026#34;, self.call_openai) graph.add_node(\u0026#34;action\u0026#34;, self.take_action) graph.add_conditional_edges( \u0026#34;llm\u0026#34;, self.exists_action, {True: \u0026#34;action\u0026#34;, False: END} ) graph.add_edge(\u0026#34;action\u0026#34;, \u0026#34;llm\u0026#34;) graph.set_entry_point(\u0026#34;llm\u0026#34;) self.graph = graph.compile() self.tools = {t.name: t for t in tools} # 模型绑定工具（同样兼容 DeepSeek，因为 DeepSeek 支持 function calling） self.model = model.bind_tools(tools) def exists_action(self, state: AgentState): result = state[\u0026#39;messages\u0026#39;][-1] return len(result.tool_calls) \u0026gt; 0 def call_openai(self, state: AgentState): messages = state[\u0026#39;messages\u0026#39;] if self.system: messages = [SystemMessage(content=self.system)] + messages message = self.model.invoke(messages) return {\u0026#39;messages\u0026#39;: [message]} def take_action(self, state: AgentState): tool_calls = state[\u0026#39;messages\u0026#39;][-1].tool_calls results = [] for t in tool_calls: print(f\u0026#34;Calling: {t}\u0026#34;) if not t[\u0026#39;name\u0026#39;] in self.tools: print(\u0026#34;\\n ....bad tool name....\u0026#34;) result = \u0026#34;bad tool name, retry\u0026#34; else: result = self.tools[t[\u0026#39;name\u0026#39;]].invoke(t[\u0026#39;args\u0026#39;]) results.append(ToolMessage(tool_call_id=t[\u0026#39;id\u0026#39;], name=t[\u0026#39;name\u0026#39;], content=str(result))) print(\u0026#34;Back to the model!\u0026#34;) return {\u0026#39;messages\u0026#39;: results} # 系统提示（可根据 DeepSeek 调整，但原 prompt 也适用） prompt = \u0026#34;\u0026#34;\u0026#34;You are a smart research assistant. Use the search engine to look up information. \\ You are allowed to make multiple calls (either together or in sequence). \\ Only look up information when you are sure of what you want. \\ If you need to look up some information before asking a follow up question, you are allowed to do that! \u0026#34;\u0026#34;\u0026#34; # ================= 使用 DeepSeek 模型创建 Agent ================= # 对于简单任务（如天气查询），使用 deepseek-chat 即可 abot = Agent(deepseek_model, [tool], system=prompt) # 可选：可视化图结构（需要 IPython，如果没有可注释） # from IPython.display import Image # Image(abot.graph.get_graph().draw_png()) # 测试1：单个城市天气 messages = [HumanMessage(content=\u0026#34;What is the weather in sf?\u0026#34;)] result = abot.graph.invoke({\u0026#34;messages\u0026#34;: messages}) print(\u0026#34;Final answer:\u0026#34;, result[\u0026#39;messages\u0026#39;][-1].content) # 测试2：两个城市天气 messages = [HumanMessage(content=\u0026#34;What is the weather in SF and LA?\u0026#34;)] result = abot.graph.invoke({\u0026#34;messages\u0026#34;: messages}) print(\u0026#34;Final answer:\u0026#34;, result[\u0026#39;messages\u0026#39;][-1].content) # 测试3：多步推理问题（需要更好性能，仍用 deepseek-chat，也可以用 deepseek-coder） query = \u0026#34;Who won the super bowl in 2024? In what state is the winning team headquarters located? \\ What is the GDP of that state? Answer each question.\u0026#34; messages = [HumanMessage(content=query)] # 这里如果希望使用更强的模型，DeepSeek 目前主要提供 deepseek-chat，已经足够 abot = Agent(deepseek_model, [tool], system=prompt) result = abot.graph.invoke({\u0026#34;messages\u0026#34;: messages}) print(\u0026#34;Multi-step answer:\u0026#34;, result[\u0026#39;messages\u0026#39;][-1].content) 1. 代码整体架构 你实现了一个基于 LangGraph 的 ReAct Agent，它能够循环地进行“思考→调用工具→观察结果→继续思考”的过程。核心组件包括：\nStateGraph：定义 Agent 的状态和流程。\nAgentState：存储消息列表（使用 operator.add 作为 reducer，自动累加新消息）。\nAgent 类：封装了图构建、模型调用、工具执行逻辑。\n外部工具：TavilySearch（搜索互联网）。\n大模型：DeepSeek Chat（通过 ChatOpenAI 接口调用，支持 function calling）。\n2. 流程图与节点 text\nllm (调用模型) → exists_action (判断是否有工具调用) → 如果有 → action (执行工具) → 回到 llm → 如果没有 → END (结束)\ncall_openai：调用模型，传入系统提示和对话历史。模型可以选择返回普通文本或请求工具调用。\nexists_action：检查模型返回的消息中是否包含 tool_calls。\ntake_action：遍历所有工具调用，执行对应的工具函数（TavilySearch），将结果包装成 ToolMessage 返回。\n由于 graph.add_edge(\u0026quot;action\u0026quot;, \u0026quot;llm\u0026quot;)，工具执行完后会自动再次调用模型，让模型根据工具结果继续推理。\n3. DeepSeek 配置兼容性 python\n1 2 3 4 5 6 deepseek_model = ChatOpenAI( model=\u0026#34;deepseek-chat\u0026#34;, openai_api_key=DEEPSEEK_API_KEY, openai_api_base=\u0026#34;https://api.deepseek.com/v1\u0026#34;, temperature=0 ) DeepSeek 完全兼容 OpenAI 的 function calling API，因此 model.bind_tools(tools) 可以正常工作。\n使用 openai_api_base 而不是 base_url，这是 langchain_openai 的旧参数名，但依然有效。\n4. 运行结果逐项分析 测试1：单个城市天气 输入：\u0026quot;What is the weather in sf?\u0026quot;\n执行过程：\n模型调用一次，返回 tool_calls，请求 tavily_search，参数 query: 'current weather in San Francisco'。\nAgent 执行搜索工具，得到天气结果（温度55°F，多云等）。\n模型再次被调用，结合搜索结果生成最终回答。\n输出：\ntext\nHere\u0026rsquo;s the current weather in San Francisco (as of May 7, 2026, ~11:38 PM local time)\u0026hellip; 模型没有继续请求工具，因此流程结束。\n测试2：两个城市天气 输入：\u0026quot;What is the weather in SF and LA?\u0026quot;\n执行过程：\n模型一次返回了两个 tool_calls（并行请求）： 搜索 San Francisco 天气 搜索 Los Angeles 天气 take_action 遍历这两个调用，依次执行（串行）。 得到两个观察结果后，模型生成对比回答。 输出： text Here\u0026rsquo;s the current weather for both cities (as of late evening on May 7, 2026)\u0026hellip; 模型正确合并了两个来源的信息。\n测试3：多步推理问题 输入：\ntext Who won the super bowl in 2024? In what state is the winning team headquarters located? What is the GDP of that state? Answer each question.\n执行过程（从日志可见多轮交互）：\n第一轮：模型请求搜索 Who won the Super Bowl in 2024? → 得到结果 “Kansas City Chiefs”。 第二轮：模型根据上一步结果，请求搜索 Kansas City Chiefs headquarters location state → 得到 “Missouri”。 第三轮：同时请求搜索 GDP of Missouri 2024 和再次确认总部位置（避免错误）→ 得到 GDP 数据。 第四轮：模型整理答案，不再请求工具，输出最终答案。 输出： text\nWho won the Super Bowl in 2024? The Kansas City Chiefs won Super Bowl LVIII\u0026hellip; In what state\u0026hellip;? Missouri. What is the GDP\u0026hellip;? ~$356.65 billion (real GDP) or ~$448.7 billion (nominal). 值得注意：模型能够根据中间结果决定下一步需要查询什么，体现了 ReAct 的多步推理能力。\n5. 警告信息解读 警告1 text\nLangChainPendingDeprecationWarning: The default value of allowed_objects will change in a future version\u0026hellip;\n来源：langgraph.cache.base 内部使用 JsonPlusSerializer 时产生的警告。 影响：目前不影响运行，未来 LangGraph 更新可能需要显式设置 allowed_objects 参数。暂时可忽略。 警告2 （未出现在本次运行，但你之前遇到过） 你之前遇到的 TavilySearchResults 弃用警告已经通过使用 langchain_tavily.TavilySearch 解决了。现在没有该警告。\n6. 代码的潜在改进点 尽管代码能正确运行，仍有几个可以优化的地方：\n6.1 工具调用的并行执行 目前 take_action 是串行执行所有工具调用。如果模型一次请求多个工具（如测试2），可以改为并发执行以提高效率：\npython\n1 2 3 4 5 6 from concurrent.futures import ThreadPoolExecutor def take_action(self, state): tool_calls = state[\u0026#39;messages\u0026#39;][-1].tool_calls with ThreadPoolExecutor() as executor: results = list(executor.map(self._invoke_tool, tool_calls)) return {\u0026#39;messages\u0026#39;: results} 6.2 错误处理增强 当工具名称不存在时，只返回字符串 \u0026quot;bad tool name, retry\u0026quot;。可以更明确地引导模型：\npython\n1 result = f\u0026#34;Error: Tool \u0026#39;{t[\u0026#39;name\u0026#39;]}\u0026#39; not found. Available tools: {list(self.tools.keys())}\u0026#34; 6.3 最大循环次数保护 目前没有设置最大迭代次数，如果模型陷入循环（反复请求相同工具），理论上会无限运行。可以在 Agent 中添加计数器：\npython\n1 2 def __init__(self, ..., max_iterations=10): self.max_iterations = max_iterations 并在 call_openai 或图外部控制。\n6.4 环境变量检查 你已经检查了 DEEPSEEK_API_KEY，但 TAVILY_API_KEY 没有显式检查。可以添加：\npython\n1 2 if not os.getenv(\u0026#34;TAVILY_API_KEY\u0026#34;): raise ValueError(\u0026#34;请设置 TAVILY_API_KEY 环境变量\u0026#34;) 6.5 模型选择 deepseek-chat 对于大多数任务足够好。如果任务涉及大量代码或逻辑推理，可以尝试 deepseek-coder。 对于第三步中的 GDP 查询，模型没有进行数值计算而是直接输出了搜索到的结果，符合预期。 7. 总结 成功运行：你的 Agent 能够正确理解问题、调用 Tavily 搜索工具、整合信息并返回答案。\nDeepSeek 兼容性：完全支持 OpenAI function calling，与 LangChain 无缝集成。\nLangGraph 工作流：llm → exists_action → action → llm 循环稳定，能够处理单步和多步推理。\n警告处理：已消除 Tavily 弃用警告，剩余的 LangGraph 内部警告不影响使用。\nAI Search Agentic Search 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 # 导入 dotenv 中的 load_dotenv 函数，用于加载 .env 文件中的环境变量 # pip install python-dotenv tavily-python 提前导入相关python依赖 # 搜索工具初始连接 from dotenv import load_dotenv # 导入 os 模块，用于读取操作系统的环境变量 import os # 从 tavily 库中导入 TavilyClient 类，用于调用 Tavily 搜索 API from tavily import TavilyClient # 调用 load_dotenv()，它会自动查找项目根目录下的 .env 文件，并将其中的键值对加载到环境变量中 load_dotenv() # 从环境变量中获取 TAVILY_API_KEY 的值（即你注册 Tavily 后获得的真实 API Key） # 这里使用 os.getenv()，它等同于 os.environ.get()，如果环境变量不存在则返回 None api_key = os.getenv(\u0026#34;TAVILY_API_KEY\u0026#34;) # 检查 API Key 是否成功读取，如果为空则主动抛出异常，让程序停止并提示错误信息 if not api_key: raise ValueError(\u0026#34;未找到 TAVILY_API_KEY，请检查 .env 文件\u0026#34;) # 使用读取到的 API Key 创建 TavilyClient 对象，该对象负责发起搜索请求 client = TavilyClient(api_key=api_key) # 调用 client.search() 方法向 Tavily API 发起搜索请求 # 参数 \u0026#34;What is in Nvidia\u0026#39;s new Blackwell GPU?\u0026#34; 是查询的问题 # include_answer=True 表示希望 Tavily 在返回结果中直接提供一个总结性的答案（而非只返回原始网页片段） result = client.search(\u0026#34;What is in Nvidia\u0026#39;s new Blackwell GPU?\u0026#34;, include_answer=True) # 从返回的 result 字典中提取 \u0026#34;answer\u0026#34; 字段，并使用 print() 函数将其输出到控制台 # 注意：在 VSCode 中运行 Python 脚本时，必须显式使用 print() 才能看到输出 print(result[\u0026#34;answer\u0026#34;]) Regular Search 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from ddgs import DDGS import requests from bs4 import BeautifulSoup import re city = \u0026#34;Bei Jing\u0026#34; query = f\u0026#34;\u0026#34;\u0026#34; what is your current weather in {city}? Should I travel there today? \u0026#34;\u0026#34;\u0026#34; # ② 代理设置（如果需要）—— 使用字符串而不是字典 proxy = \u0026#34;http://127.0.0.1:7897\u0026#34; # 改成你实际代理地址 ddg = DDGS(proxy=proxy) # ✅ 传入字符串 # 如果没有代理，就用 ddg = DDGS() # def search(query, max_results=3): # # 使用 backend=\u0026#34;html\u0026#34; 或 \u0026#34;lite\u0026#34; # results = ddg.text(query, max_results=max_results, backend=\u0026#34;html\u0026#34;) # return [item[\u0026#34;href\u0026#34;] for item in results] # urls = search(query) # for url in urls: # print(url) def search(query, max_results=6): try: results = ddg.text(query, max_results=max_results) return [i[\u0026#34;href\u0026#34;] for i in results] except Exception as e: print(f\u0026#34;returning previous results due to exception reaching ddg.\u0026#34;) results = [ # cover case where DDG rate limits due to high deeplearning.ai volume \u0026#34;https://weather.com/weather/today/l/USCA0987:1:US\u0026#34;, \u0026#34;https://weather.com/weather/hourbyhour/l/54f9d8baac32496f6b5497b4bf7a277c3e2e6cc5625de69680e6169e7e38e9a8\u0026#34;, ] return results for i in search(query): print(i) def scrape_weather_info(url): \u0026#34;\u0026#34;\u0026#34;Scrape content from the given URL\u0026#34;\u0026#34;\u0026#34; if not url: return \u0026#34;Weather information could not be found.\u0026#34; # fetch data headers = {\u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0\u0026#39;} response = requests.get(url, headers=headers) if response.status_code != 200: return \u0026#34;Failed to retrieve the webpage.\u0026#34; # parse result soup = BeautifulSoup(response.text, \u0026#39;html.parser\u0026#39;) return soup url = search(query)[0] # scrape first wesbsite soup = scrape_weather_info(url) # print(f\u0026#34;Website: {url}\\n\\n\u0026#34;) # print(str(soup.body)[:50000]) weather_data = [] for tag in soup.find_all([\u0026#39;h1\u0026#39;, \u0026#39;h2\u0026#39;, \u0026#39;h3\u0026#39;, \u0026#39;p\u0026#39;]): text = tag.get_text(\u0026#34; \u0026#34;, strip=True) weather_data.append(text) # combine all elements into a single string weather_data = \u0026#34;\\n\u0026#34;.join(weather_data) # remove all spaces from the combined text weather_data = re.sub(r\u0026#39;\\s+\u0026#39;, \u0026#39; \u0026#39;, weather_data) print(f\u0026#34;Website: {url}\\n\\n\u0026#34;) print(weather_data) different from agentic and regular search\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from ddgs import DDGS import requests from bs4 import BeautifulSoup import re from dotenv import load_dotenv import os from tavily import TavilyClient import json from pygments import highlight,lexers,formatters load_dotenv() api_key = os.getenv(\u0026#34;TAVILY_API_KEY\u0026#34;) if not api_key: raise ValueError(\u0026#34;未找到 TAVILY_API_KEY，请检查 .env 文件\u0026#34;) client = TavilyClient(api_key=api_key) city = \u0026#34;Bei Jing\u0026#34; query = f\u0026#34;\u0026#34;\u0026#34; what is your current weather in {city}? Should I travel there today? \u0026#34;\u0026#34;\u0026#34; result = client.search(query,max_results=1) data =result[\u0026#34;results\u0026#34;][0][\u0026#34;content\u0026#34;] # print(data) # parse JSON parsed_json = json.loads(data.replace(\u0026#34;\u0026#39;\u0026#34;, \u0026#39;\u0026#34;\u0026#39;)) # pretty print JSON with syntax highlighting formatted_json = json.dumps(parsed_json, indent=4) colorful_json = highlight(formatted_json, lexers.JsonLexer(), formatters.TerminalFormatter()) print(colorful_json) Persistence and Streaming Persistence\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 \u0026#34;\u0026#34;\u0026#34; 基于 DeepSeek 模型 + Tavily 搜索工具的 LangGraph 智能体示例 支持多轮对话和工具调用，使用内存检查点保存线程状态。 \u0026#34;\u0026#34;\u0026#34; # ======================= 1. 导入依赖 ======================= import os from dotenv import load_dotenv # 加载 .env 文件中的环境变量（API 密钥等） _ = load_dotenv() # LangGraph 核心组件 from langgraph.graph import StateGraph, END from langgraph.checkpoint.memory import MemorySaver # 内存检查点（简便，无需 SQLite） # 类型定义 from typing import TypedDict, Annotated import operator # LangChain 消息类型 from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage # DeepSeek 模型（OpenAI 兼容） from langchain_openai import ChatOpenAI # Tavily 搜索工具（新版包，需安装 langchain-tavily） from langchain_tavily import TavilySearch # 替代已弃用的 TavilySearchResults # ======================= 2. 定义状态结构 ======================= class AgentState(TypedDict): \u0026#34;\u0026#34;\u0026#34;智能体的状态结构，使用 Annotated 实现消息自动累加\u0026#34;\u0026#34;\u0026#34; messages: Annotated[list[AnyMessage], operator.add] # ======================= 3. 初始化工具 ======================= # 创建 Tavily 搜索工具实例，每次返回最多 2 条结果 tool = TavilySearch(max_results=2) # ======================= 4. 定义 Agent 类 ======================= class Agent: \u0026#34;\u0026#34;\u0026#34; 支持工具调用的智能体封装类 使用 LangGraph 构建执行图，包含 LLM 节点和动作节点， 并自动路由：无工具调用则结束，有调用则执行工具后返回 LLM。 \u0026#34;\u0026#34;\u0026#34; def __init__(self, model, tools, checkpointer, system=\u0026#34;\u0026#34;): \u0026#34;\u0026#34;\u0026#34; 参数: model: 绑定工具后的 LLM 模型实例 tools: 工具列表（每个工具应有 name, invoke 等方法） checkpointer: 检查点保存器（例如 MemorySaver） system: 系统提示词（可选） \u0026#34;\u0026#34;\u0026#34; self.system = system # 构建 StateGraph，状态类型为 AgentState graph = StateGraph(AgentState) # 添加两个节点 graph.add_node(\u0026#34;llm\u0026#34;, self.call_openai) # LLM 推理节点 graph.add_node(\u0026#34;action\u0026#34;, self.take_action) # 工具执行节点 # 添加条件边：从 llm 节点出发，根据 exists_action 决定跳转到 action 或 END graph.add_conditional_edges( \u0026#34;llm\u0026#34;, self.exists_action, {True: \u0026#34;action\u0026#34;, False: END} ) # 添加普通边：action 执行完毕后回到 llm 节点 graph.add_edge(\u0026#34;action\u0026#34;, \u0026#34;llm\u0026#34;) # 设置入口点为 llm graph.set_entry_point(\u0026#34;llm\u0026#34;) # 编译图，传入检查点保存器（支持多线程对话记忆） self.graph = graph.compile(checkpointer=checkpointer) # 将工具列表转换为 {工具名: 工具对象} 映射，便于快速调用 self.tools = {t.name: t for t in tools} # 模型绑定工具（使模型能够生成 tool_calls） self.model = model.bind_tools(tools) def call_openai(self, state: AgentState): \u0026#34;\u0026#34;\u0026#34;LLM 节点：调用模型生成回复（可能包含工具调用请求）\u0026#34;\u0026#34;\u0026#34; messages = state[\u0026#39;messages\u0026#39;] # 如果设置了系统提示词，将其插入到消息列表最前面 if self.system: messages = [SystemMessage(content=self.system)] + messages # 调用模型 response = self.model.invoke(messages) # 返回新消息（将被自动添加到状态中的 messages 列表） return {\u0026#39;messages\u0026#39;: [response]} def exists_action(self, state: AgentState): \u0026#34;\u0026#34;\u0026#34;条件判断函数：检查最后一条消息是否包含工具调用\u0026#34;\u0026#34;\u0026#34; last_message = state[\u0026#39;messages\u0026#39;][-1] # 如果有 tool_calls 属性且非空，则返回 True return len(last_message.tool_calls) \u0026gt; 0 def take_action(self, state: AgentState): \u0026#34;\u0026#34;\u0026#34;动作节点：执行工具调用，并将结果封装为 ToolMessage 返回\u0026#34;\u0026#34;\u0026#34; last_message = state[\u0026#39;messages\u0026#39;][-1] tool_calls = last_message.tool_calls results = [] for tool_call in tool_calls: print(f\u0026#34;调用工具: {tool_call}\u0026#34;) # 调试输出 # 根据工具名获取工具实例，并调用其 invoke 方法 tool_result = self.tools[tool_call[\u0026#39;name\u0026#39;]].invoke(tool_call[\u0026#39;args\u0026#39;]) # 构造 ToolMessage，必须包含 tool_call_id 和原工具名 results.append( ToolMessage( tool_call_id=tool_call[\u0026#39;id\u0026#39;], name=tool_call[\u0026#39;name\u0026#39;], content=str(tool_result) ) ) print(\u0026#34;工具执行完毕，返回模型继续推理...\u0026#34;) return {\u0026#39;messages\u0026#39;: results} # ======================= 5. 配置模型和检查点 ======================= # 设置系统提示词（指导智能体合理使用搜索工具） prompt = \u0026#34;\u0026#34;\u0026#34;You are a smart research assistant. Use the search engine to look up information. \\ You are allowed to make multiple calls (either together or in sequence). \\ Only look up information when you are sure of what you want. \\ If you need to look up some information before asking a follow up question, you are allowed to do that! \u0026#34;\u0026#34;\u0026#34; # 创建 DeepSeek 模型实例（使用与 OpenAI 兼容的接口） model = ChatOpenAI( model=\u0026#34;deepseek-chat\u0026#34;, # DeepSeek 对话模型 base_url=\u0026#34;https://api.deepseek.com/v1\u0026#34;, # DeepSeek API 地址 api_key=os.getenv(\u0026#34;DEEPSEEK_API_KEY\u0026#34;) # 从环境变量读取 API Key ) # 创建内存检查点保存器（程序重启后记忆丢失，但无需 SQLite 依赖） memory = MemorySaver() # 实例化智能体 abot = Agent(model, [tool], system=prompt, checkpointer=memory) # ======================= 6. 多线程对话示例 ======================= # 定义线程配置（每个 thread_id 代表一个独立的对话会话） thread_1 = {\u0026#34;configurable\u0026#34;: {\u0026#34;thread_id\u0026#34;: \u0026#34;1\u0026#34;}} thread_2 = {\u0026#34;configurable\u0026#34;: {\u0026#34;thread_id\u0026#34;: \u0026#34;2\u0026#34;}} # ---- 线程1：询问旧金山天气 ---- print(\u0026#34;=== 线程1：询问旧金山天气 ===\u0026#34;) messages = [HumanMessage(content=\u0026#34;What is the weather in sf?\u0026#34;)] for event in abot.graph.stream({\u0026#34;messages\u0026#34;: messages}, thread_1): # 遍历流式输出的事件，打印每个节点的消息 for node_name, node_output in event.items(): print(f\u0026#34;[{node_name}] 输出: {node_output[\u0026#39;messages\u0026#39;][-1].content}\u0026#34;) # ---- 线程1：继续询问洛杉矶天气（利用记忆） ---- print(\u0026#34;\\n=== 线程1：询问洛杉矶天气（同一会话） ===\u0026#34;) messages = [HumanMessage(content=\u0026#34;What about in la?\u0026#34;)] for event in abot.graph.stream({\u0026#34;messages\u0026#34;: messages}, thread_1): for node_name, node_output in event.items(): print(f\u0026#34;[{node_name}] 输出: {node_output[\u0026#39;messages\u0026#39;][-1].content}\u0026#34;) # ---- 线程1：比较两个城市温度 ---- print(\u0026#34;\\n=== 线程1：询问哪个更暖和（同一会话） ===\u0026#34;) messages = [HumanMessage(content=\u0026#34;Which one is warmer?\u0026#34;)] for event in abot.graph.stream({\u0026#34;messages\u0026#34;: messages}, thread_1): for node_name, node_output in event.items(): print(f\u0026#34;[{node_name}] 输出: {node_output[\u0026#39;messages\u0026#39;][-1].content}\u0026#34;) # ---- 线程2：新会话，同样的问题（无上下文记忆） ---- print(\u0026#34;\\n=== 线程2：新会话询问哪个更暖和（无历史） ===\u0026#34;) messages = [HumanMessage(content=\u0026#34;Which one is warmer?\u0026#34;)] for event in abot.graph.stream({\u0026#34;messages\u0026#34;: messages}, thread_2): for node_name, node_output in event.items(): print(f\u0026#34;[{node_name}] 输出: {node_output[\u0026#39;messages\u0026#39;][-1].content}\u0026#34;) Streaming\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 \u0026#34;\u0026#34;\u0026#34; 基于 DeepSeek 模型 + Tavily 搜索工具的 LangGraph 智能体 - 使用异步 SQLite 检查点（支持多线程记忆） - 使用 astream_events 实现流式输出 - 修正导入路径为 langgraph.checkpoint.sqlite.aio \u0026#34;\u0026#34;\u0026#34; import os import asyncio from dotenv import load_dotenv _ = load_dotenv() # 正确的异步 SQLite 检查点导入路径 from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage from langchain_openai import ChatOpenAI from langchain_tavily import TavilySearch # 需安装 langchain-tavily # ======================= 状态定义 ======================= class AgentState(TypedDict): messages: Annotated[list[AnyMessage], operator.add] # ======================= 工具 ======================= tool = TavilySearch(max_results=2) # ======================= Agent 类 ======================= class Agent: def __init__(self, model, tools, checkpointer, system=\u0026#34;\u0026#34;): self.system = system graph = StateGraph(AgentState) graph.add_node(\u0026#34;llm\u0026#34;, self.call_openai) graph.add_node(\u0026#34;action\u0026#34;, self.take_action) graph.add_conditional_edges(\u0026#34;llm\u0026#34;, self.exists_action, {True: \u0026#34;action\u0026#34;, False: END}) graph.add_edge(\u0026#34;action\u0026#34;, \u0026#34;llm\u0026#34;) graph.set_entry_point(\u0026#34;llm\u0026#34;) # 编译时传入有效的 checkpointer 实例 self.graph = graph.compile(checkpointer=checkpointer) self.tools = {t.name: t for t in tools} self.model = model.bind_tools(tools) def call_openai(self, state: AgentState): messages = state[\u0026#39;messages\u0026#39;] if self.system: messages = [SystemMessage(content=self.system)] + messages response = self.model.invoke(messages) return {\u0026#39;messages\u0026#39;: [response]} def exists_action(self, state: AgentState): last_message = state[\u0026#39;messages\u0026#39;][-1] return len(last_message.tool_calls) \u0026gt; 0 def take_action(self, state: AgentState): last_message = state[\u0026#39;messages\u0026#39;][-1] tool_calls = last_message.tool_calls results = [] for t in tool_calls: print(f\u0026#34;调用工具: {t}\u0026#34;) result = self.tools[t[\u0026#39;name\u0026#39;]].invoke(t[\u0026#39;args\u0026#39;]) results.append(ToolMessage(tool_call_id=t[\u0026#39;id\u0026#39;], name=t[\u0026#39;name\u0026#39;], content=str(result))) print(\u0026#34;工具执行完毕，返回模型继续推理...\u0026#34;) return {\u0026#39;messages\u0026#39;: results} # ======================= 系统提示词 ======================= prompt = \u0026#34;\u0026#34;\u0026#34;You are a smart research assistant. Use the search engine to look up information. \\ You are allowed to make multiple calls (either together or in sequence). \\ Only look up information when you are sure of what you want. \\ If you need to look up some information before asking a follow up question, you are allowed to do that! \u0026#34;\u0026#34;\u0026#34; # ======================= 模型配置（DeepSeek） ======================= model = ChatOpenAI( model=\u0026#34;deepseek-chat\u0026#34;, base_url=\u0026#34;https://api.deepseek.com/v1\u0026#34;, api_key=os.getenv(\u0026#34;DEEPSEEK_API_KEY\u0026#34;) ) # ======================= 异步主函数 ======================= async def main(): # 重要：AsyncSqliteSaver.from_conn_string 返回异步上下文管理器， # 必须用 async with 才能获得真正的 checkpointer 实例 async with AsyncSqliteSaver.from_conn_string(\u0026#34;:memory:\u0026#34;) as memory: abot = Agent(model, [tool], system=prompt, checkpointer=memory) thread = {\u0026#34;configurable\u0026#34;: {\u0026#34;thread_id\u0026#34;: \u0026#34;4\u0026#34;}} messages = [HumanMessage(content=\u0026#34;What is the weather in SF?\u0026#34;)] # 流式输出 async for event in abot.graph.astream_events( {\u0026#34;messages\u0026#34;: messages}, thread, version=\u0026#34;v1\u0026#34; ): kind = event[\u0026#34;event\u0026#34;] if kind == \u0026#34;on_chat_model_stream\u0026#34;: content = event[\u0026#34;data\u0026#34;][\u0026#34;chunk\u0026#34;].content if content: # 过滤掉工具调用时的空内容 print(content, end=\u0026#34;|\u0026#34;) print(\u0026#34;\\n\u0026#34;) # 换行结束 # 可选：同一线程继续对话，验证记忆功能 print(\u0026#34;继续询问洛杉矶天气：\u0026#34;) messages2 = [HumanMessage(content=\u0026#34;What about in LA?\u0026#34;)] async for event in abot.graph.astream_events( {\u0026#34;messages\u0026#34;: messages2}, thread, version=\u0026#34;v1\u0026#34; ): if event[\u0026#34;event\u0026#34;] == \u0026#34;on_chat_model_stream\u0026#34;: content = event[\u0026#34;data\u0026#34;][\u0026#34;chunk\u0026#34;].content if content: print(content, end=\u0026#34;|\u0026#34;) print(\u0026#34;\\n\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: asyncio.run(main()) 执行结果\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 Let| me| look| up| the| current| weather| in| San| Francisco|.|调用工具: {\u0026#39;name\u0026#39;: \u0026#39;tavily_search\u0026#39;, \u0026#39;args\u0026#39;: {\u0026#39;query\u0026#39;: \u0026#39;current weather in San Francisco\u0026#39;, \u0026#39;search_depth\u0026#39;: \u0026#39;basic\u0026#39;}, \u0026#39;id\u0026#39;: \u0026#39;call_00_QaGKAAhxxDmgUExKL9Md7517\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;tool_call\u0026#39;} 工具执行完毕，返回模型继续推理... Let| me| try| again|.|调用工具: {\u0026#39;name\u0026#39;: \u0026#39;tavily_search\u0026#39;, \u0026#39;args\u0026#39;: {\u0026#39;query\u0026#39;: \u0026#39;San Francisco weather today\u0026#39;, \u0026#39;search_depth\u0026#39;: \u0026#39;basic\u0026#39;}, \u0026#39;id\u0026#39;: \u0026#39;call_00_0t5rBYaVJrV0Iry7XtIJ8882\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;tool_call\u0026#39;} 工具执行完毕，返回模型继续推理... Here|\u0026#39;s| the| current| weather| in| San| Francisco| as| of| late| evening| on| **|May| |8|,| |202|6|**|: |🌡|️| **|Temperature|**:| |54|°|F| (|12|.|2|°|C|),| but| feels| like| **|52|.|6|°|F| (|11|.|4|°|C|)|** |⛅| **|Conditions|**:| Part|ly| cloudy| |💨| **|Wind|**:| WSW| at| |5|.|6| mph| (|gust|ing| up| to| |8|.|6| mph|) |💧| **|Hum|idity|**:| |86|% |☁|️| **|Cloud| cover|**:| |50|% |🌧|️| **|Ch|ance| of| rain|**:| |0|% |👁|️| **|Visibility|**:| |9| miles| |It|\u0026#39;s| a| cool|,| partly| cloudy| evening| in| SF| —| typical| for| this| time| of| year|!| If| you|\u0026#39;re| planning| for| tomorrow| (|May| |9|),| it|\u0026#39;s| expected| to| be| sunny| with| a| high| around| **|66|°|F| (|19|°|C|)**| in| the| afternoon|.| 继续询问洛杉矶天气： 调用工具: {\u0026#39;name\u0026#39;: \u0026#39;tavily_search\u0026#39;, \u0026#39;args\u0026#39;: {\u0026#39;query\u0026#39;: \u0026#39;current weather in Los Angeles\u0026#39;, \u0026#39;search_depth\u0026#39;: \u0026#39;basic\u0026#39;}, \u0026#39;id\u0026#39;: \u0026#39;call_00_DU0izSYvzKayq7bI5btZ5735\u0026#39;, \u0026#39;type\u0026#39;: \u0026#39;tool_call\u0026#39;} 工具执行完毕，返回模型继续推理... Here|\u0026#39;s| the| current| weather| in| **|Los| Angeles|**| as| of| the| same| time| (|even|ing| of| May| |8|,| |202|6|): |🌡|️| **|Temperature|**:| |61|°|F| (|16|.|1|°|C|),| feels| like| **|61|°|F|** |☁|️| **|Conditions|**:| Over|cast| (|cloud|y|) |💨| **|Wind|**:| South| at| |3|.|1| mph| |💧| **|Hum|idity|**:| |83|% |☁|️| **|Cloud| cover|**:| |100|%| (|full| over|cast|) |🌧|️| **|Ch|ance| of| rain|**:| |0|% |👁|️| **|Visibility|**:| |8| miles| |**|Quick| comparison| —| SF| vs| LA|:** ||| || San| Francisco| || Los| Angeles| | ||---|---||---|| ||| 🌡|️| Temp| || |54|°|F| (|12|°|C|)| || |61|°|F| (|16|°|C|)| | ||| ☁|️| Sky| || Part|ly| cloudy| || Over|cast| | ||| 💨| Wind| || WSW| |5|.|6| mph| || S| |3|.|1| mph| | ||| 💧| Humidity| || |86|%| || |83|%| | |LA| is| about| **|7|°|F| warmer|**| than| SF| tonight|,| though| it|\u0026#39;s| more| over|cast|.| Both| are| dry| with| no| rain| expected|!| ","permalink":"https://www.taigong.work/posts/langchainandlanggraph/","summary":"\u003ch2 id=\"langchain和langgraph\"\u003e\u003cstrong\u003eLangChain和LangGraph\u003c/strong\u003e\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e特性\u003c/th\u003e\n          \u003cth\u003eLangChain\u003c/th\u003e\n          \u003cth\u003eLangGraph\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e诞生背景\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e解决LLM应用开发的零散、重复问题\u003c/td\u003e\n          \u003ctd\u003e解决LangChain复杂智能体自定义难、扩展难、生产部署难的问题\u003ca href=\"https://www.langchain.com/blog/building-langgraph\"\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e核心理念\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e模块化、可组合的链\u003c/td\u003e\n          \u003ctd\u003e“图即工作流”，低抽象、强控制的状态机\u003ca href=\"https://www.langchain.com/blog/building-langgraph\"\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e核心组件\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003eComponents, Chains, LCEL\u003c/td\u003e\n          \u003ctd\u003eNodes, Edges, StateGraph, Checkpointer\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e执行模式\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e线性串联为主，LLM自主决策为辅\u003c/td\u003e\n          \u003ctd\u003e有向图驱动，支持循环、分支与并行处理\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e生态系统\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e丰富的社区集成，庞大的工具库\u003c/td\u003e\n          \u003ctd\u003e原生LangChain集成，可独立运行\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e设计目标\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e快速开发、快速原型\u003c/td\u003e\n          \u003ctd\u003e生产可靠、可观测、易扩展\u003ca href=\"https://www.langchain.com/blog/building-langgraph\"\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eDeep Agents是一个基于LangGraph的代理工具：规划、子代理、文件系统工具和上下文管理。\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eLangChain是代理框架：模型、工具和代理循环的抽象和集成。\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eLangGraph是编排运行时：持久执行、流、人在循环和持久性。\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eLangSmith是用于跨框架跟踪、评估、提示和部署的平台。\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cstrong\u003eFunction Calling（函数调用/工具调用）\u003c/strong\u003e 是大语言模型（LLM）的一项核心能力——它允许模型在生成回复时，不直接输出文本，而是输出一个 \u003cstrong\u003e结构化的函数调用请求\u003c/strong\u003e（通常是 JSON），由开发者实际执行该函数，然后将执行结果返回给模型，模型再据此生成最终回复。\u003c/p\u003e\n\u003ch3 id=\"注本文根据吴恩达ai-agent课程学习整理模型限制未使用chatgpt用deekseek替换\"\u003e注：本文根据吴恩达AI Agent课程学习整理，模型限制未使用chatgpt，用Deekseek替换。\u003c/h3\u003e\n\u003cp\u003e基于react模式的智能代理\n代码\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e  9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 36\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 37\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 38\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 39\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 40\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 41\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 42\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 43\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 44\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 45\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 46\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 47\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 48\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 49\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 50\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 51\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 52\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 53\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 54\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 55\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 56\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 57\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 58\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 59\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 60\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 61\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 62\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 63\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 64\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 65\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 66\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 67\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 68\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 69\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 70\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 71\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 72\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 73\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 74\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 75\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 76\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 77\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 78\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 79\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 80\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 81\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 82\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 83\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 84\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 85\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 86\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 87\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 88\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 89\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 90\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 91\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 92\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 93\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 94\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 95\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 96\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 97\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 98\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 99\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e100\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e101\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e102\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e103\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e104\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e105\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e106\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e107\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e108\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e109\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e110\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e111\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e112\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e113\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e114\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e115\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e116\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e117\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e118\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e119\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e120\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e121\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e122\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e123\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e124\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e125\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e126\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e127\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e128\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e129\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e130\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e131\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e132\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e133\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e134\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e135\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e136\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e137\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e138\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e139\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e140\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e141\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e142\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e143\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e144\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e145\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e146\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e147\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e148\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e149\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e150\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e151\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e152\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e153\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e154\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e155\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e156\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e157\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e158\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e159\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e160\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e161\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e162\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e163\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e164\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-py\" data-lang=\"py\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e openai\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e re\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e httpx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e os\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e dotenv \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e load_dotenv\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e_ \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e load_dotenv()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003efrom\u003c/span\u003e openai \u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e OpenAI\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# ================= 改动开始 =================\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 使用 DeepSeek 的 API 地址和你的 API Key\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 建议在 .env 文件中设置 DEEPSEEK_API_KEY\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eDEEPSEEK_API_KEY \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e os\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egetenv(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;DEEPSEEK_API_KEY\u0026#34;\u003c/span\u003e)  \u003cspan style=\"color:#75715e\"\u003e# 如果没有设置，会读取 OPENAI_API_KEY 兼容\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e DEEPSEEK_API_KEY:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eraise\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eValueError\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;请在环境变量中设置 DEEPSEEK_API_KEY\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eclient \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e OpenAI(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    api_key\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eDEEPSEEK_API_KEY,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    base_url\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;https://api.deepseek.com/v1\u0026#34;\u003c/span\u003e      \u003cspan style=\"color:#75715e\"\u003e# DeepSeek 官方 API 地址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# ================= 改动结束 =================\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 测试调用（可选）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echat_completion \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e client\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echat\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecompletions\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecreate(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    model\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;deepseek-chat\u0026#34;\u003c/span\u003e,          \u003cspan style=\"color:#75715e\"\u003e# 使用 DeepSeek 模型\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    messages\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e[{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;role\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;content\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello world\u0026#34;\u003c/span\u003e}]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(chat_completion\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echoices[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econtent)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAgent\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e __init__(self, system\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystem \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e system\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esystem:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend({\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;role\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;system\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;content\u0026#34;\u003c/span\u003e: system})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e __call__(self, message):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend({\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;role\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;content\u0026#34;\u003c/span\u003e: message})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        result \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eexecute()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        self\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eappend({\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;role\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;assistant\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;content\u0026#34;\u003c/span\u003e: result})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e result\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eexecute\u003c/span\u003e(self):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        completion \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e client\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echat\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecompletions\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecreate(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            model\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;deepseek-chat\u0026#34;\u003c/span\u003e,          \u003cspan style=\"color:#75715e\"\u003e# 统一使用 DeepSeek 模型\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            temperature\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            messages\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eself\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e completion\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003echoices[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessage\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econtent\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eYou run in a loop of Thought, Action, PAUSE, Observation.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eAt the end of the loop you output an Answer\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eUse Thought to describe your thoughts about the question you have been asked.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eUse Action to run one of the actions available to you - then return PAUSE.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eObservation will be the result of running those actions.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eYour available actions are:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ecalculate:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ee.g. calculate: 4 * 7 / 3\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eRuns a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eaverage_dog_weight:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ee.g. average_dog_weight: Collie\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ereturns average weight of a dog when given the breed\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eExample session:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eQuestion: How much does a Bulldog weigh?\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eThought: I should look the dogs weight using average_dog_weight\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eAction: average_dog_weight: Bulldog\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003ePAUSE\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eYou will be called again with this:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eObservation: A Bulldog weights 51 lbs\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eYou then output:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003eAnswer: A bulldog weights 51 lbs\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003estrip()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecalculate\u003c/span\u003e(what):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e eval(what)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eaverage_dog_weight\u003c/span\u003e(name):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Scottish Terrier\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Scottish Terriers average 20 lbs\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Border Collie\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a Border Collies average weight is 37 lbs\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eelif\u003c/span\u003e name \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Toy Poodle\u0026#34;\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a toy poodles average weight is 7 lbs\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;An average dog weights 50 lbs\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eknown_actions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;calculate\u0026#34;\u003c/span\u003e: calculate,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;average_dog_weight\u0026#34;\u003c/span\u003e: average_dog_weight\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Agent(prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eresult \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e abot(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;How much does a toy poodle weigh?\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(result)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eresult \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e average_dog_weight(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Toy Poodle\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eresult\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enext_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(result)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003emessages\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Agent(prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003equestion \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;I have 2 dogs, a border collie and a scottish terrier. \u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eWhat is their combined weight\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot(question)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enext_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(average_dog_weight(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Border Collie\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enext_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(average_dog_weight(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Scottish Terrier\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enext_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(eval(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;37 + 20\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enext_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(eval(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;37 + 20\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eprint(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eabot(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eaction_re \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003ecompile(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;^Action: (\\w+): (.*)$\u0026#39;\u003c/span\u003e)   \u003cspan style=\"color:#75715e\"\u003e# python regular expression to selection action\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003equery\u003c/span\u003e(question, max_turns\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    i \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    bot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Agent(prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    next_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e question\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e i \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e max_turns:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        i \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        result \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e bot(next_prompt)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        print(result)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        actions \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            action_re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e(a)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e a \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e result\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esplit(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e action_re\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e(a)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e actions:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#75715e\"\u003e# There is an action to run\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            action, action_input \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e actions[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egroups()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e action \u003cspan style=\"color:#f92672\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003ein\u003c/span\u003e known_actions:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003eraise\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Unknown action: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(action, action_input))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; -- running \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(action, action_input))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            observation \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e known_actions[action](action_input)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            print(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation:\u0026#34;\u003c/span\u003e, observation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            next_prompt \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Observation: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eformat(observation)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003equestion \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u0026#34;I have 2 dogs, a border collie and a scottish terrier. \u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eWhat is their combined weight\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003equery(question)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e这段代码实现了一个\u003cstrong\u003e基于大语言模型的 ReAct 风格智能代理\u003c/strong\u003e，能够通过“思考-行动-观察”循环来解决需要多步推理或工具调用的问题。它使用了 DeepSeek 模型（兼容 OpenAI API），并内置了两个简单工具：数学计算和查询狗的平均体重。\u003c/p\u003e","title":"LangChain和LangGraph"},{"content":"ISO/IEC 27001:2022 信息安全管理体系 全套落地方案 第一部分 项目概述 1.1 项目背景 公司处理超过1000万人的个人信息，已触发《个人信息保护合规审计管理办法》规定的每两年至少一次合规审计的法定义务。ISO/IEC 27001信息安全管理体系认证与个人信息保护合规审计具有高度协同效应——前者构建系统化的信息安全管理框架，后者聚焦具体的合规检查点。两者结合可实现“一次建设、双重收益”。\n重要提示：新版国家标准GB/T 22080-2025/ISO/IEC 27001:2022已于2025年6月30日发布，2026年1月1日起正式实施，全面取代旧版GB/T 22080-2016。自2025年8月1日起，认证机构已开始受理依据新版标准的初次认证申请。任何新申请认证的企业，均须按照新版标准执行。\n1.2 标准概述 ISO/IEC 27001是由国际标准化组织（ISO）与国际电工委员会（IEC）联合发布的信息安全管理体系（ISMS）要求标准。其核心框架建立在PDCA循环之上：\n阶段 内容 关键活动 Plan（计划） 明确ISMS范围和边界 识别法律法规要求、评估风险、制定处置计划 Do（执行） 制定信息安全方针和策略 落实控制措施、分配安全职责、实施培训和技术防护 Check（检查） 评估体系有效性和合规性 内部审核、管理评审 Act（改进） 根据检查结果持续优化 采取纠正措施、持续改进 1.3 新版标准核心变化 ISO/IEC 27001:2022相较于2013版的主要变化：\n变化维度 2013版本 2022版本 控制项数量 114项 93项 控制项分类 14个控制域 4大主题：组织控制（37项）、人员控制（8项）、物理控制（14项）、技术控制（34项） 新增控制项 — 威胁情报、云服务信息安全、ICT业务连续性、物理安全监控、数据泄露预防、配置管理等11项 1.4 项目实施总览 项目维度 说明 项目周期 约5-6个月（从启动到获证） 项目目标 通过ISO/IEC 27001:2022认证，同步支撑个人信息保护合规审计 覆盖范围 全公司信息安全管理体系（建议先从核心业务系统切入） 项目团队 跨部门项目组（IT、法务、HR、行政、业务部门） 第二部分 项目组织与团队 2.1 项目组织结构 text\n项目指导委员会（高层管理层） │ 项目经理（信息安全负责人） │ ┌──────────┬──────────┬──────────┼──────────┬──────────┐ │ │ │ │ │ │ IT组 法务组 HR组 行政组 业务组 内审组 （技术落地） （合规审核）（培训考核）（物理安全）（业务对接）（体系检查）\n2.2 各角色职责 角色 职责 建议人选 项目指导委员会 审批方针、提供资源、管理评审决策 总经理/CEO、CTO、CIO 项目经理 统筹项目进度、协调资源、向高层汇报 信息安全负责人/IT总监 IT组 技术控制措施部署（加密、DLP、MFA等） IT/安全团队 法务组 合规要求识别、合同审查、隐私政策 法务/合规团队 HR组 培训组织、保密协议签署、人员管理 人力资源部 行政组 物理安全控制（门禁、监控、访客管理等） 行政/后勤部门 业务组 业务流程中的信息安全控制 各业务部门负责人 内审组 内部审核、不符合项跟踪 持证内审员（不少于2人） 2.3 人员能力要求 项目经理：应具备信息安全管理体系相关知识和项目管理经验\n内审员：应持有ISO 27001内审员证书，熟悉标准条款和审核技巧\n技术人员：应具备网络安全、数据加密、访问控制等专业技能\n全员：需接受信息安全意识培训并通过考核\n第三部分 第一阶段：前期准备与策划（第1-4周） 3.1 获取高层承诺 目标：获得管理层对建立ISMS的正式承诺和支持。\n具体工作：\n向管理层提交项目立项报告，说明认证的必要性、收益和资源需求\n管理层签署《信息安全方针》，明确资源投入\n正式任命信息安全负责人（可与个人信息保护负责人合并）\n确定项目预算（认证费、咨询费、技术改造投入等）\n交付物：\n《项目立项报告》\n《信息安全方针》（管理层签署版）\n《项目预算审批表》\n3.2 组建项目团队 具体工作：\n发布正式的项目组成立通知\n明确各成员职责分工\n召开项目启动会\n交付物：\n《项目组成员及职责表》\n《项目启动会会议纪要》\n3.3 界定认证范围 具体工作：\n业务范围：明确覆盖的业务系统（如ERP、客户数据库、核心业务系统等）\n物理范围：确定覆盖的物理场所（总部、分支机构、数据中心等）\n组织范围：明确覆盖的部门和人员\n排除项：明确不在认证范围内的部分及理由\n交付物：\n《ISMS范围界定书》 3.4 标准培训 具体工作：\n组织项目核心成员参加ISO 27001标准培训\n理解标准条款要求（正文10个条款 + 附录A 93项控制）\n理解PDCA循环和过程方法\n交付物：\n《培训记录表》\n《培训考核记录》\n3.5 制定项目计划 具体工作：\n编制详细的项目实施计划，包括各阶段时间节点、责任人、交付物。\n交付物：\n《ISO 27001项目实施计划》 第四部分 第二阶段：现状评估与差距分析（第4-6周） 4.1 现状调研 具体工作：\n收集现有信息安全相关制度、流程、记录\n访谈各部门负责人，了解现行安全管理现状\n检查现有技术控制措施（防火墙、加密、备份等）\n交付物：\n《现状调研报告》 4.2 差距分析 具体工作：\n对照ISO 27001:2022标准要求（正文10个条款 + 附录A 93项控制），逐项评估现状与标准要求的差距。\n差距分析维度：\n维度 检查内容 组织控制（37项） 信息安全政策、资产管理、访问控制策略、供应商管理等 人员控制（8项） 背景审查、培训、保密协议、离职管理等 物理控制（14项） 安全区域、设备安全、环境安全等 技术控制（34项） 网络安全、访问控制、加密、日志、漏洞管理等 交付物：\n《差距分析报告》（含改进建议和优先级） 4.3 法律法规与合规要求识别 具体工作：\n识别适用的法律法规（《个人信息保护法》《网络安全法》《数据安全法》等）\n识别行业监管要求\n识别合同义务和客户要求\n交付物：\n《法律法规与合规要求清单》 第五部分 第三阶段：体系设计与文件编制（第6-10周） 5.1 风险评估（核心环节） 风险评估是ISO 27001的核心环节。按照ISO 27001:2022第6.1.2条款要求开展。\n步骤一：资产识别\n列出所有信息资产，包括：\n数据资产：客户数据、员工数据、财务数据、知识产权等\n硬件资产：服务器、网络设备、终端设备、存储设备等\n软件资产：操作系统、应用软件、数据库、开发工具等\n人员资产：关键岗位人员、外包人员等\n服务资产：云服务、外包服务、第三方API等\n对每项资产进行分类和分级（如：高敏感、中敏感、低敏感）。\n步骤二：威胁与脆弱性识别\n对每项资产识别：\n威胁：可能对资产造成损害的事件源（如黑客攻击、内部人员泄露、自然灾害等）\n脆弱性：资产存在的弱点（如弱密码、未打补丁、缺乏访问控制等）\n步骤三：风险分析与评价\n采用定性与定量相结合的方法：\n可能性评估：威胁发生的概率（高/中/低）\n影响评估：威胁发生后的损失程度（高/中/低）\n风险值计算：风险值 = 可能性 × 影响\n步骤四：风险处置\n根据风险等级制定处置策略：\n接受：风险在可接受范围内\n规避：停止相关业务活动\n转移：购买保险、外包等\n降低：实施控制措施\n2025版新增要求：评估云计算服务商风险并保留审计记录。\n交付物：\n《资产清单与分级表》\n《威胁与脆弱性识别表》\n《风险评估报告》\n《风险处置计划》\n5.2 适用性声明（SoA）编制 适用性声明（Statement of Applicability, SoA）是ISO 27001认证的关键文件，它连接了风险评估和风险处置。\n编制方法：\n对照附录A的93项控制措施\n逐项评估每项控制是否适用于贵公司\n对适用项说明实施状态（已实施/计划实施）\n对不适用项说明理由（如“不涉及云服务”）\n2025版新增要求：SoA中需说明AI训练数据管控措施及区块链应用安全策略。\n交付物：\n《适用性声明（SoA）》 5.3 四阶文件体系编制 ISO 27001文件体系采用四阶金字塔结构：\n层级 文件类型 内容 数量参考 一阶 管理手册 信息安全方针、目标、范围、组织架构 1份 二阶 程序文件 各管理过程的程序（如风险管理、内审、事件响应等） 10-15份 三阶 作业指导书 具体操作规范（如密码管理规范、备份操作规范等） 20-30份 四阶 记录表单 各类记录模板（如风险登记表、培训记录、访问申请单等） 30-50份 ISO 27001:2022要求的26份强制性文件可参考标准清单编制。\n核心文件清单：\n一阶文件：\n《信息安全管理手册》 二阶程序文件：\n《文件控制程序》\n《记录控制程序》\n《内部审核程序》\n《管理评审程序》\n《风险评估与处置程序》\n《信息安全事件管理程序》\n《业务连续性管理程序》\n《供应商安全管理程序》\n《培训管理程序》\n《访问控制管理程序》\n《资产管理程序》\n《变更管理程序》\n《纠正与预防措施程序》\n三阶作业指导书：\n《密码安全管理规范》\n《数据备份与恢复操作规范》\n《系统漏洞管理规范》\n《日志审计管理规范》\n《办公场所安全管理规范》\n《移动设备管理规范》\n《远程办公安全规范》\n四阶记录表单：\n各类申请表、记录表、日志模板等 交付物：\n完整的四阶文件体系（所有文件发布实施） 第六部分 第四阶段：控制措施部署与试运行（第10-16周） 6.1 技术控制措施部署 根据风险评估结果和SoA，落实技术控制措施。\n控制类别 具体措施 优先级 访问控制（A.9） 实施最小权限原则、多因素认证（MFA）、权限审批流程 高 密码安全 密码长度≥12位，含大小写+数字+符号 高 数据加密（A.8） 数据库敏感字段加密（身份证号、银行卡号等） 高 数据防泄露（DLP） 部署DLP工具，防止敏感数据未授权泄露 高 备份恢复 建立备份策略（频率≥1次/周），验证可恢复性 高 边界防护（A.13） 防火墙规则审计（禁止全通策略）、WAF防护日志 高 漏洞管理 定期扫描+修复闭环（高危漏洞72小时内处置） 高 日志审计 启用系统日志、操作日志、访问日志，定期审查 中 终端安全 终端防护软件、补丁管理 中 网络安全 网络隔离、入侵检测/防御 中 2025版强制要求：\n数据库加密系统\nDLP数据防泄漏工具\n供应商安全评估（每年复评）\n6.2 管理控制措施部署 控制类别 具体措施 人员管理（A.7） 全员签署保密协议 培训管理 年度信息安全培训≥8学时，考核通过率≥90% 供应商管理（A.15） 供应商安全评估、合同安全条款、云服务商SOC2报告或ISO 27001证书审查 事件响应（A.17） 建立应急响应预案、开展演练 变更管理 建立变更审批流程 资产管理 建立资产登记和管理制度 6.3 试运行 强制要求：体系运行不少于3个月，保留充分的运行记录。\n试运行期间需保留的证据：\n应急演练记录（如勒索软件攻击模拟）\n备份恢复日志\n权限变更申请与审批记录\n漏洞扫描与修复记录\n培训签到与考核记录\n事件处理记录\n访问控制日志\n供应商评估记录\n交付物：\n各类运行记录和日志（3个月）\n《试运行总结报告》\n第七部分 第五阶段：内部审核（第16-17周） 7.1 内部审核策划 审核目的：验证ISMS运行是否符合ISO 27001标准要求及企业自身要求，是否有效运行。\n审核范围：覆盖ISMS范围内的所有部门和过程。\n审核人员：\n持证内审员交叉检查\n覆盖不少于30%的部门\n内审员应独立于被审核部门\n交付物：\n《年度内部审核方案》\n《内部审核计划》\n7.2 内部审核实施 审核方法：\n首次会议：说明审核目的、范围、日程\n文件审核：审查体系文件是否符合标准要求\n现场审核：通过查阅记录、访谈人员、观察操作收集证据\n审核记录：填写《内部审核检查表》\n审核重点领域：\n权限变更记录的完整性和及时性\n漏洞修复的闭环管理\n风险评估的更新情况\n培训记录和效果\n事件响应和演练记录\n交付物：\n《内部审核检查表》（各部门）\n《不符合项报告》\n《内部审核报告》\n7.3 不符合项整改 要求：不符合项（NC）应在15日内完成根本原因分析及纠正措施。\n整改流程：\n分析不符合项的根本原因\n制定纠正措施\n实施纠正措施\n验证纠正措施的有效性\n交付物：\n《纠正措施记录表》\n《整改验证记录》\n第八部分 第六阶段：管理评审（第17-18周） 8.1 管理评审输入 根据ISO/IEC 27001:2022第9.3.2条款，管理评审输入应包括：\n输入项 说明 a) ISMS审核和评审的结果 内部审核报告、以往管理评审决议跟踪 b) 相关方的反馈 客户反馈、监管反馈、员工反馈 c) 用于改进ISMS的技术、产品或程序 新技术应用、流程优化 d) 预防和纠正措施的状况 不符合项整改情况 e) 以往管理评审的跟踪措施 上期决议落实情况 f) 可能影响ISMS的变化 业务变化、法规变化、技术变化 g) 改进建议 各部门提出的改进建议 交付物：\n《管理评审输入材料汇编》 8.2 管理评审会议 参与人员：高层管理层（总经理、CTO、CIO等）。\n评审内容：\nISMS的适宜性：是否仍然适合组织战略\nISMS的充分性：是否充分覆盖了风险\nISMS的有效性：是否实现了预期目标\n交付物：\n《管理评审会议纪要》 8.3 管理评审输出 根据ISO/IEC 27001:2022第9.3.3条款，管理评审输出应包括：\nISMS有效性改进的决定和措施\n影响信息安全的变更需求\n资源需求\n交付物：\n《管理评审报告》（管理层审批） 第九部分 第七阶段：认证审核（第18-22周） 9.1 认证机构选择 选择标准：\n必须选择获得CNAS（中国合格评定国家认可委员会） 认可的认证机构\n知名认证机构包括：SGS、BSI、TÜV等\n考虑机构的行业经验和服务质量\n签约内容：\n签订认证合同\n约定审核时间和费用\n9.2 第一阶段审核（文件审核） 审核目的：审核体系文件是否符合标准要求，确认现场审核准备情况。\n审核内容：\nISMS文件体系完整性\n适用性声明（SoA）\n风险评估报告\n体系运行记录\n审核结果：\n提出改进意见，企业需在第二阶段前完成整改 9.3 第二阶段审核（现场审核） 审核目的：实地检查ISMS运行情况。\n审核内容：\n（一）技术控制审核：\n审核点 审核内容 访问控制（A.9） 权限审批记录、最小权限原则执行、密码强度抽查、MFA启用情况 数据安全（A.8） 敏感字段加密、生产环境禁止显示明文密码、备份可恢复性演示 网络安全（A.13） 防火墙规则审计、WAF防护日志、漏洞扫描报告及修复记录 （二）管理流程审核：\n审核点 审核内容 人员管理（A.7） 随机访谈3-5名员工（如“如何报告钓鱼邮件？”）、培训记录及考核成绩 离职管控 账户禁用/权限回收时效（离职当日完成） 供应商风险（A.15） 云服务商SOC2报告或ISO 27001证书、供应商安全评估表 应急响应（A.17） 近1年演练记录（如勒索软件攻击模拟） （三）不符合项处理：\n审核组发现不符合项\n企业需在规定时间内完成整改\n9.4 认证决定与发证 流程：\n认证机构评审整改材料\n通过后颁发ISO 27001证书\n证书有效期三年\n重要提示：在获得新版国家标准认证证书前，不得声明信息安全管理体系通过了新版国家标准认证，也不得以任何误导方式使用认证证书。\n第十部分 获证后的持续维护 10.1 监督审核 频率：每年一次监督审核\n目的：确保体系持续符合要求\n10.2 再认证 时间：证书到期前进行再认证审核\n流程：与初次认证类似\n10.3 持续改进 定期更新风险评估，应对业务变化和新兴威胁\n持续监视、测量、分析和评价ISMS有效性\n根据内审、管理评审和监督审核结果持续优化\n第十一部分 项目文件清单汇总 11.1 必须输出的文件（按阶段） 阶段 文件名称 准备阶段 项目立项报告、信息安全方针、项目预算审批表、项目组成员及职责表、ISMS范围界定书、项目实施计划 现状评估 现状调研报告、差距分析报告、法律法规与合规要求清单 体系设计 资产清单与分级表、威胁与脆弱性识别表、风险评估报告、风险处置计划、适用性声明（SoA） 文件编制 信息安全管理手册、14+程序文件、20+作业指导书、30+记录表单 试运行 各类运行记录和日志（3个月）、试运行总结报告 内审 年度内审方案、内审计划、内审检查表、不符合项报告、内审报告、纠正措施记录 管理评审 管理评审输入材料、管理评审会议纪要、管理评审报告 认证 认证申请材料、整改记录 11.2 四阶文件详细清单 层级 文件类型 核心文件 一阶 管理手册 《信息安全管理手册》 二阶 程序文件 风险管理、内审、管理评审、事件响应、业务连续性、供应商管理、培训、访问控制、资产管理、变更管理等程序 三阶 作业指导书 密码管理、备份恢复、漏洞管理、日志审计、办公场所安全、移动设备管理、远程办公安全等规范 四阶 记录表单 资产登记表、风险登记表、培训记录、访问申请单、变更申请单、事件报告表、备份记录、权限审批表等 第十二部分 审核关键检查要点 以下为认证审核（第二阶段）中审核员重点检查的内容：\n12.1 技术控制检查要点 检查项 具体要求 证据要求 密码强度 长度≥12位，含大小写+数字+符号 密码策略文档、服务器抽查记录 多因素认证（MFA） 高风险系统强制启用 MFA配置截图、启用记录 权限管理 最小权限原则、审批流程完整 权限审批单、权限清单 数据加密 敏感字段加密（身份证号、银行卡号等） 加密配置文档、加密策略 备份恢复 可恢复性验证、现场演示恢复1份文件 备份恢复演练记录 防火墙 禁止全通策略 防火墙规则审计报告 漏洞管理 高危漏洞72小时内处置 漏洞扫描报告、修复记录 12.2 管理流程检查要点 检查项 具体要求 证据要求 员工意识 随机访谈3-5名员工 访谈记录 培训考核 年度培训≥8学时，通过率≥90% 培训签到表、考核成绩 离职管控 离职当日完成账户禁用/权限回收 离职流程记录 供应商评估 每年复评 供应商安全评估表 应急演练 近1年演练记录 演练方案、演练记录、总结报告 第十三部分 常见挑战与应对策略 挑战 表现 应对策略 管理层支持不足 资源不足、优先级低 量化安全风险与商业价值，用业务语言沟通 将ISO 27001视为IT部门的事 仅IT部门参与 强调其涉及HR、法务、行政、业务等多部门 体系与业务“两层皮” 制度与实际脱节 将安全控制嵌入业务流程 风险评估不科学 识别不完整、方法不统一 建立可重复、可比较的风险评估流程 过度依赖模板文件 文档精美但无效 审核员更看重运行有效性而非文档精美度 全员安全意识薄弱 员工参与度低 多样化培训方式，持续宣贯 第十四部分 与个人信息保护合规审计的协同建议 维度 ISO 27001 个人信息保护合规审计 协同点 风险评估 信息安全风险评估 个人信息保护影响评估 可共用评估方法论和部分数据 控制措施 93项控制（含数据安全） 加密、去标识化、访问控制等 技术措施可复用 组织保障 信息安全管理体系 个人信息保护负责人 负责人可统筹两者 持续改进 PDCA循环 每两年审计 审计发现可驱动ISMS改进 第三方管理 供应商安全评估 委托处理合同管理 可统一管理流程 建议：将ISO 27001体系建设与个人信息保护合规审计的准备工作合并推进，避免重复建设，降低总体合规成本。\n第十五部分 时间总览 阶段 时间 关键交付物 前期准备与策划 第1-4周 项目立项、方针签署、范围界定、项目计划 现状评估与差距分析 第4-6周 差距分析报告、风险评估报告 体系设计与文件编制 第6-10周 ISMS文件体系（四阶文件）、SoA 控制措施部署与试运行 第10-16周 技术控制部署、3个月运行记录 内部审核 第16-17周 内审报告、不符合项整改 管理评审 第17-18周 管理评审报告 认证审核 第18-22周 认证证书 总计 约5-6个月 — ","permalink":"https://www.taigong.work/posts/iso27001-plan/","summary":"\u003ch1 id=\"isoiec-270012022-信息安全管理体系-全套落地方案\"\u003eISO/IEC 27001:2022 信息安全管理体系 全套落地方案\u003c/h1\u003e\n\u003ch2 id=\"第一部分-项目概述\"\u003e第一部分 项目概述\u003c/h2\u003e\n\u003ch3 id=\"11-项目背景\"\u003e1.1 项目背景\u003c/h3\u003e\n\u003cp\u003e公司处理超过1000万人的个人信息，已触发《个人信息保护合规审计管理办法》规定的每两年至少一次合规审计的法定义务。ISO/IEC 27001信息安全管理体系认证与个人信息保护合规审计具有高度协同效应——前者构建系统化的信息安全管理框架，后者聚焦具体的合规检查点。两者结合可实现“一次建设、双重收益”。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e重要提示\u003c/strong\u003e：新版国家标准GB/T 22080-2025/ISO/IEC 27001:2022已于2025年6月30日发布，\u003cstrong\u003e2026年1月1日起正式实施\u003c/strong\u003e，全面取代旧版GB/T 22080-2016\u003ca href=\"http://www.zdhy.net/news/show-982.aspx\"\u003e\u003c/a\u003e\u003ca href=\"https://www.zpedu.com/it/xxaq/38401.html\"\u003e\u003c/a\u003e。自2025年8月1日起，认证机构已开始受理依据新版标准的初次认证申请\u003ca href=\"http://www.zdhy.net/news/show-982.aspx\"\u003e\u003c/a\u003e。任何新申请认证的企业，均须按照新版标准执行。\u003c/p\u003e\n\u003ch3 id=\"12-标准概述\"\u003e1.2 标准概述\u003c/h3\u003e\n\u003cp\u003eISO/IEC 27001是由国际标准化组织（ISO）与国际电工委员会（IEC）联合发布的信息安全管理体系（ISMS）要求标准。其核心框架建立在\u003cstrong\u003ePDCA循环\u003c/strong\u003e之上：\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e阶段\u003c/th\u003e\n          \u003cth\u003e内容\u003c/th\u003e\n          \u003cth\u003e关键活动\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003ePlan（计划）\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e明确ISMS范围和边界\u003c/td\u003e\n          \u003ctd\u003e识别法律法规要求、评估风险、制定处置计划\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eDo（执行）\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e制定信息安全方针和策略\u003c/td\u003e\n          \u003ctd\u003e落实控制措施、分配安全职责、实施培训和技术防护\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eCheck（检查）\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e评估体系有效性和合规性\u003c/td\u003e\n          \u003ctd\u003e内部审核、管理评审\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eAct（改进）\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e根据检查结果持续优化\u003c/td\u003e\n          \u003ctd\u003e采取纠正措施、持续改进\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"13-新版标准核心变化\"\u003e1.3 新版标准核心变化\u003c/h3\u003e\n\u003cp\u003eISO/IEC 27001:2022相较于2013版的主要变化\u003ca href=\"https://www.zpedu.com/it/xxaq/38401.html\"\u003e\u003c/a\u003e：\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e变化维度\u003c/th\u003e\n          \u003cth\u003e2013版本\u003c/th\u003e\n          \u003cth\u003e2022版本\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e控制项数量\u003c/td\u003e\n          \u003ctd\u003e114项\u003c/td\u003e\n          \u003ctd\u003e\u003cstrong\u003e93项\u003c/strong\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e控制项分类\u003c/td\u003e\n          \u003ctd\u003e14个控制域\u003c/td\u003e\n          \u003ctd\u003e\u003cstrong\u003e4大主题\u003c/strong\u003e：组织控制（37项）、人员控制（8项）、物理控制（14项）、技术控制（34项）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e新增控制项\u003c/td\u003e\n          \u003ctd\u003e—\u003c/td\u003e\n          \u003ctd\u003e威胁情报、云服务信息安全、ICT业务连续性、物理安全监控、数据泄露预防、配置管理等11项\u003ca href=\"https://www.zpedu.com/it/xxaq/38401.html\"\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"14-项目实施总览\"\u003e1.4 项目实施总览\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e项目维度\u003c/th\u003e\n          \u003cth\u003e说明\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e项目周期\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e约5-6个月（从启动到获证）\u003ca href=\"http://zhuohangtech.com/Article/iso27001rzsbl.html\"\u003e\u003c/a\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e项目目标\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e通过ISO/IEC 27001:2022认证，同步支撑个人信息保护合规审计\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e覆盖范围\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e全公司信息安全管理体系（建议先从核心业务系统切入）\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003e项目团队\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e跨部门项目组（IT、法务、HR、行政、业务部门）\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"第二部分-项目组织与团队\"\u003e第二部分 项目组织与团队\u003c/h2\u003e\n\u003ch3 id=\"21-项目组织结构\"\u003e2.1 项目组织结构\u003c/h3\u003e\n\u003cp\u003etext\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e                    项目指导委员会（高层管理层）\n                              │\n                    项目经理（信息安全负责人）\n                              │\n    ┌──────────┬──────────┬──────────┼──────────┬──────────┐\n    │          │          │          │          │          │\n  IT组      法务组      HR组      行政组     业务组    内审组\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e（技术落地） （合规审核）（培训考核）（物理安全）（业务对接）（体系检查）\u003c/p\u003e","title":"信息安全管理体系方案-ISO27001"},{"content":"需求是定期备份某个远程目录文件，增量备份。 以下内容为脚本，请作为批处理脚本使用\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @echo off chcp 65001 \u0026gt;nul :: Configuration set SRC=\u0026#34;\\\\192.168.1.1\\财务部\u0026#34; set DEST=\u0026#34;D:\\Backup\u0026#34; set LOG=\u0026#34;D:\\Backup\\finance_backup.log\u0026#34; echo ============================================== \u0026gt;\u0026gt; %LOG% echo Start Time: %date% %time% \u0026gt;\u0026gt; %LOG% :: Check if Source is accessible before mirroring to prevent accidental wipes if not exist %SRC% ( echo ERROR: Source path not found. Aborting to protect backup. \u0026gt;\u0026gt; %LOG% exit /b 1 ) :: Run Robocopy :: /MIR : Mirroring (CAUTION: Deletes files in DEST not in SRC) :: /Z : Restartable mode (good for large files/network drops) :: /FFT : Assume FAT File Times (better for network/NAS compatibility) :: /R:2 : Retry twice on fail :: /W:5 : Wait 5 seconds between retries :: /NP : No Progress (keeps log file clean) :: /MT:8 : Multi-threaded (Optional: speeds up transfer of many small files) robocopy %SRC% %DEST% /MIR /Z /FFT /R:2 /W:5 /NP /MT:8 /LOG+:%LOG% set RC=%ERRORLEVEL% echo End Time: %date% %time% \u0026gt;\u0026gt; %LOG% echo Robocopy ExitCode=%RC% \u0026gt;\u0026gt; %LOG% echo ============================================== \u0026gt;\u0026gt; %LOG% :: Return success if RC is less than 8 if %RC% LSS 8 (exit /b 0) else (exit /b %RC%) ... ","permalink":"https://www.taigong.work/posts/rebocopy/","summary":"\u003cp\u003e需求是定期备份某个远程目录文件，增量备份。\n以下内容为脚本，请作为批处理脚本使用\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\n\u003ctable style=\"border-spacing:0;padding:0;margin:0;border:0;\"\u003e\u003ctr\u003e\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 1\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 2\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 3\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 4\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 5\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 6\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 7\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 8\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e 9\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e10\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e11\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e12\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e13\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e14\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e15\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e16\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e17\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e18\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e19\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e20\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e21\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e22\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e23\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e24\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e25\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e26\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e27\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e28\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e29\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e30\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e31\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e32\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e33\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e34\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e35\n\u003c/span\u003e\u003cspan style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f\"\u003e36\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\"\u003e\n\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bat\" data-lang=\"bat\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e@\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e off\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echcp 65001 \u0026gt;nul\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: Configuration\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e SRC=\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\\\\192.168.1.1\\财务部\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e DEST=\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;D:\\Backup\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e LOG=\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;D:\\Backup\\finance_backup.log\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e ============================================== \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e Start Time: %date% %time% \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: Check if Source is accessible before mirroring to prevent accidental wipes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enot\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eexist\u003c/span\u003e %SRC% (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e ERROR: Source path not found. Aborting to protect backup. \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eexit\u003c/span\u003e /b 1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: Run Robocopy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /MIR  : Mirroring (CAUTION: Deletes files in DEST not in SRC)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /Z    : Restartable mode (good for large files/network drops)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /FFT  : Assume FAT File Times (better for network/NAS compatibility)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /R:2  : Retry twice on fail\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /W:5  : Wait 5 seconds between retries\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /NP   : No Progress (keeps log file clean)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: /MT:8 : Multi-threaded (Optional: speeds up transfer of many small files)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003erobocopy %SRC% %DEST% /MIR /Z /FFT /R:2 /W:5 /NP /MT:8 /LOG+:%LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e RC=%ERRORLEVEL%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e End Time: %date% %time% \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e Robocopy ExitCode=%RC% \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e ============================================== \u0026gt;\u0026gt; %LOG%\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\u003cspan style=\"color:#75715e\"\u003e: Return success if RC is less than 8\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e %RC% \u003cspan style=\"color:#f92672\"\u003eLSS\u003c/span\u003e 8 (\u003cspan style=\"color:#66d9ef\"\u003eexit\u003c/span\u003e /b 0) \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eexit\u003c/span\u003e /b %RC%)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","title":"利用Robocopy脚本实现企业财务数据自动备份"},{"content":"GPUSTACK GPUStack 是一个非常优秀的轻量级大模型多机整合与服务化框架，它支持异构算力和私有化集群。虽然它底层对华为昇腾（CANN）和鲲鹏（ARM64）的自动化原生支持可能不如传统 NVIDIA N卡那样“即插即用”，但通过其支持的 vLLM（Ascend 分支）后端 配合 Docker，我们可以非常优雅地实现多机并联。\n以下是使用 GPUStack 极限部署 DeepSeek-V4-Flash 的全新实操方案：\n🛠️ GPUStack 多机架构设计 由于 310P3 运行大模型必须依赖华为 CANN 环境和特定的 vLLM/MindIE 算子，在 GPUStack 体系下，我们推荐采用 「Host 宿主机 GPUStack 管理 + Container 容器内算力节点」 的架构。\n节点 0 (Master 节点)：运行 GPUStack Server（控制台与调度中心）+ 运行 GPUStack Worker（提供本地两块 310P3 算力）。 节点 1、2、3 (Worker 节点)：仅运行 GPUStack Worker 注册到 Master，提供各自的 310P3 算力。 第一阶段：宿主机基础环境（4台机器同步执行） 无论用什么框架，底层的驱动和固件是绕不开的。请确保 4台 机器均已完成以下操作：\n1. 安装昇腾 310P3 驱动与固件 参考上一篇文档，确保 npu-smi info 能够正常看到每台机器的 96GB 显存。\n2. 安装 Docker 与 Ascend-Docker-Runtime 为了让 Docker 容器能够调用宿主机的 310P3 芯片，必须安装华为官方的容器运行时：\n1 2 3 4 5 6 7 8 9 10 11 # 安装常规 Docker sudo yum install -y docker-ce sudo systemctl start docker \u0026amp;\u0026amp; sudo systemctl enable docker # 下载并安装华为 Ascend Docker Runtime # 请去昇腾官网下载 Ascend-docker-runtime_*.run chmod +x Ascend-docker-runtime_*.run sudo ./Ascend-docker-runtime_*.run --install # 重启 Docker 使运行时生效 sudo systemctl restart docker 第二阶段：部署 GPUStack Server（仅在 节点0 执行） 在主节点上，我们直接拉起 GPUStack 的控制中心。\n1 2 3 4 curl -sfL [https://get.gpustack.ai](https://get.gpustack.ai) | sh - # 启动成功后，查看初始登录 Token cat /var/lib/gpustack/token 此时你可以通过浏览器访问 http://192.168.10.11:10114 登录 GPUStack WebUI。\n第三阶段：配置昇腾专属 Worker 容器（4台机器同步执行） 由于 GPUStack 原生的二进制 Worker 无法直接编译昇腾复杂的底层通信（HCCL），我们需要采用 HuggingFace/vLLM-Ascend 或 华为官方 MindIE 的 Docker 镜像 作为 Worker 的运行载体。这里以社区广泛兼容的 vllm-ascend 镜像为例：\n拉取支持 310P3/910B 的 vLLM 镜像 1 docker pull flashel/vllm-ascend:latest # 或使用华为官方提供的 MindIE 镜像 将容器作为 GPUStack Worker 启动并注册在 4 台机器上，将整个容器伪装成 Worker 注入到 节点0 的 Server 中。在 节点0 执行： 1 2 3 4 5 6 7 8 9 10 11 docker run -d --name gpustack-worker-0 \\ --net=host \\ --device=/dev/davinci0 \\ --device=/dev/davinci1 \\ --device=/dev/davinci_manager \\ --device=/dev/devmm_svm \\ --device=/dev/hisi_hdc \\ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \\ -v /data/models:/data/models \\ flashel/vllm-ascend:latest \\ gpustack worker --server-url [http://192.168.10.11:10114](http://192.168.10.11:10114) --token \u0026lt;你的SERVER_TOKEN\u0026gt; 在 节点1、2、3 执行： 只需将上面的命令复制到另外三台机器执行即可（确保 \u0026ndash;server-url 依然指向 节点0）。 ⚠️ 关键点解释：\u0026ndash;device=/dev/davinci* 和挂载 driver 目录是为了把宿主机的 310P3 显卡硬直通进容器。通过 \u0026ndash;net=host 让 4 个容器共享物理网络，以保证 MoE 模型的分布式 All-to-All 通信。\n启动完成后，刷新 节点0 的 GPUStack WebUI，你会在 \u0026ldquo;Nodes\u0026rdquo; 页面看到 4 个健康的节点，每台机器都识别出了对应大显存的昇腾 NPU。\n第四阶段：在 GPUStack 界面极限部署 DeepSeek-V4-Flash 现在，你不需要再去手写繁琐的 rank_table.json 和环境变量了，所有的分布式切分全部在 WebUI 页面通过可视化表单完成。\n添加模型 (Models -\u0026gt; Add Model) Model Name: deepseek-v4-flash-int4 Source: 选择 Local Path（如果已经提前把量化好的模型文件放在了 4 台机器的 /data/models/deepseek-v4-flash-int4 目录下）。\n配置分布式推理参数（核心压榨配置）在模型的高级设置（Advanced Settings）中，按以下参数配置以适配 310P3 的硬件极限： 配置项 设定值 作用说明 Backend vLLM 强制指定使用容器内适配了昇腾的 vLLM 后端 Tensor Parallel Size (TP) 2 在单台机器内部的 2 个显卡芯片间做张量并行 Pipeline Parallel Size (PP) 4 纵向跨 4 台服务器做流水线切分，规避物理网络带宽瓶颈 Max Model Length 32768 上下文先限制在 32K，防止首轮实验 OOM Max Num Seq (Batch Size) 1 必须设为 1，310P3 的算力无法承受多并发的 V4-Flash Additional Arguments --quantization awq 或 --quantization gptq 明确告诉后端这是 INT4 量化模型 🔍 极限技术验证与监控 点击部署后，GPUStack 会自动在 4 台机器的容器之间拉起通信。你可以通过以下方式验证和压榨这套系统：\n观察控制台日志：在 GPUStack 的 Model Logs 页面，你会看到 4 个节点通过网卡建立 HCCL（华为集合通信）握手的过程。\n算力压榨监控：在 4 台宿主机上分别开一个终端运行：\n1 watch -n 1 npu-smi info 当你通过 GPUStack 自带的 Playground 发送 Prompt 时，你会非常直观地看到：由于配置了 PP=4（流水线并行），4台机器的 310P3 显存占用是满的（约 36G+），但 AI Core 利用率（算力）并不是同时拉满，而是像接力赛一样，节点0 闪烁 -\u0026gt; 节点1 闪烁 -\u0026gt; 节点2 闪烁 -\u0026gt; 节点3 闪烁。\n💡 结论 利用 GPUStack + Docker 容器直通，你免去了手动管理分布式多进程拉起的痛苦。虽然受限于 310P3 LPDDR4X 内存带宽，最终的吐字速度（Token/s）依然是一秒几个字的“极限验证速度”，但你用最现代化的私有大模型 management 框架，成功在老旧/魔改推理卡上完成了对 2840亿 参数顶级 MoE 模型的驯服！\n","permalink":"https://www.taigong.work/posts/deploy-gpustack/","summary":"\u003ch3 id=\"gpustack\"\u003eGPUSTACK\u003c/h3\u003e\n\u003cp\u003eGPUStack 是一个非常优秀的轻量级大模型多机整合与服务化框架，它支持异构算力和私有化集群。虽然它底层对华为昇腾（CANN）和鲲鹏（ARM64）的自动化原生支持可能不如传统 NVIDIA N卡那样“即插即用”，但通过其支持的 \u003cstrong\u003evLLM（Ascend 分支）后端\u003c/strong\u003e 配合 Docker，我们可以非常优雅地实现多机并联。\u003c/p\u003e\n\u003cp\u003e以下是使用 GPUStack 极限部署 DeepSeek-V4-Flash 的全新实操方案：\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"-gpustack-多机架构设计\"\u003e🛠️ GPUStack 多机架构设计\u003c/h3\u003e\n\u003cp\u003e由于 310P3 运行大模型必须依赖华为 CANN 环境和特定的 vLLM/MindIE 算子，在 GPUStack 体系下，我们推荐采用 \u003cstrong\u003e「Host 宿主机 GPUStack 管理 + Container 容器内算力节点」\u003c/strong\u003e 的架构。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e节点 0 (Master 节点)\u003c/strong\u003e：运行 GPUStack Server（控制台与调度中心）+ 运行 GPUStack Worker（提供本地两块 310P3 算力）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e节点 1、2、3 (Worker 节点)\u003c/strong\u003e：仅运行 GPUStack Worker 注册到 Master，提供各自的 310P3 算力。\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"第一阶段宿主机基础环境4台机器同步执行\"\u003e第一阶段：宿主机基础环境（4台机器同步执行）\u003c/h2\u003e\n\u003cp\u003e无论用什么框架，底层的驱动和固件是绕不开的。请确保 4台 机器均已完成以下操作：\u003c/p\u003e\n\u003ch3 id=\"1-安装昇腾-310p3-驱动与固件\"\u003e1. 安装昇腾 310P3 驱动与固件\u003c/h3\u003e\n\u003cp\u003e参考上一篇文档，确保 \u003ccode\u003enpu-smi info\u003c/code\u003e 能够正常看到每台机器的 96GB 显存。\u003c/p\u003e\n\u003ch3 id=\"2-安装-docker-与-ascend-docker-runtime\"\u003e2. 安装 Docker 与 Ascend-Docker-Runtime\u003c/h3\u003e\n\u003cp\u003e为了让 Docker 容器能够调用宿主机的 310P3 芯片，必须安装华为官方的容器运行时：\u003c/p\u003e","title":"昇腾 310P 部署 GPUSTACK 集群"},{"content":"👨‍💻 Taigong 专注：\nAI Agent 安全工程 自动化系统 后端架构 📌 这个站用于记录真实工程实践\n","permalink":"https://www.taigong.work/about/","summary":"\u003ch2 id=\"-taigong\"\u003e👨‍💻 Taigong\u003c/h2\u003e\n\u003cp\u003e专注：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAI Agent\u003c/li\u003e\n\u003cli\u003e安全工程\u003c/li\u003e\n\u003cli\u003e自动化系统\u003c/li\u003e\n\u003cli\u003e后端架构\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003cp\u003e📌 这个站用于记录真实工程实践\u003c/p\u003e","title":"关于我"}]