
1. ML2025 Homework 1 到底在考什么Agentic RAG 的检索链路拆解ML2025 Homework 1 的核心任务是让你从零搭一个带 Agent 协作的检索增强生成系统Retrieval Augmented Generation with Agentic System。简单说RAG 就是让 LLM 在回答前先去外部知识里捞一把资料把捞到的内容拼进 prompt再让模型基于这些资料作答而 Agentic 的意思是这个流程不是一条直线走到底而是拆成几个各司其职的 Agent——有的负责把问题里的废话过滤掉有的负责抽关键词有的负责调搜索工具最后再交给生成模型收口。这套作业适合两类人一是正在上 ML2025 课程、需要跑通 Homework 1 的学生二是想自己动手理解 RAG 检索链路、但不想被各家 API Key 和模型接入折腾到崩溃的开发者。数据集是 90 道手工题答案都是一个词或短语分 public.txt带答案和 private.txt不带答案。由于 JudgeBoi 提交入口关闭我们直接本地用模型验证正确性就行。我踩过的坑是一开始想用本地量化模型硬扛结果检索和生成两头都卡后来换成统一 API 通道才顺起来。这篇就按「问题过滤 → 关键词抽取 → 检索 → 拼接 → 生成」这条链路把 config.toml 和 settings.json 的骨架、TaoToken 统一 Key 的接入方式、以及验证动作全部给出来你照着改就能跑。2. 前置准备用 TaoToken 统一 Key 打通 Agent 系统的模型通道Agentic RAG 最烦的地方在于过滤、抽词、生成这几个环节可能想用不同模型如果每个模型都去单独申请 Key、单独配 base_url配置文件会乱成一锅粥。TaoToken 的思路是给你一个统一的 API 通道和 KeyOpenAI 兼容格式你只要改 base_url 和 model 名就能切换后端模型Agent 系统里所有调用点共用一套凭证。你需要先拿到 Key进控制台创建 API Key地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。创建完复制那串 sk- 开头的字符串后面配置里会用到。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到参数不确定时对着查。这里要强调一点TaoToken 是合规的 API 聚合通道不是让你去搞什么灰色中转所有调用都走标准 HTTPS 接口。你只需要把 base_url 指向 https://taotoken.net/api 不要加任何 UTM 后缀到 API 地址上UTM 只用于官网跳转统计。环境依赖方面作业里用到的库一次性装齐python3 -m pip install openai langchain langchain-huggingface langchain-community \ faiss-cpu requests beautifulsoup4 charset-normalizer requests-html lxml_html_clean如果你要跑本地嵌入模型还需要 sentence-transformers 和 torch但本文的检索部分我们用 FAISS HuggingFaceEmbeddings模型选 chuxin-llm/Chuxin-Embedding向量维度 1024中文语料上表现够用。3. 可复制配置config.toml 与 settings.json 骨架把配置和代码分离是让 Agent 系统可维护的关键。下面这份 config.toml 定义了模型通道、Agent 角色、检索参数三块内容你直接复制改 Key 即可。# config.toml [api] base_url https://taotoken.net/api api_key sk-你的TaoToken密钥 timeout 60 max_retries 3 [models] # 过滤和抽词用轻量模型生成用能力更强的模型 filter_model gpt-4o-mini keyword_model gpt-4o-mini generation_model gpt-4o [retrieval] embedding_model chuxin-llm/Chuxin-Embedding chunk_size 100 chunk_overlap 10 top_k 3 public_corpus ./public.txt [agents.question_extraction] role 你是一个专门负责问题过滤的 AI 助手能从输入中识别出与问题相关的核心描述去除无关或冗余内容。 task 请从以下输入中筛选并保留与问题相关的部分删除与问题无关的句子或细节 [agents.keyword_extraction] role 你是一个关键词提取助手能从问题中找出最重要的词汇与短语帮助搜索工具更准确地找到答案。 task 请从以下问题中抽取最能代表问题核心的关键词或短语以逗号分隔settings.json 则用来存运行时开关和路径方便你在 notebook 里快速切换{ use_web_search: false, use_public_corpus: true, save_intermediate: true, log_level: INFO, output_dir: ./outputs, dataset: { public: ./public.txt, private: ./private.txt }, search: { provider: bocha, endpoint: https://api.bochaai.com/v1/ai-search, count: 3, freshness: oneYear } }读取配置的代码很短用 tomllibPython 3.11或 tomliimport json, tomllib from pathlib import Path with open(config.toml, rb) as f: cfg tomllib.load(f) with open(settings.json, r, encodingutf-8) as f: settings json.load(f) BASE_URL cfg[api][base_url] API_KEY cfg[api][api_key] GEN_MODEL cfg[models][generation_model]注意api_key 不要硬编码进 git 仓库本地用 .env 或环境变量注入更稳妥比如export TAOTOKEN_API_KEYsk-xxx代码里用os.environ.get读取。4. 检索链路实现从问题过滤到 FAISS 向量库4.1 Agent 调用封装作业里没法直接用 LLMAgent 类所以用字典定义 Agent 角色再写一个 call_agent 函数把 role task input 拼成完整 prompt 发给模型。这样每个 Agent 就是一个纯函数调试起来很清楚。from openai import OpenAI client OpenAI(base_urlBASE_URL, api_keyAPI_KEY) def call_agent(agent_name: str, input_text: str, model: str) - str: agent cfg[agents][agent_name] prompt agent[role] \n agent[task] \n input_text resp client.chat.completions.create( modelmodel, messages[{role: user, content: prompt}], temperature0.0, ) return resp.choices[0].message.content.strip()过滤 Agent 负责把「校歌为学校宣告或规定的代表该校的歌曲」这种背景描述删掉只留「虎山雄风飞扬是哪间学校的校歌歌词」抽词 Agent 则输出「虎山、雄风、飞扬、校歌、歌词」这类关键词。两个 Agent 串起来检索的 query 就干净多了。4.2 构建 FAISS 向量库检索部分用 RecursiveCharacterTextSplitter 把 public.txt 切块chunk_size 设 100、overlap 设 10再用 HuggingFaceEmbeddings 生成向量存进 FAISS。from langchain_huggingface import HuggingFaceEmbeddings from langchain.vectorstores import FAISS from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.docstore.document import Document embedding_model HuggingFaceEmbeddings( model_namecfg[retrieval][embedding_model] ) text_splitter RecursiveCharacterTextSplitter( chunk_sizecfg[retrieval][chunk_size], chunk_overlapcfg[retrieval][chunk_overlap], ) def build_faiss_index(extra_textsNone): with open(cfg[retrieval][public_corpus], r, encodingutf-8) as f: public_text f.read() public_docs text_splitter.split_documents( [Document(page_contentpublic_text)] ) all_docs list(public_docs) if extra_texts: extra_docs text_splitter.split_documents( [Document(page_contentt) for t in extra_texts] ) all_docs extra_docs vectorstore FAISS.from_documents(all_docs, embedding_model) retriever vectorstore.as_retriever( search_kwargs{k: cfg[retrieval][top_k]} ) return vectorstore, retriever4.3 检索与生成拼接拿到 retriever 后把关键词拼成 query 去检索取回 top_k 文档和原问题一起塞进生成 promptdef rag_answer(question: str) - str: filtered call_agent(question_extraction, question, cfg[models][filter_model]) keywords call_agent(keyword_extraction, filtered, cfg[models][keyword_model]) _, retriever build_faiss_index() docs retriever.invoke(keywords) context \n.join(d.page_content for d in docs) final_prompt ( f问题{filtered}\n关键词{keywords}\n f相关文档\n{context}\n请基于以上内容回答问题{filtered} ) resp client.chat.completions.create( modelGEN_MODEL, messages[{role: user, content: final_prompt}], temperature0.0, ) return resp.choices[0].message.content.strip()跑一道题看看问题「虎山雄风飞扬是哪间学校的校歌歌词」过滤后去掉背景描述抽词得到「虎山、雄风、飞扬、校歌、歌词」检索命中「光华国小」那条文档生成模型输出「虎山雄风飞扬是光华国小的校歌歌词」。链路通了。5. 验证请求与成功结果怎么确认真的跑通了验证分三层别只看最后答案对不对。第一层验证 API 通道。写个最小请求确认 TaoToken 的 Key 和 base_url 能通resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 回复 OK 两个字母}], ) print(resp.choices[0].message.content)如果返回 OK说明通道没问题如果报 401检查 Key 是否复制完整报 404检查 base_url 是不是写成了带路径的形式正确写法就是https://taotoken.net/api。第二层验证检索命中。单独打印 retriever.invoke(keywords) 的结果看命中的文档里有没有包含答案。如果 top_k3 都没命中先把 chunk_size 调小到 80 试试或者把关键词抽取得更聚焦。第三层验证生成答案。用 public.txt 里带答案的题跑一批统计准确率。作业里 90 道题public 部分能到 70% 以上就算链路健康。private.txt 没答案但你可以人工抽查几条看生成结果是否合理。成功跑通时控制台会依次打印过滤后的问题、抽取的关键词、检索到的文档条数、最终答案。把这四段日志存到 outputs 目录方便复盘。6. 本篇常见错排查报错一ModuleNotFoundError: No module named langchain_huggingface说明装的是旧版 langchain新版把 HuggingFaceEmbeddings 拆到独立包了。执行pip install -U langchain langchain-huggingface即可。报错二FAISS 建索引时报维度不匹配多半是 embedding_model 换了但没重建索引。FAISS 索引和嵌入模型绑定换模型必须重新 from_documents不能复用旧索引文件。报错三检索结果全是无关文档检查关键词抽取 Agent 的输出是不是把整段 prompt 都吐回来了。如果模型没按「逗号分隔」格式输出在 task 描述里加一句「只输出关键词不要解释」并把 temperature 设为 0。报错四API 返回 429 限流Agent 系统一次问答会发 3 次请求过滤、抽词、生成批量跑 90 道题就是 270 次调用。在 client 初始化时加 max_retries并在循环里加time.sleep(0.5)做节流。报错五web 搜索返回空作业里用博查 AI 搜索做 web 检索实测下来对单句问句效果一般返回的 summary 经常和问题不相关。建议优先用 public.txt 本地语料web 搜索作为补充且只在本地检索没命中时才触发。报错六生成答案带一堆解释文字生成 prompt 末尾加「只输出答案本身不要复述问题」并把 temperature 压到 0。作业答案是一个词或短语格式约束很重要。7. 下一步把统一 Key 用到长期编码与 Agent 任务跑通 Homework 1 之后你会发现 Agentic RAG 的骨架可以复用到很多场景文档问答、代码库检索、多轮工具调用。这时候如果每次实验都手动配 Key、切模型效率会被拖垮。TaoToken 的 Coding Plan 适合长期编码和 Agent 类任务模型通道统一管理地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。如果你想先在网页端快速验证某个模型对检索结果的理解能力可以用模型对话入口 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 把检索到的 context 粘进去看模型怎么答再决定要不要写进代码。接入文档里对 OpenAI 兼容格式、参数、错误码都有说明遇到配置问题先查 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 建议给不同项目建不同的 Key方便追踪用量。最后留一个实用技巧把 config.toml 里的模型名做成环境变量覆盖比如TAOTOKEN_GEN_MODELgpt-4o这样你在 notebook 里切换模型不用改文件直接改环境变量重启内核就行。检索链路的 chunk_size 和 top_k 是最值得调的两个参数先固定模型只调这两个观察准确率变化比盲目换模型有效得多。