news 2026/9/5 15:58:13

Hermes Agent实战:Session、Skill、Tool与上下文加载协同

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Hermes Agent实战:Session、Skill、Tool与上下文加载协同

学习 Hermes Agent 这类 AI 大模型侧的 Agent 工程时,最容易踩的坑不是模型不会回答,而是把 Session 会话、Skill 技能、工具调用和上下文加载当成四件独立的事。实际跑一个能用的 Agent,这四部分必须围绕同一条请求链路协作:用户输入进入后,系统先加载会话和历史上下文,再把可用技能与工具描述放进提示词,模型决定调用哪个工具,工具执行结果回填给模型,最后产出回复并持久化会话。本文会从这四个核心概念开始,用一个最小可运行的 Python 工程把它们串起来,并给出环境配置、目录设计、代码实现、运行验证、常见问题和生产部署建议。

这套内容适合三类读者:第一次接触 Agent 开发,想理解 Session、Skill、Tool 和上下文加载之间关系的开发者;已经能调用大模型 API,但觉得多轮对话和工具调用难以组织的人;准备在本地或测试环境部署 Hermes Agent,想先建立工程化思维的技术人员。

1. 先搞懂 Agent 的四个关键概念,再动手写代码

1.1 Hermes Agent 不是模型,而是一套 Agent 运行工程

很多资料把 Agent 简单理解成“接一个大模型 API 就能自动完成任务的程序”,这个理解会直接导致后期代码乱。模型只负责推理和文字生成,真正让 Agent 具备状态管理、技能扩展、工具调用能力的是外面这一层运行框架。

Hermes Agent 的学习重点,是 Session 会话管理、Skill 技能注册、Tool 工具调用、Context 上下文加载这四层。它们各自解决一个具体问题:

  • Session 记录“之前发生过什么”。
  • Skill 告诉模型“当前具备哪些专项能力”。
  • Tool 把模型的决定变成真实可执行的函数。
  • Context 决定“这一次请求到底能把哪些信息放进输入”。

如果一上来就写 model.chat.completions.create,代码很短,但后续加一个技能、接一个工具、做一次会话恢复,都会变得非常难受。先理解这四层,后面的部署和排错才有依据。

1.2 Session 会话不是聊天记录,而是完整运行状态

很多开发者在 Web 项目里用过 Session,容易下意识把 Agent Session 等同于浏览器会话。两者概念接近,但 Agent 里的 Session 需要保存的内容更复杂,至少包括:

  • 会话 ID。
  • 模型名称和参数快照。
  • 多轮 user、assistant 消息。
  • 中间出现的工具调用和结果。
  • 技能执行状态和上下文引用。
  • 更新时间、来源渠道等元数据。

在纯 API 调用中,如果你把每条消息每次请求都重新传给模型,确实也能实现多轮对话。但这里的问题是状态不可控:用户清空历史时如何同步?工具执行产生中间结果时如何回填?切换模型后旧历史还能不能用?Session 的职责就是把这些问题统一收纳到结构化存储中。

1.3 Skill 技能负责把“会做什么”注入给模型

Skill 的粒度比 Tool 大。一个 Skill 往往是一套能力目录,里面写明它适合处理什么任务、需要调用哪些 Tool、应该按什么顺序执行。

例如笔记助手是一个 Skill,它内部可以包含三个 Tool:list_notes、read_note、save_note。模型只有在用户请求涉及笔记时才激活这个 Skill,激活后 Prompt 中会出现这个技能的说明。不要把所有技能一次性全塞进系统提示词,模型在超长上下文中反而会丢失关键指令。Skill 注册表的价值就是按需装配。

1.4 Tool 工具负责把模型决定变成可执行动作

模型本身不能直接操作文件系统、数据库或外部 API。Tool 就是模型与真实世界的桥梁。

模型不会自己执行代码,它只会输出一次“结构化决定”,例如:

{ "name": "save_note", "arguments": "{\"title\": \"Session学习笔记\", \"content\": \"Session状态要先持久化再继续下一轮对话\"}" }

Agent 侧拿到这个结果后,去本地函数注册表里找到 save_note,执行它,再把执行结果转成消息返回给模型。缺少这一步,模型能力再强也只能在文字世界内打转。

1.5 Context 上下文加载决定模型这次能看到什么

模型是无状态推理引擎,每次请求是否能看到历史、工具结果、外部知识,完全取决于 Agent 如何构造 messages。

合理做法是分层组装:

  • System 层:固定系统角色和全局规则。
  • Skill 层:当前任务激活的技能说明。
  • History 层:最近且未超过预算的历史消息。
  • ToolResult 层:本次链路刚产生的工具执行结果。
  • User 层:用户本次输入。
  • Retrieval 层:如果需要外挂知识库,则把命中片段插入到历史消息前后。

Context 拼接顺序对模型理解影响很大。工具结果必须紧跟调用它的助理消息,否则模型无法把结果对应到具体动作;历史消息过长时优先丢弃中间步骤,保留最近意图和最终结论。

2. 环境准备与目录设计:先搭一个可复现的最小工程

2.1 版本和环境要求

不建议一开始就在不确定的前提下使用最新版桌面安装包。任何 Agent 工程都应该先记录一套可复现的环境组合,出现问题时才能定位是代码问题还是依赖问题。

项目建议要求说明
操作系统Windows 10/11、Ubuntu 20.04+、macOS后续命令以 Linux/macOS 为主,Windows 注意路径写法
Python3.10 或更高需要 dataclass、类型注解等现代语法
包管理pip、venv避免直接装到系统 Python
模型接口OpenAI 兼容接口支持配置 base_url,便于切换本地模型服务
磁盘空间预留 500MB 以上仅代码学习;本地模型另算
网络能访问模型 API 即可生产环境还需考虑访问控制和审计

如果实际下载的 Hermes Agent 安装包自带运行环境,优先按官方 README 的版本要求执行。下面给出的目录和代码用于说明这套工程逻辑,落到真实项目时需要根据安装包的版本和包路径调整。

2.2 创建虚拟环境并安装依赖

先建立项目目录,再创建虚拟环境。

mkdir hermes-agent-lab cd hermes-agent-lab python -m venv .venv source .venv/bin/activate

Windows 环境把激活命令换成:

.venv\Scripts\activate

安装基础依赖:

python -m pip install --upgrade pip pip install openai python-dotenv pyyaml

openai 是官方 SDK,python-dotenv 用来读取 .env 配置,pyyaml 用来读取 Skill 技能描述文件。如果你的环境不需要 YAML 技能目录,可以暂时不装 pyyaml,但建议保留,后面做技能注册时会更方便。

2.3 项目目录结构与 .env 配置

设计合理的目录结构,能让 Session、Skill、Tool、Context 各归其位。本项目建议如下:

hermes-agent-lab/ ├── .env ├── .env.example ├── requirements.txt ├── app/ │ ├── __init__.py │ ├── main.py │ ├── session.py │ ├── context.py │ ├── skills.py │ └── tools.py ├── data/ │ ├── notes/ │ └── sessions/ └── skills/ ├── meeting_review/ │ ├── skill.yaml │ └── instructions.md └── chat_helper/ └── skill.yaml

.env 示例:

HERMES_API_KEY=sk-your-key HERMES_BASE_URL=https://api.openai.com/v1 HERMES_MODEL=gpt-4o-mini HERMES_SESSION_DIR=data/sessions HERMES_NOTE_DIR=data/notes

不要提交 .env 到 Git,提交时只保留 .env.example。API Key 必须通过环境变量注入,不要把密钥写死在代码里。如果使用本地大模型服务,把 HERMES_BASE_URL 改成对应的 OpenAI 兼容地址即可,例如:

HERMES_BASE_URL=http://127.0.0.1:8000/v1

不同模型对函数调用、JSON 输出和控制符的处理能力不同,切换模型前要先确认模型是否支持原生 tools 参数。

2.4 先验证模型连接,再进入功能开发

在写复杂代码前,先写一段最小连接验证:

import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() client = OpenAI( api_key=os.getenv("HERMES_API_KEY"), base_url=os.getenv("HERMES_BASE_URL"), ) resp = client.chat.completions.create( model=os.getenv("HERMES_MODEL", "gpt-4o-mini"), messages=[ {"role": "system", "content": "你是 Hermes Agent 的连通性测试助手。"}, {"role": "user", "content": "请回复:连接正常"}, ], ) print(resp.choices[0].message.content)

这一步能确认 Key、Base URL、模型名三者是否匹配。很多安装和接入问题都出在这个环节,而不是后面的 Agent 代码。

3. Session 会话的代码落地:用 JSON 文件做可恢复的状态存储

3.1 定义 Session 数据结构

为了不过度设计,先把 Session 建模成 dataclass。这里的关键是:messages 必须是结构化列表,每个元素都是 API 可直接消费的字典,而不是把整段对话存成一个大字符串。

在 app/session.py 中写入:

from dataclasses import dataclass, field, asdict import json import os import time @dataclass class Session: session_id: str model: str = "" messages: list[dict] = field(default_factory=list) metadata: dict = field(default_factory=dict) created_at: float = field(default_factory=time.time) updated_at: float = field(default_factory=time.time) def add_user_message(self, content: str): self.messages.append({"role": "user", "content": content}) self.updated_at = time.time() def add_assistant_message(self, content: str): self.messages.append({"role": "assistant", "content": content}) self.updated_at = time.time() def to_dict(self): self.updated_at = time.time() return asdict(self) @classmethod def from_dict(cls, data): return cls(**data)

注意不要把 role 之外的自定义字段混进 messages。像“这条消息属于哪个技能”“这条消息的 token 数”这类信息应放入 metadata,而不是消息体里。API 对 messages 有严格结构要求,混合业务字段可能让兼容本地模型的接口直接报错。

3.2 SessionStore:保存、恢复、防止路径穿越

Session 的保存位置由 HERMES_SESSION_DIR 控制。每个会话一个 JSON 文件,文件名使用 session_id。

class SessionStore: def __init__(self, session_dir: str = "data/sessions"): self.session_dir = session_dir os.makedirs(session_dir, exist_ok=True) def _path(self, session_id: str) -> str: if session_id != os.path.basename(session_id): raise ValueError("session_id 不能包含路径分隔符") return os.path.join(self.session_dir, f"{session_id}.json") def save(self, session: Session): path = self._path(session.session_id) tmp = path + ".tmp" with open(tmp, "w", encoding="utf-8") as f: json.dump(session.to_dict(), f, ensure_ascii=False, indent=2) os.replace(tmp, path) def load(self, session_id: str) -> Session: path = self._path(session_id) if not os.path.exists(path): return Session(session_id=session_id) with open(path, "r", encoding="utf-8") as f: data = json.load(f) return Session.from_dict(data)

这里有一个容易被忽略的安全细节:session_id 必须经过 os.path.basename 校验,否则恶意调用可能通过 ../../ 之类路径访问其他文件。Session 文件本质是本地数据,但生产环境里如果 session_id 来自外部,这就是路径穿越漏洞入口。

3.3 为什么保存要使用 tmp 文件加 rename

SessionStore.save 先写 .tmp 文件,再调用 os.replace 替换正式文件。直接打开正式文件写入,如果进程中途崩溃或磁盘写入失败,原文件可能变成半截 JSON。

os.replace 在同一个文件系统内是原子操作,要么新内容完全生效,要么旧内容保持不变。这个技巧在保存 Session、技能缓存和知识库索引时都建议保留。

恢复会话时可以这样验证:

store = SessionStore("data/sessions") s = store.load("demo-001") print(len(s.messages)) s.add_user_message("继续上一次话题") store.save(s)

多次运行程序后,再次打开还能从 data/sessions/demo-001.json 恢复历史,这就是 Session 持久化的意义。

4. Skill 技能注册和 Context 上下文加载:按需提示比堆指令更可靠

4.1 SkillRegistry:用目录和描述文件注册技能

很多开发者喜欢把所有指令写在 system prompt 里,结果技能一多,提示词首屏被挤爆,模型反而丢失关键信息。

这里用目录方式注册 Skill。每个技能目录里放一个 skill.yaml,描述技能名称、使用条件和工具清单。

例如 skills/meeting_review/skill.yaml:

name: meeting_review description: 当用户要求整理会议纪要、记录项目复盘或保存讨论结论时使用 prompt: | 你是会议与项目复盘助手。用户提出保存或总结需求时,先判断是否涉及已保存笔记。 如果需要查看已有内容,调用 list_notes 和 read_note。 需要落盘时,调用 save_note 写入 Markdown 文件,并给用户返回文件路径。 tools: - list_notes - read_note - save_note

SkillRegistry 的作用是扫描目录、读取 YAML、把可用技能暴露给 ContextBuilder。代码放在 app/skills.py:

import os from dataclasses import dataclass import yaml @dataclass class Skill: name: str description: str prompt: str = "" tools: list[str] | None = None class SkillRegistry: def __init__(self, skill_dir: str = "skills"): self.skill_dir = skill_dir self._skills: dict[str, Skill] = {} def load(self): for root, _, files in os.walk(self.skill_dir): if "skill.yaml" not in files: continue yaml_path = os.path.join(root, "skill.yaml") with open(yaml_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) skill = Skill( name=data["name"], description=data.get("description", ""), prompt=data.get("prompt", ""), tools=data.get("tools", []), ) self._skills[skill.name] = skill return self def get_skill_text(self, skill_names: list[str] | None = None) -> str: lines = [] for name, skill in self._skills.items(): if skill_names and name not in skill_names: continue lines.append(f"技能名:{skill.name}") lines.append(f"适用场景:{skill.description}") lines.append(f"执行说明:\n{skill.prompt}") return "\n\n".join(lines)

在这个实现里,技能说明是否注入到 System prompt,由 skill_names 参数决定。最简单的激活策略是把所有技能说明都注入;更精细的做法是在用户输入前先做一次轻量分类,只激活相关技能。

4.2 ContextBuilder:把会话历史、技能说明、用户输入组装成一次请求

ContextBuilder 需要解决两个问题:历史顺序不能乱,上下文长度不能超。代码放在 app/context.py:

class ContextBuilder: def __init__(self, base_system_prompt: str, max_history_chars: int = 8000): self.base_system_prompt = base_system_prompt self.max_history_chars = max_history_chars @staticmethod def _estimate_cost(message: dict) -> int: text = message.get("content") if isinstance(text, str): return len(text) + 64 return 128 def build(self, session, user_input: str, skill_text: str = "", extra_context: str = "") -> list[dict]: system_prompt = self.base_system_prompt if skill_text: system_prompt += "\n\n可用技能说明:\n" + skill_text if extra_context: system_prompt += "\n\n检索参考内容:\n" + extra_context messages = [{"role": "system", "content": system_prompt}] recent = [] used = 0 for m in reversed(session.messages): cost = self._estimate_cost(m) if used + cost > self.max_history_chars: break recent.append(m) used += cost recent.reverse() messages.extend(recent) messages.append({"role": "user", "content": user_input}) return messages

这段代码里最关键的是 max_history_chars 预算。历史消息从后往前选择,优先保留最近的对话;从前往后截断会丢掉用户刚刚表达的意图,导致模型出现“失忆感”。

4.3 上下文加载里不要忽略技能说明的排序

System prompt 建议顺序是:角色定位、任务规则、技能说明、工具使用约束、输出格式、安全边界。

技能说明不能放在用户消息之后。如果模型已经读完一条具体用户请求,才知道“你拥有笔记工具”,它对工具的感知会明显降低。Agent 开发和 Prompt 调试的核心原则是:先给模型完整操作手册,再让模型看到具体任务。

5. Tool 工具调用闭环:从模型输出结构化参数到真实执行

5.1 先注册安全、可验证的本机工具

工具选择对演示效果很关键。这里选择三个笔记类工具,避免引入网络请求和系统命令,降低教学风险。代码放在 app/tools.py:

import json import os import re from pathlib import Path class ToolRegistry: def __init__(self): self._handlers = {} self.schemas = [] def register(self, name, description, parameters, handler): self._handlers[name] = handler self.schemas.append({ "type": "function", "function": { "name": name, "description": description, "parameters": parameters, }, }) def execute(self, name: str, arguments: dict): if name not in self._handlers: raise KeyError(f"未知工具:{name}") return self._handlers[name](**arguments) def has_tools(self) -> bool: return len(self.schemas) > 0

再定义三个实际函数:

def _safe_note_path(note_dir: str, title: str) -> Path: filename = re.sub(r"[^a-zA-Z0-9_-]", "_", title) + ".md" path = Path(note_dir) / filename return path def list_notes(note_dir: str) -> dict: path = Path(note_dir) path.mkdir(parents=True, exist_ok=True) files = [p.name for p in path.glob("*.md")] return {"ok": True, "count": len(files), "files": files} def save_note(note_dir: str, title: str, content: str) -> dict: path = _safe_note_path(note_dir, title) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") return {"ok": True, "path": str(path)} def read_note(note_dir: str, title: str) -> dict: path = _safe_note_path(note_dir, title) if not path.exists(): return {"ok": False, "error": "笔记不存在"} return {"ok": True, "content": path.read_text(encoding="utf-8")}

save_note 使用正则把标题里的特殊字符替换成下划线,防止路径穿越。工具函数返回 dict 的好处是结构清晰,能直接写入 Tool 消息。

在主程序中完成注册:

registry = ToolRegistry() registry.register( name="list_notes", description="列出笔记目录下所有 Markdown 笔记文件名", parameters={ "type": "object", "properties": {}, }, handler=lambda: list_notes(NOTE_DIR), ) registry.register( name="save_note", description="把内容保存为一条 Markdown 笔记,标题只用字母数字下划线连字符", parameters={ "type": "object", "properties": { "title": { "type": "string", "description": "笔记标题,不带扩展名", }, "content": { "type": "string", "description": "Markdown 正文", }, }, "required": ["title", "content"], }, handler=lambda title, content: save_note(NOTE_DIR, title, content), )

工具描述写得好不好,直接决定模型调用准确率。描述要写清触发条件、参数格式、边界限制,不能只写“保存笔记”。没有说明文件路径规则时,模型可能生成带斜杠的标题。

5.2 AgentRuntime:发起请求、接收工具调用、回填结果、循环收敛

工具调用的本质是循环,不是单次请求。第一轮模型可能返回 tool_calls,Agent 执行后把结果追加进去,再发起第二轮请求。代码可以直接写在 app/main.py:

import json from openai import OpenAI class AgentRuntime: def __init__(self, client: OpenAI, session, store, context_builder, tool_registry, model: str): self.client = client self.session = session self.store = store self.context_builder = context_builder self.tools = tool_registry self.model = model def run(self, user_input: str) -> str: messages = self.context_builder.build( self.session, user_input, skill_text="", ) while True: kwargs = {"model": self.model, "messages": messages} if self.tools.has_tools(): kwargs["tools"] = self.tools.schemas resp = self.client.chat.completions.create(**kwargs) assistant_msg = resp.choices[0].message tool_calls = getattr(assistant_msg, "tool_calls", None) assistant = { "role": "assistant", "content": assistant_msg.content or "", } if tool_calls: assistant["tool_calls"] = [] for tc in tool_calls: assistant["tool_calls"].append({ "id": tc.id, "type": "function", "function": { "name": tc.function.name, "arguments": tc.function.arguments, }, }) messages.append(assistant) if not tool_calls: self.session.add_user_message(user_input) self.session.add_assistant_message(assistant_msg.content or "") self.store.save(self.session) return assistant_msg.content or "" for tc in tool_calls: try: args = json.loads(tc.function.arguments or "{}") result = self.tools.execute(tc.function.name, args) result_payload = {"ok": True, "data": result} except Exception as exc: result_payload = {"ok": False, "error": str(exc)} messages.append({ "role": "tool", "tool_call_id": tc.id, "content": json.dumps(result_payload, ensure_ascii=False), })

这段循环有几个重要工程点:

  • 工具执行失败不要直接抛异常,应该把错误信息作为 tool 消息返回给模型,让模型决定是修正参数重试,还是向用户说明失败。
  • 每次循环都要把 assistant 消息先放进 messages,再放 tool 消息,顺序反了模型会把工具结果挂到错误对话上。
  • 只有最终得到普通回复时才写入 Session,工具调用中间过程不长期保存,否则 Session 文件会迅速膨胀。

5.3 完整的 CLI 入口

在 app/main.py 最后补上入口:

def main(): from app.session import SessionStore from app.context import ContextBuilder from app.tools import ToolRegistry session_store = SessionStore("data/sessions") session = session_store.load("demo-001") session.model = MODEL registry = ToolRegistry() # 注册三个笔记工具 builder = ContextBuilder( base_system_prompt="你是运行在本地的 Hermes Agent 助手,使用中文回答,工具返回值作为辅助事实。", max_history_chars=6000, ) runtime = AgentRuntime( client=client, session=session, store=session_store, context_builder=builder, tool_registry=registry, model=MODEL, ) print("Hermes Agent 已启动,输入 exit 退出") while True: user_input = input("你 > ").strip() if user_input.lower() in ("exit", "quit"): break answer = runtime.run(user_input) print("Agent >", answer) if __name__ == "__main__": main()

这个入口不是为了“能跑就行”,而是让每个组件都有明确生命周期。Session 在程序启动时加载,Context 按轮构建,Tool 使用全局注册表,模型调用集中在 AgentRuntime。真实项目在这些类之间补充日志、监控和配置中心时会更顺畅。

6. 运行验证:通过一次“保存笔记再读取”确认完整链路

6.1 预期运行过程

启动程序前先用命令行确认环境变量已加载:

python -c "from dotenv import load_dotenv; load_dotenv(); import os; print(bool(os.getenv('HERMES_API_KEY')))"

输出 True 表示 .env 配置读取成功。然后执行:

python -m app.main

第一次输入:

你 > 请保存一条 Session 使用笔记,标题是 session_os_replace,内容是:保存会话时要先写 tmp 文件再用 os.replace 替换。

模型会先返回一次工具调用。可以打印日志确认类似信息:

[ToolCall] save_note title: session_os_replace content: 保存会话时要先写 tmp 文件再用 os.replace 替换。 [ToolResult] {'ok': True, 'path': 'data/notes/session_os_replace.md'} Agent > 已保存到 data/notes/session_os_replace.md

继续输入:

你 > 读取刚才那条笔记,并说明里面讲了什么

这次模型会调用 read_note,再基于返回内容总结。

6.2 Session 文件验证

程序退出后查看会话目录:

ls data/sessions cat data/sessions/demo-001.json

文件里应包含 user 消息和 assistant 消息,工具调用过程不在 messages 中持久化。这是有意设计,避免 Session 文件被工具输出撑爆。

6.3 验证检查清单

检查项预期结果失败时排查方向
API 连通能返回一次普通回复Key、Base URL、模型名
Tool schema 生效模型不要求说明也会自动调用 save_note工具描述不清晰、模型不支持原生 tools
Tool 参数正确生成的标题不含斜杠和空格参数 description 要明确规则
Tool 结果回填模型能引用保存成功后的文件路径检查 tool_call_id 是否与 assistant 消息一致
Session 保存再次启动后能读取历史消息检查 data/sessions 权限和 JSON 文件是否完整
Context 截断长历史不会导致 context length exceeded调低 max_history_chars,或引入摘要压缩

完整的验证不是只确认“程序启动了”,而是确认模型确实感知到工具返回结果,并且能把结果用于下一轮回答。如果模型执行完 save_note 后仍然回答“我不知道你有没有保存成功”,说明 Tool 消息回填链路有问题,优先检查消息顺序和 tool_call_id。

7. 常见问题与排查路径

7.1 安装或首次启动时要求登录、网页授权

不少 Agent 产品在安装后第一次启动时,会要求打开网页完成账号绑定或 API Key 授权。这不是安装失败,而是产品把用户身份和模型服务绑定在一起。

处理顺序:

  1. 完整查看启动页提示的链接,不要在终端里反复回车跳过。
  2. 打开网页完成登录后,回到应用点击“已完成授权”或重启程序。
  3. 如果显示授权成功但状态未变,检查安装目录、系统代理、防火墙设置。
  4. 如果只想学习 Agent 工程原理,可以直接用本文这类纯 Python 工程,不依赖桌面安装包。

7.2 报错 context length exceeded

这个错误出现时,模型输入超过了窗口长度。先看报错发生在哪一步:如果发生在知识库内容加载后,说明检索片段太长;如果发生在多轮对话后期,说明 Session 历史没做截断。

解决方法是在 ContextBuilder 中压缩历史,同时限制检索片段返回长度。需要注意中文 token 估算不能用简单字符数,但本地先用字符数做粗粒度预算,再用实际 token 数调整,是性价比最高的改法。

7.3 Tool 调用结果看起来没问题,但模型还在乱编

出现这种现象,通常不是因为模型笨,而是模型没有看到完整结果。原因集中在三处:

  • Tool 返回内容过长被截断,模型只看到了前半段。
  • Tool 消息和 assistant tool_call 没有正确对应。
  • 工具结果只写在日志里,没有作为消息发回模型。

检查时把消息列表完整打印出来,按角色和顺序检查:

system user assistant(tool_calls) tool(tool_call_id=xxx) assistant(final)

只要 assistant tool_call 后缺少 tool 消息,模型在下一轮就只能猜测工具执行结果。

7.4 Session 文件保存失败或 JSON 损坏

先检查 data/sessions 目录是否存在、进程是否有写权限。再查看代码是否直接覆盖正式文件,强烈建议保留 tmp 加 os.replace 的写法。如果并发场景需要多个线程或进程同时写同一 Session,还需要引入文件锁或改为数据库存储,不能只在单进程脚本里做一次性覆盖。

问题现象常见原因处理建议
安装环境后 import openai 失败虚拟环境未激活或依赖没装好执行 pip list 检查,重新激活 venv
API Key 读取为空.env 不在当前目录或未调用 load_dotenv打印 os.getenv 结果,确认工作目录
模型返回内容中文乱码系统默认编码问题文件统一 UTF-8,终端切换 UTF-8
工具参数在中文标题下报错文件系统非法字符用白名单规则替换符号,禁止直接拼路径
多次运行后 Session 暴涨只做追加不做压缩设置历史条数和字符数上限

8. 部署和生产化建议:从能 demo 到能上线,中间还差四件事

8.1 学习环境与生产环境的差异

能力学习环境生产环境
配置.env 固定读本地配置中心或环境变量注入
Session 存储JSON 文件Redis、PostgreSQL,支持多实例
日志print结构化日志,带请求和会话 ID
安全本地信任鉴权、限流、审计、密钥加密
上下文粗粒度截断Token 预算 + 历史摘要 + 向量检索
发布源码直接运行容器化、镜像版本、回滚方案

minimal demo 能跑通,不代表可以直接暴露到公网。生产环境里 Session 文件按用户名隔离、Tool 白名单、文件路径校验、模型输出敏感词过滤、操作审计,每一条都不能少。

8.2 Skill 和 Tool 的扩展原则

Skill 越少越精确。每新增一个技能,都要评估它是否增加了信息量,还是只是重复了其他

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 15:52:49

VLDB 2026微软两篇论文解读:数据库系统研究与复现工程实践

好,直接开整。VLDB 2026 的认可名单里出现 Microsoft Research 的两篇论文,这个信号比“又发了 Paper”要重得多。做数据库系统的人应该都懂,VLDB 不是靠刷实验报告能进的会议,它对系统完整性、实验可复现性、工程实现深度的要求&…

作者头像 李华
网站建设 2026/9/5 15:48:13

Windows 下从零部署 pgvector:完整编译安装与验证向量搜索指南

Windows 下从零部署 pgvector:完整编译安装与验证向量搜索指南 【免费下载链接】pgvector Open-source vector similarity search for Postgres 项目地址: https://gitcode.com/GitHub_Trending/pg/pgvector 引言 pgvector 是为 PostgreSQL 提供向量相似性搜…

作者头像 李华
网站建设 2026/9/5 15:46:12

.NET 8构建企业级在线考试系统:跨平台、多数据库与国产化实战

简介:星期八在线考试系统是一套面向高校、职业院校及企事业单位的教学管理平台,解决大规模、高并发、强安全要求的在线考试数字化难题。系统基于.NET8构建,具备企业级稳定性与信创适配能力,支持国产数据库(人大金仓、达…

作者头像 李华
网站建设 2026/9/5 15:44:16

Pake GitHub Actions 构建指南:免本地环境在线打包网页桌面应用

Pake GitHub Actions 构建指南:免本地环境在线打包网页桌面应用 【免费下载链接】Pake 🤱🏻 Turn any webpage into a desktop app with one command. 项目地址: https://gitcode.com/GitHub_Trending/pa/Pake 本文基于 Pake 仓库的官…

作者头像 李华