零基础实现背景知识记忆智能体:以“谁发明了钢琴键”为例

发布时间:2026/9/1 21:55:18
零基础实现背景知识记忆智能体:以“谁发明了钢琴键”为例 最近在做一个知识问答类的小型智能体时遇到一个特别典型的需求让智能体记住一些固定的背景知识比如“谁发明了钢琴键”这样的事实型问题。这类问题看起来简单但直接丢给大模型往往得到的是模棱两可甚至错误的回答。网上关于智能体搭建的资料很多但大部分停留在概念介绍真正能把“知识记忆”这个能力落地的最小实现却很少。这篇文章我会围绕“记住谁发明了钢琴键背景智能体”这个主题完整拆解一个最小可运行的背景知识记忆型智能体。你不需要先掌握复杂的 Agent 框架只需要有 Python 基础跟着一步步做就能实现一个能检索知识、调用大模型、还能持续记忆新知识的智能体。如果你是零基础前面两节可以帮你理解智能体和背景知识的关系如果你已经有开发经验可以直接跳到完整代码实现部分。1. 背景与核心概念1.1 什么是智能体智能体Agent这个词在最近两年频繁出现在各类技术文章中泛指一类能够感知输入、做出决策、执行动作、并具备一定记忆能力的 AI 程序。一个最简单的智能体通常包含四个部分感知模块接收用户输入例如问题、指令或外部事件。决策模块判断当前应该做什么例如是直接回答还是先查知识库。行动模块实际执行动作例如调用大模型、查询数据库、调用外部 API。记忆模块保存短期对话上下文和长期背景知识。普通问答机器人只有“输入 → 大模型 → 输出”这一条链路它没有记忆也无法控制知识来源。而智能体的核心区别在于它可以在回答问题之前先做检索、查资料、组装上下文再交给大模型生成答案。这样得到的答案更可控也更可靠。1.2 为什么记忆对智能体很重要智能体的“记忆”可以分为两类短期记忆当前对话的上下文例如用户刚才问了什么、助手上一轮回答了什么。长期记忆固定不变或很少变化的知识例如“谁发明了钢琴键”这种历史事实。对于事实型问题长期记忆是最关键的。大模型虽然训练数据里包含大量知识但它对具体的事实细节可能记不准确而且一旦涉及冷门知识很容易一本正经地给出错误答案。所以工程上更稳妥的做法是把要回答的事实提前整理成结构化知识库让智能体在回答前先检索相关知识再结合知识库内容生成回答。这就是常说的检索增强生成RAG思路。本文的“背景知识记忆智能体”本质上就是一个极简版 RAG 应用。1.3 本文要做成的智能体长什么样本文要构建的智能体核心能力如下接收用户问题例如“谁发明了钢琴键”。从内置知识库中检索相关知识条目。将检索到的知识作为上下文交给大模型组织语言。返回一段基于知识库内容的准确回答。支持用户补充新知识并能把新知识写入知识库实现“记住”的效果。整个项目代码量不大但工作流是完整的。你可以把它理解成一个可以不断扩展的智能体雏形。2. 先把知识点搞清楚谁发明了钢琴键在做智能体之前有一个问题必须先解决知识本身要准确。如果知识库里的内容都是错的那智能体再聪明也没有用。2.1 现代钢琴的发明者关于“谁发明了钢琴键”这个问题准确地说现代钢琴及其键盘击弦机构是由意大利人巴托罗密欧·克里斯托弗里Bartolomeo Cristofori1655—1731发明的。他是佛罗伦萨美第奇家族的乐器制造师在约1700年前后设计出一种新的键盘乐器通过琴槌敲击琴弦发声演奏者可以通过触键力度控制音量强弱因此这种乐器最初被称为“能强能弱的羽管键琴”后来演化为钢琴。需要说明的是键盘乐器本身的历史比钢琴更早。管风琴、羽管键琴、击弦古钢琴都早于现代钢琴。克里斯托弗里的贡献是把“拨弦发声”改成了“敲弦发声”让键盘乐器第一次拥有了表现力丰富的强弱变化。2.2 钢琴键的规格演变今天我们常见的钢琴是 88 键这个规格并不是克里斯托弗里发明的而是经过一百多年演变后由施坦威等钢琴制造商在 19 世纪后期逐步确立并推广开来的。早期克里斯托弗里制作的钢琴只有 49 键左右后来随着音乐作品的表现需求增加键盘范围不断扩展。所以如果用户问“谁发明了钢琴键”或“谁发明了钢琴”知识库中应该给出准确的回答并且可以补充 88 键规格的背景这样智能体的回答会更完整也更有深度。2.3 把知识设计成结构化条目为了让智能体能检索知识我们需要把知识整理成结构化条目。每条知识建议包含以下字段id知识条目的唯一编号。topic主题分类例如“钢琴历史”。questions可能触发该知识的典型问题用于检索匹配。content知识的具体内容。source知识来源用于溯源和防止错误信息。把知识结构化是为了让检索模块能够快速定位到相关内容。后面在代码实现中你会看到这些字段的实际用法。3. 环境准备与整体设计3.1 运行环境说明本文示例以 Python 3.9 为运行环境依赖较少只需要安装 requests 库用于调用大模型 HTTP 接口。如果你本地还没有 Python 环境建议先安装 Python 3.9 及以上版本并配置好 pip。需要特别说明的是大模型接口部分以常见的 OpenAI 兼容接口为例不同服务商地址和模型名称可能不同实际使用时需要根据你的项目情况调整。3.2 智能体工作流设计在写代码之前先把工作流设计清楚。整个智能体的运行流程如下用户输入问题 ↓ 检索模块在知识库中查找相关条目 ↓ 组装系统提示词包含检索到的知识 ↓ 调用大模型接口生成回答 ↓ 输出回答给用户 ↓ 用户可补充新知识写入知识库这个流程是典型的“检索-增强-生成”链路。检索模块负责从知识库中找候选内容提示词组装负责把候选内容变成大模型的上下文大模型负责把知识组织成自然流畅的回答。3.3 项目结构规划为了让代码清晰我们把项目拆成多个文件piano_agent/ ├── knowledge_base.py # 知识库定义与持久化 ├── retriever.py # 检索模块 ├── llm_client.py # 大模型调用模块 ├── agent.py # 智能体主流程 └── main.py # 命令行交互入口每个文件职责单一便于扩展。下面逐个实现。4. 完整代码实现4.1 知识库模块知识库模块负责定义知识条目并提供读取、添加、保存知识的功能。为了简单起见我们使用 JSON 文件作为持久化存储这样智能体记住的新知识在程序重启后也不会丢失。# 文件路径piano_agent/knowledge_base.py import json import os KNOWLEDGE_FILE knowledge.json DEFAULT_KNOWLEDGE [ { id: 1, topic: 钢琴历史, questions: [谁发明了钢琴, 钢琴是谁发明的, 谁发明了钢琴键, 钢琴的发明者是谁], content: 现代钢琴由意大利人巴托罗密欧·克里斯托弗里Bartolomeo Cristofori1655—1731发明。他在约1700年前后设计出用琴槌敲击琴弦发声的键盘乐器演奏者可以通过触键力度控制音量强弱。, source: 公开音乐史资料 }, { id: 2, topic: 钢琴历史, questions: [钢琴键有多少个, 88键是谁规定的, 钢琴键盘规格], content: 现代钢琴通常为88键即52个白键和36个黑键。这个规格在19世纪后期由施坦威等钢琴制造商逐步确立并推广克里斯托弗里早期制作的钢琴只有约49键。, source: 公开音乐史资料 }, { id: 3, topic: 钢琴原理, questions: [钢琴和羽管键琴有什么区别, 钢琴为什么能控制强弱], content: 钢琴与羽管键琴的核心区别在于发声方式羽管键琴用拨子拨弦音量基本固定钢琴用琴槌敲击琴弦琴槌击弦后立即弹开琴弦自由振动因此演奏者可以通过不同的触键力度控制音量变化。, source: 公开乐器声学资料 } ] def load_knowledge(): 从 JSON 文件加载知识库若文件不存在则使用默认知识。 if os.path.exists(KNOWLEDGE_FILE): with open(KNOWLEDGE_FILE, r, encodingutf-8) as f: return json.load(f) return list(DEFAULT_KNOWLEDGE) def save_knowledge(knowledge_base): 将知识库保存到 JSON 文件。 with open(KNOWLEDGE_FILE, w, encodingutf-8) as f: json.dump(knowledge_base, f, ensure_asciiFalse, indent2) def add_knowledge(knowledge_base, topic, questions, content, source): 向知识库中添加一条新知识并返回新的知识库列表。 new_id max(item[id] for item in knowledge_base) 1 knowledge_base.append({ id: new_id, topic: topic, questions: questions, content: content, source: source }) save_knowledge(knowledge_base) return knowledge_base这里默认知识库里放了三条和钢琴背景相关的知识。add_knowledge 函数用于支持用户补充新知识这是实现“记住”能力的关键。4.2 检索模块检索模块负责根据用户问题从知识库中找到最相关的知识条目。为了保证代码简单且可控我们使用关键词匹配加打分的方式每条知识中配置了 questions 字段如果用户问题包含某个关键词就给该条目加分。# 文件路径piano_agent/retriever.py def retrieve(knowledge_base, question, top_k2): 根据问题从知识库中检索最相关的知识条目。 返回得分最高的 top_k 条知识。 scored [] for item in knowledge_base: score 0 # 在问题关键词列表中进行匹配 for q in item.get(questions, []): # 简单分词按常见标点和空格切分 words [w for w in question.replace(, ).replace(?, ).split() if w] # 如果问题中的词出现在问题模板里则加分 for w in words: if w in q: score 1 # 如果整个问题模板出现在问题中则额外加分 if q in question: score 3 # 在 content 中匹配关键词 for keyword in [钢琴, 发明, 键盘, 88, 琴槌, 羽管键琴]: if keyword in question and keyword in item[content]: score 1 if score 0: scored.append((score, item)) scored.sort(keylambda x: x[0], reverseTrue) return [item for _, item in scored[:top_k]]这个检索算法并不复杂但对目前的知识库规模已经够用。真实项目中通常会使用向量检索或更完善的全文检索引擎来替代但核心思路是一致的先召回候选内容再交给大模型精炼回答。4.3 大模型调用模块大模型调用模块使用 requests 库调用兼容 OpenAI 接口的 chat completions 地址。API Key 从环境变量中读取避免硬编码到代码仓库中。# 文件路径piano_agent/llm_client.py import os import requests def chat_with_llm(system_prompt, user_question): 调用大模型接口返回模型生成的回答文本。 api_key os.environ.get(LLM_API_KEY) base_url os.environ.get(LLM_BASE_URL, https://api.openai.com/v1) model os.environ.get(LLM_MODEL, gpt-3.5-turbo) if not api_key: raise RuntimeError(未找到 LLM_API_KEY 环境变量) url f{base_url}/chat/completions headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: model, messages: [ {role: system, content: system_prompt}, {role: user, content: user_question} ], temperature: 0.3 } resp requests.post(url, headersheaders, jsonpayload, timeout30) resp.raise_for_status() data resp.json() return data[choices][0][message][content]设置 temperature 为 0.3是希望模型在组织语言时保守一些减少自由发挥。对于事实型问题低温度更合适。4.4 智能体主流程智能体主流程把知识库、检索、大模型调用串联起来。核心逻辑是先检索知识然后把知识放入系统提示词最后让大模型基于这些知识回答。# 文件路径piano_agent/agent.py from knowledge_base import load_knowledge from retriever import retrieve from llm_client import chat_with_llm def build_system_prompt(context_items): 把检索到的知识条目组装成 system prompt。 knowledge_text \n\n.join( f【知识{idx 1}】\n主题{item[topic]}\n内容{item[content]}\n来源{item[source]} for idx, item in enumerate(context_items) ) prompt ( 你是一个知识问答助手。请优先根据下面的背景知识回答问题。\n 如果背景知识足以回答请直接基于背景知识组织答案并保持简洁准确。\n 如果背景知识与问题无关或无法回答请如实说明不要编造事实。\n\n f背景知识\n{knowledge_text} ) return prompt def run_agent(question): 智能体入口接收问题返回回答。 knowledge_base load_knowledge() context_items retrieve(knowledge_base, question) # 即使没有检索到知识也保留空上下文让模型知道自己没有足够资料 system_prompt build_system_prompt(context_items) answer chat_with_llm(system_prompt, question) return answer这里有一个重要的设计细节当检索不到知识时我们依然调用大模型但提示词中会明确告知模型“没有足够的背景知识”这样模型通常会回答“我无法从现有知识库中找到答案”而不是强行编造。4.5 命令行交互入口main.py 提供命令行交互循环同时支持用户通过特定指令向知识库补充新知识。# 文件路径piano_agent/main.py from knowledge_base import load_knowledge, add_knowledge, save_knowledge from agent import run_agent def main(): print(钢琴背景知识智能体已启动。) print(输入问题开始提问输入 /remember 补充新知识输入 /quit 退出。) while True: user_input input(\n你).strip() if user_input /quit: print(智能体已退出。) break if user_input /remember: topic input(请输入主题).strip() content input(请输入知识内容).strip() source input(请输入来源可为空).strip() knowledge_base load_knowledge() add_knowledge( knowledge_base, topictopic or 用户补充, questions[], contentcontent, sourcesource or 用户手动补充 ) print(已记住该知识。) continue try: answer run_agent(user_input) print(f智能体{answer}) except Exception as e: print(f调用出错{e}) if __name__ __main__: main()4.6 运行与验证在运行之前需要先配置大模型接口的环境变量。以 Linux 或 macOS 为例export LLM_API_KEY你的API密钥 export LLM_BASE_URLhttps://api.openai.com/v1 export LLM_MODELgpt-3.5-turboWindows 下的命令类似set LLM_API_KEY你的API密钥 set LLM_BASE_URLhttps://api.openai.com/v1 set LLM_MODELgpt-3.5-turbo然后进入项目目录启动交互程序cd piano_agent python main.py输入“谁发明了钢琴键”预期输出大致如下你谁发明了钢琴键 智能体根据背景知识现代钢琴由意大利人巴托罗密欧·克里斯托弗里发明他在约1700年前后设计出用琴槌敲击琴弦发声的键盘乐器。克里斯托弗里的这项发明让演奏者可以通过触键力度控制音量强弱这也是钢琴区别于早期羽管键琴的关键。注意实际输出文本会因模型不同而略有差异但核心事实应该和知识库保持一致。再测试一下知识补充能力你/remember 请输入主题钢琴家 请输入知识内容弗里德里克·肖邦是波兰作曲家和钢琴家被誉为“钢琴诗人”。 请输入来源可为空公开音乐史资料 已记住该知识。然后输入“肖邦是什么人”智能体就会从新写入的知识库中检索到相关内容。这就实现了“记住”的效果。5. 不写代码也能搭低代码平台思路如果你不想维护 Python 代码或者想快速验证智能体效果可以考虑使用 Coze扣子、Dify 这类智能体搭建平台。这些平台普遍提供了知识库、工作流、模型配置等可视化能力。5.1 在 Coze扣子中搭建在 Coze 中搭建一个“背景知识记忆智能体”通常遵循以下流程创建智能体填写人设和回复逻辑。在知识库中上传包含“钢琴发明者”“88键规格”等内容的文档。在提示词中明确要求“优先基于知识库内容回答知识库不足时如实说明”。发布后在对话窗口中测试“谁发明了钢琴键”一类的问题。对于本文的场景知识库文件可以直接用 Markdown 或 TXT 编写内容就是克里斯托弗里和钢琴键规格的背景资料。平台会自动把文档切分并建立索引开发者不需要自己写检索代码。5.2 在 Dify 中搭建Dify 同样提供知识库功能。你可以创建知识库后上传文档再创建一个聊天应用把知识库挂载到应用中并设置模型和提示词。这样用户的每个问题都会先经过知识库检索再交给模型生成回答。低代码平台的优点是快速、可视化、便于非技术人员参与维护知识库。缺点是对检索逻辑、模型参数、部署环境的可控性相对较弱。如果只是验证业务场景推荐先用低代码平台跑通流程再决定是否需要自研。5.3 提示词模板参考无论使用代码方案还是低代码平台提示词都很关键。下面这个模板可以直接套用你是一个背景知识问答助手。 回答问题时请遵循以下规则 1. 优先依据知识库中检索到的背景知识进行回答。 2. 如果知识库内容与问题相关请用简洁的语言组织答案必要时可以补充说明。 3. 如果知识库中找不到相关内容请明确回答“知识库中没有找到相关信息”不要编造事实。 4. 回答中涉及事实时尽量保留知识来源中的关键信息。这个模板的核心目的是约束模型有知识就用知识没知识就承认不知道。这也是背景知识记忆型智能体最重要的行为边界。6. 常见问题与排查思路在实际运行过程中会遇到一些典型问题下面整理了一份排查清单。问题现象常见原因解决思路启动时报错“未找到 LLM_API_KEY 环境变量”没有配置环境变量或配置后未重新打开终端检查环境变量配置命令然后在当前终端重新执行 export/set 命令请求大模型接口超时网络不稳定或模型服务响应较慢检查网络连通性适当调大 timeout 参数或更换响应更快的模型回答内容没有使用知识库检索模块没有命中知识条目在 run_agent 中打印 context_items检查检索得分逻辑是否覆盖了用户问题知识补充后重启丢失没有调用 save_knowledge 保存 JSON 文件确认 add_knowledge 中已经调用 save_knowledge并检查当前工作目录是否有写入权限模型回答出现编造事实提示词没有明确约束“无法回答时如实说明”强化 system prompt 中的约束同时把 temperature 调低例如 0.2 到 0.3中文问题分词效果差简单按空格切分无法覆盖中文连续文本可以引入 jieba 分词或使用包含完整问题模板的匹配方式API 接口返回 404 或 401接口地址配置错误或 API Key 无效检查 LLM_BASE_URL 是否符合服务商文档检查 API Key 权限6.1 如何确认检索是否命中当回答不符合预期时建议先做模块级验证。你可以在 Python 交互环境中单独测试检索from knowledge_base import load_knowledge from retriever import retrieve kb load_knowledge() result retrieve(kb, 谁发明了钢琴键) for item in result: print(item[id], item[content])如果这里没有输出知识条目说明检索逻辑没有覆盖到用户问题需要调整 questions 列表或关键词匹配规则。6.2 如何验证知识持久化补充知识后可以查看项目目录下的 knowledge.json 文件cat knowledge.json如果文件内容中包含刚刚补充的知识条目说明持久化正常。如果文件不存在请检查运行命令时的工作目录是否正确。7. 最佳实践与工程建议把智能体从“能跑”升级到“能上线”还需要关注以下几个维度。7.1 知识溯源与质量控制背景知识智能体的价值很大程度取决于知识库的质量。在为知识库添加条目时建议保留来源字段并尽量引用公开、可查证的信息。对于不确定的历史细节宁可在内容中标注“存在争议”也不要让模型给出斩钉截铁的结论。在团队协作场景中可以为知识库增加审核状态字段例如 draft、reviewed、published新知识只有审核通过后才进入线上检索范围。7.2 提示词与知识上下文的隔离不要把知识直接写死在系统提示词中也不要把所有知识都塞进每轮请求。正确做法是动态检索出最相关的 top_k 条知识再拼接到提示词中。这样既能控制 token 消耗也能减少无关知识对模型回答的干扰。当知识库条目很多时建议为知识条目增加权重字段并在检索打分时考虑权重让重要知识更容易被召回。7.3 安全边界API Key 必须通过环境变量或密钥管理服务注入绝不能硬编码到代码仓库中。如果需要提交代码到 Git 仓库建议在 .gitignore 中排除环境变量文件和知识库 JSON 文件避免敏感信息泄露。同时要注意知识库中不应存放个人隐私、账号密码、内部敏感数据。如果需要接入企业内部知识应该严格遵循最小权限原则并增加访问控制。7.4 日志与可观测性在 agent 调用链路上建议输出结构化日志至少包含以下信息用户原始问题。检索命中的知识条目 id 和得分。发送给模型的 system prompt 长度。模型返回结果。接口调用耗时。有了日志当回答出现问题时才能快速判断是检索问题还是模型生成问题。7.5 从单智能体到多智能体的扩展如果后续业务复杂度上升可以把当前代码中的模块拆成独立的子智能体知识检索智能体专门负责从知识库中找到候选内容。对话管理智能体负责维护多轮上下文。答案生成智能体根据检索结果生成最终回答。这就是多智能体架构的雏形。你可以基于本文的最小实现逐步加入意图识别、工具调用、记忆管理等能力向企业级智能体演进。8. 下一步学习路线到这里一个能“记住谁发明了钢琴键”的背景知识智能体已经搭建完成。你掌握了从知识结构化、检索、提示词组装到大模型调用的完整链路也理解了为什么背景知识型智能体不能只依赖模型本身。下一步你可以在两个方向上继续深入。一是替换检索模块学习向量数据库和 embedding 的使用让智能体能处理更大规模、更复杂的知识库。二是研究智能体框架例如 LangChain、Dify、Coze 等理解工作流编排和企业级部署的常见模式。建议你先别急着研究复杂框架而是把本文的项目改造成你自己熟悉的领域比如换成公司产品手册、考试知识点、历史人物介绍。自己动手加几条知识、改一版提示词、测一轮问答比看十篇概念文章更有用。如果本文对你有帮助可以收藏备用后续替换知识库时直接拿来改。

相关新闻