1. 从两个 Key 到一条链路:LangChain 1.0.2 全栈开发里最烦的那件事
LangChain 1.0.2 全栈开发教程里,RAG 检索链和 Agents 工具调用是两条主线,但真正卡住大多数人的不是链怎么写,而是 Key 怎么管。RAG 要调 Embedding 模型,Agents 要调对话模型,工具调用还要再调一次模型,一个项目里散落着三四个不同厂商的 API Key,改一个环境变量就得翻半天配置文件。LangChain 1.0.2 本身把init_chat_model和bind_tools的接口统一了,但模型供应商的 base_url 和 api_key 还是各写各的。
这篇教程面向需要统一管理多模型 Key 的开发者,给出config.toml与settings.json骨架,把 TaoToken 作为统一 Key 和 API 通道接进 LangChain 1.0.2 的 RAG 链与 Agent 工具调用里,最后附一条 RAG 问答和一条 Agent 工具调用的可复制验证动作。适合已经跑通过 LangChain 基础 demo、准备把 RAG 和 Agents 拼成一个完整应用的人。如果你还在纠结用哪个 Embedding 模型、工具调用返回的tool_calls怎么解析,这篇会把这些环节串起来。
我试过在一个项目里同时接三个厂商的 Key,结果.env文件里六行变量,换台机器就报AuthenticationError。后来把模型通道收敛到一个入口,配置从六行降到两行,RAG 和 Agent 共用同一套凭证,排障时只需要看一个地方。
2. TaoToken 前置:统一 Key 与 API 通道的接入位置
TaoToken 在这里扮演的角色是模型调用的统一入口。LangChain 1.0.2 的init_chat_model支持传入base_url和api_key,只要这个入口兼容 OpenAI 的 Chat Completions 协议,RAG 里的对话模型和 Agent 里的推理模型都能走同一条通道。Embedding 部分如果也用兼容接口,同样可以收敛进来。
接入位置有三个:一是config.toml里放模型名和 base_url,二是settings.json里放运行时参数,三是环境变量里放 Key。这样做的目的是让 RAG 链和 Agent 共享同一份模型配置,而不是各写各的。
先拿到 Key。访问 https://taotoken.net/api-keys 创建,注意这个页面带 UTM 参数,创建后复制保存。API 通道地址是 https://taotoken.net/api ,这个地址不带 UTM,直接作为base_url使用。模型对话入口在 https://taotoken.net/models ,可以在这里确认你要用的模型名。接入文档在 https://taotoken.net/doc ,配置项对不上时回来查。
注意:Key 只放环境变量或本地配置文件,不要提交到 Git。
.env和config.toml都要进.gitignore。
3. 可复制配置:config.toml 与 settings.json 骨架
3.1 config.toml 骨架
config.toml放模型定义,RAG 和 Agent 都从这里读。LangChain 1.0.2 本身不解析 toml,我们用 Python 标准库tomllib(3.11+)或tomli读进来,再传给init_chat_model。
# config.toml [provider] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" [models.chat] name = "gpt-4o-mini" temperature = 0.2 max_tokens = 2048 [models.agent] name = "gpt-4o-mini" temperature = 0.0 max_tokens = 4096 [models.embedding] name = "text-embedding-3-small" dim = 1536 [rag] chunk_size = 400 chunk_overlap = 50 top_k = 3 [agent] max_iterations = 8这里base_url统一指向 TaoToken 的 API 通道,api_key_env指向环境变量名,不直接写 Key。models.chat给 RAG 用,models.agent给 Agent 用,models.embedding给向量化用。三个模型走同一个 base_url,Key 也同一份。
3.2 settings.json 骨架
settings.json放运行时开关,方便不改代码切换行为。
{ "env": "dev", "tracing": false, "rag": { "vector_store": "milvus_lite", "collection": "demo_collection", "db_path": "./milvus_demo.db" }, "agent": { "enable_tools": true, "tool_timeout": 15, "stream": true }, "logging": { "level": "INFO", "log_tool_calls": true } }tracing关掉时不上报链路,本地调试够用。log_tool_calls打开后,Agent 每次工具调用的入参和返回都会打到日志,排障时不用猜模型到底调了什么。
3.3 加载配置的代码
# settings_loader.py import os import json import tomllib from pathlib import Path def load_config(config_path: str = "config.toml") -> dict: with open(config_path, "rb") as f: cfg = tomllib.load(f) api_key = os.getenv(cfg["provider"]["api_key_env"]) if not api_key: raise RuntimeError(f"环境变量 {cfg['provider']['api_key_env']} 未设置") cfg["provider"]["api_key"] = api_key return cfg def load_settings(settings_path: str = "settings.json") -> dict: with open(settings_path, "r", encoding="utf-8") as f: return json.load(f) if __name__ == "__main__": cfg = load_config() st = load_settings() print("base_url:", cfg["provider"]["base_url"]) print("chat model:", cfg["models"]["chat"]["name"]) print("env:", st["env"])运行前设置环境变量:
export TAOTOKEN_API_KEY="你的Key"Windows CMD 用set TAOTOKEN_API_KEY=你的Key。跑python settings_loader.py,能打印出 base_url 和模型名就说明配置读通了。
3.4 把配置接进 LangChain 1.0.2
# model_factory.py from langchain.chat_models import init_chat_model from settings_loader import load_config def build_chat_model(role: str = "chat"): cfg = load_config() m = cfg["models"][role] return init_chat_model( model=m["name"], model_provider="openai", base_url=cfg["provider"]["base_url"], api_key=cfg["provider"]["api_key"], temperature=m["temperature"], max_tokens=m["max_tokens"], )role传chat或agent,分别拿到 RAG 用和 Agent 用的模型实例。两者 base_url 和 Key 相同,只有温度和 max_tokens 不同。这样 RAG 链和 Agent 共享同一套凭证,换模型只改config.toml。
4. 验证请求:一条 RAG 问答与一条 Agent 工具调用
4.1 RAG 问答验证
RAG 链的核心是检索加生成。这里用 Milvus Lite 做本地向量库,Embedding 走 TaoToken 通道。先建库、灌数据,再跑问答。
# rag_demo.py import os from pymilvus import MilvusClient, DataType from langchain.chat_models import init_chat_model from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough from settings_loader import load_config, load_settings cfg = load_config() st = load_settings() client = MilvusClient(uri=st["rag"]["db_path"]) collection = st["rag"]["collection"] def build_schema(): return ( MilvusClient.create_schema(auto_id=True, enable_dynamic_field=True) .add_field(field_name="id", datatype=DataType.INT64, is_primary=True) .add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=cfg["models"]["embedding"]["dim"]) .add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2048) ) def build_index(): p = MilvusClient.prepare_index_params() p.add_index(field_name="vector", index_type="AUTOINDEX", metric_type="L2") return p if client.has_collection(collection): client.drop_collection(collection) client.create_collection(collection_name=collection, schema=build_schema(), index_params=build_index()) # 用 TaoToken 通道做 embedding from openai import OpenAI oai = OpenAI(base_url=cfg["provider"]["base_url"], api_key=cfg["provider"]["api_key"]) def embed(texts): resp = oai.embeddings.create(model=cfg["models"]["embedding"]["name"], input=texts) return [d.embedding for d in resp.data] docs = [ "LangChain 1.0.2 的 create_agent 使用 LangGraph 构建基于图的运行时。", "RAG 的检索阶段先算查询向量,再在向量库中找相似向量。", "TaoToken 提供统一的 API 通道,base_url 是 https://taotoken.net/api 。", ] vecs = embed(docs) client.insert(collection_name=collection, data=[ {"vector": v, "text": t} for v, t in zip(vecs, docs) ]) def retrieval(query: str): qv = embed([query])[0] res = client.search( collection_name=collection, data=[qv], anns_field="vector", search_params={"metric_type": "L2"}, output_fields=["text"], limit=cfg["rag"]["top_k"], ) return "\n".join(hit["entity"]["text"] for hit in res[0]) llm = init_chat_model( model=cfg["models"]["chat"]["name"], model_provider="openai", base_url=cfg["provider"]["base_url"], api_key=cfg["provider"]["api_key"], temperature=cfg["models"]["chat"]["temperature"], ) prompt = ChatPromptTemplate.from_messages([ ("system", "根据上下文回答问题,不要编造。\n\n上下文:\n{context}"), ("human", "{query}"), ]) chain = ( {"query": RunnablePassthrough(), "context": lambda x: retrieval(x)} | prompt | llm | StrOutputParser() ) print(chain.invoke("TaoToken 的 base_url 是什么?"))跑起来后应该输出类似「TaoToken 的 base_url 是 https://taotoken.net/api」。如果检索不到,检查top_k和 embedding 维度是否和 schema 里的dim一致。这条链验证了 RAG 的检索加生成两段都走通了 TaoToken 通道。
4.2 Agent 工具调用验证
Agent 部分用create_agent加一个自定义工具,验证工具调用链路。
# agent_demo.py from langchain.tools import tool from langchain.agents import create_agent from langchain.chat_models import init_chat_model from settings_loader import load_config, load_settings cfg = load_config() st = load_settings() @tool def query_order(order_id: str) -> str: """根据订单号查询订单状态。""" fake_db = { "A1001": "已发货,预计明天到达", "A1002": "待付款", "A1003": "已签收", } return fake_db.get(order_id, "订单不存在") llm = init_chat_model( model=cfg["models"]["agent"]["name"], model_provider="openai", base_url=cfg["provider"]["base_url"], api_key=cfg["provider"]["api_key"], temperature=cfg["models"]["agent"]["temperature"], ) agent = create_agent( model=llm, tools=[query_order], system_prompt="你是订单助手,需要调用工具查询订单状态。", ) if st["agent"]["stream"]: for chunk in agent.stream({"messages": [{"role": "user", "content": "帮我查一下订单 A1001 的状态"}]}): print(chunk, end="\n\n") else: res = agent.invoke({"messages": [{"role": "user", "content": "帮我查一下订单 A1001 的状态"}]}) print(res)跑起来后,日志里会先出现模型返回的tool_calls,里面name是query_order,args是{"order_id": "A1001"},然后工具执行返回「已发货,预计明天到达」,最后模型把结果组织成自然语言。这条验证了 Agent 的工具调用链路走通了 TaoToken 通道。
提示:如果
tool_calls为空,说明模型没触发工具调用。检查system_prompt是否明确要求调用工具,以及工具描述是否清晰。工具描述写「根据订单号查询订单状态」比写「查询」更容易被模型选中。
5. 本篇常见错排查
5.1 AuthenticationError 或 401
最常见的原因是环境变量没设或设错。先确认echo $TAOTOKEN_API_KEY有输出,再确认config.toml里api_key_env的名字和实际环境变量名一致。如果 Key 是从 https://taotoken.net/api-keys 复制的,注意不要带多余空格。另一个原因是base_url写成了带 UTM 的地址,base_url应该用 https://taotoken.net/api ,不带查询参数。
5.2 模型名报 not found
config.toml里的模型名要和 TaoToken 支持的模型名一致。去 https://taotoken.net/models 确认可用模型列表。如果用的是gpt-4o-mini这类名字,注意大小写和连字符。模型名写错时,报错通常是model_not_found或 404。
5.3 RAG 检索返回空
先检查 embedding 维度。config.toml里models.embedding.dim要和 Milvus schema 里的dim一致,也要和实际 embedding 返回的向量长度一致。text-embedding-3-small是 1536 维,如果 schema 写了 768 就会插入失败或检索异常。再检查top_k是否大于 0,以及 collection 里是否真的有数据。用client.query(collection_name=collection, filter="id > 0", output_fields=["text"], limit=5)确认数据在。
5.4 Agent 不调用工具
模型没触发工具调用时,先看system_prompt有没有明确要求。create_agent的system_prompt写「你是订单助手,需要调用工具查询订单状态」比写「你是助手」更容易触发。再看工具描述,@tool装饰的函数 docstring 就是工具描述,写清楚输入输出。如果模型支持tool_choice参数,可以在init_chat_model后通过bind_tools时指定,但create_agent内部已经处理了绑定,一般不需要手动干预。
5.5 工具调用超时
Agent 调用外部工具时可能卡住。settings.json里agent.tool_timeout设了 15 秒,但create_agent本身不直接读这个值,需要在工具函数内部自己做超时控制,或者用asyncio.wait_for包一层。如果工具是网络请求,先确认目标服务可达。日志里log_tool_calls打开后能看到工具调用开始和结束的时间戳,方便定位卡在哪一步。
5.6 配置读不到
tomllib是 Python 3.11 才有的标准库。如果用的是 3.10 或更低版本,装tomli并改成import tomli as tomllib。settings.json的编码要显式写utf-8,否则 Windows 上可能因为默认编码报UnicodeDecodeError。路径问题也要注意,config.toml和settings.json默认从当前工作目录读,如果从其他目录运行脚本,用绝对路径或Path(__file__).parent拼。
6. 把 RAG 和 Agent 拼成一个应用
RAG 链和 Agent 各自跑通后,拼起来的方式是把 RAG 检索封装成一个工具,挂到 Agent 上。这样 Agent 在需要查知识库时会调用 RAG 工具,不需要时直接回答。
# hybrid_demo.py from langchain.tools import tool from langchain.agents import create_agent from langchain.chat_models import init_chat_model from settings_loader import load_config cfg = load_config() @tool def search_knowledge(query: str) -> str: """在知识库中检索与问题相关的文档片段。""" # 这里复用 4.1 的 retrieval 函数 from rag_demo import retrieval return retrieval(query) llm = init_chat_model( model=cfg["models"]["agent"]["name"], model_provider="openai", base_url=cfg["provider"]["base_url"], api_key=cfg["provider"]["api_key"], temperature=0.0, ) agent = create_agent( model=llm, tools=[search_knowledge], system_prompt="你是知识助手,需要查资料时调用 search_knowledge 工具。", ) for chunk in agent.stream({"messages": [{"role": "user", "content": "LangChain 1.0.2 的 create_agent 用什么构建运行时?"}]}): print(chunk, end="\n\n")这条链路里,Agent 判断需要查知识库,调用search_knowledge,工具内部走 RAG 检索,返回文档片段,Agent 再组织成回答。RAG 和 Agent 共用同一份config.toml和同一个 TaoToken Key,配置只有一处。
长期跑编码类 Agent 或需要多轮工具调用的场景,可以看 https://taotoken.net/coding-plan ,按用量规划比单次调用更省心。模型对话调试在 https://taotoken.net/models 直接试,接入文档在 https://taotoken.net/doc 查配置项。Key 管理在 https://taotoken.net/api-keys ,控制台在 https://taotoken.net/console 。Claude Code 相关接入参考 https://taotoken.net/claudecode 。
最后留一个实用技巧:把config.toml里的models段做成多套,比如models.chat.dev和models.chat.prod,用settings.json里的env字段决定加载哪套。这样本地调试用便宜模型,上线切正式模型,只改一个字段,RAG 和 Agent 同时生效。