1. 从一堆 Key 到一把钥匙:LangChain 入门最烦的事
如果你刚开始学 LangChain,大概率会遇到这样一个场景:跟着教程写了个 Chain,跑通了;想换个模型对比效果,得去翻.env改OPENAI_API_KEY;再想试试 Agent 调工具,又发现工具调用和模型绑定得死死的,换一个模型就得重写一遍bind_tools。更别提同时开着 OpenAI、Claude、国产模型好几个 Key,环境变量文件越写越长,切来切去自己都记不清哪个是哪个。
LangChain 本身是个很灵活的框架,Chain 负责把「提示词 → 模型 → 解析」串成流水线,Agent 负责让模型自己决定调哪个工具、走哪条路。但灵活的另一面就是配置分散:模型供应商、API Key、Base URL、模型名,这些东西散落在代码、.env、config.toml里,入门阶段光是理清这些就够劝退的。
这篇记录就是解决这个问题的。核心思路很简单:用 TaoToken 作为统一的模型接入层,把多供应商的 Key 收敛成一把,然后在 LangChain 里通过config.toml+.env两个文件管理配置,Chain 和 Agent 共用同一套模型初始化逻辑。适合正在学 LangChain、想快速跑通最小可运行示例、又不想被 Key 管理拖住的人。下面从环境准备到运行验证,一步步来。
2. TaoToken 前置:统一 Key 与接入地址
TaoToken 在这里扮演的角色是「模型网关」——你只需要在它那边拿到一个 API Key,配置好要用的模型,LangChain 侧就统一指向它的 API 地址。这样切换模型时,改的是 TaoToken 后台的配置,而不是你项目里散落各处的环境变量。
需要提前准备的东西:
- 一个 TaoToken 账号,在控制台创建一个 API Key
- 确认你要用的模型已经在 TaoToken 侧可用
- 本地 Python 环境(建议 3.10+),装好
langchain、langchain-openai、python-dotenv、pydantic
TaoToken 的 API 地址是https://taotoken.net/api,兼容 OpenAI 的接口格式,所以 LangChain 里直接用ChatOpenAI这个类,把base_url指过去就行。这也是它方便的地方:不用为每个供应商装不同的 SDK,一套 OpenAI 兼容接口全搞定。
拿 Key 的入口在控制台的 API Keys 页面,创建后复制出来,注意它只显示一次。模型对话的调试入口可以用来先确认模型通不通,再进代码。如果你后面要长期跑编码类 Agent,可以了解下 Coding Plan,不过入门阶段先用按量调用就够了。
注意:API Key 不要硬编码进代码,也不要提交到 Git。下面统一用
.env管理。
3. 可复制配置:config.toml 与 .env 骨架
先建项目目录,结构大概这样:
langchain-demo/ ├── .env ├── config.toml ├── requirements.txt ├── chain_demo.py └── agent_demo.pyrequirements.txt内容:
langchain>=0.3.0 langchain-openai>=0.2.0 langchain-core>=0.3.0 python-dotenv>=1.0.0 pydantic>=2.0.0.env只放敏感信息,一个 Key 搞定:
TAOTOKEN_API_KEY=sk-你的TaoToken密钥config.toml放非敏感的模型配置,这样切换模型不用动代码:
[llm] base_url = "https://taotoken.net/api" model = "gpt-4o-mini" temperature = 0.3 max_tokens = 1024 [agent] model = "gpt-4o-mini" max_iterations = 5这里max_tokens控制的是模型单次输出的上限,不是输入长度。入门阶段设小一点(比如 1024),既能省钱,也能防止 Agent 陷入死循环一直输出。temperature调低让输出更稳定,方便调试。
读取配置的公共模块,我习惯单独写一个llm_factory.py,Chain 和 Agent 都从这里拿模型实例:
import os import tomllib from dotenv import load_dotenv from langchain_openai import ChatOpenAI load_dotenv() def load_config(path: str = "config.toml") -> dict: with open(path, "rb") as f: return tomllib.load(f) def get_llm(section: str = "llm") -> ChatOpenAI: cfg = load_config()[section] return ChatOpenAI( api_key=os.getenv("TAOTOKEN_API_KEY"), base_url=cfg["base_url"], model=cfg["model"], temperature=cfg.get("temperature", 0.3), max_tokens=cfg.get("max_tokens", 1024), )tomllib是 Python 3.11 内置的,3.10 的话装个tomli并改成import tomli as tomllib即可。这样模型初始化只有一处,改配置就全局生效。
4. 跑通 Chain:最小可运行示例
Chain 的本质是「输入 → 提示词模板 → 模型 → 输出解析」的流水线。先写一个最简单的,验证链路通不通:
from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from llm_factory import get_llm llm = get_llm("llm") prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个简洁的技术助手,回答控制在三句话内。"), ("human", "{question}"), ]) chain = prompt | llm | StrOutputParser() if __name__ == "__main__": result = chain.invoke({"question": "LangChain 里 Chain 和 Agent 有什么区别?"}) print(result)运行:
python chain_demo.py如果配置正确,你会看到模型返回的一段文字。这条链路里,prompt | llm | StrOutputParser()用管道符把三个组件串起来,invoke传入字典填充模板变量。这就是 Chain 最小形态——一问一答,没有状态,没有工具。
接下来加结构化输出。入门阶段经常需要模型返回 JSON 而不是自由文本,用 Pydantic 定义结构,让模型按 Schema 输出:
from pydantic import BaseModel, Field from langchain_core.prompts import ChatPromptTemplate from llm_factory import get_llm class BookInfo(BaseModel): title: str = Field(description="书名") author: str = Field(description="作者") year: int = Field(description="出版年份") llm = get_llm("llm") structured_llm = llm.with_structured_output(BookInfo) prompt = ChatPromptTemplate.from_messages([ ("system", "从用户描述中提取书籍信息。"), ("human", "{text}"), ]) chain = prompt | structured_llm if __name__ == "__main__": out = chain.invoke({"text": "我最近在读《人类简史》,尤瓦尔·赫拉利写的,2014年出版。"}) print(out) print(type(out))with_structured_output会把 Pydantic 模型转成模型能理解的 Schema,返回的直接是BookInfo实例,不用自己json.loads。实测下来,结构化输出和 Tool Calling 是两套机制:前者负责让模型返回业务数据,后者负责让模型决定调哪个工具,别混用。
流式输出也顺手加一下,Chain 支持stream:
for chunk in chain.stream({"text": "《三体》,刘慈欣,2008年。"}): print(chunk, end="", flush=True)不过结构化输出配流式会有点别扭,因为要等整个 JSON 拼完才能解析。入门阶段建议:自由文本用stream,结构化数据用invoke。
5. 跑通 Agent:工具调用与状态管理
Agent 和 Chain 的核心区别在于:Chain 是你定死的流水线,Agent 是模型自己决定下一步。在 LangChain 里,用@tool装饰器把普通函数变成模型可调用的工具:
from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的天气。输入城市名,返回天气描述。""" fake_db = {"北京": "晴,25度", "上海": "多云,28度", "深圳": "阵雨,30度"} return fake_db.get(city, "暂无该城市数据") @tool def calculate(expression: str) -> str: """计算数学表达式,例如 '2 + 3 * 4'。""" try: return str(eval(expression, {"__builtins__": {}}, {})) except Exception as e: return f"计算失败: {e}"@tool会自动提取函数名、docstring 和参数类型,生成模型能理解的工具描述。docstring 很重要,模型就是靠它判断该不该调这个工具。
然后构建 Agent。LangChain 现在推荐用 LangGraph 的create_react_agent,它内部维护一个 State,能管理多轮工具调用:
from langgraph.prebuilt import create_react_agent from llm_factory import get_llm from tools import get_weather, calculate llm = get_llm("agent") tools = [get_weather, calculate] agent = create_react_agent(llm, tools) if __name__ == "__main__": result = agent.invoke({ "messages": [("human", "北京天气怎么样?顺便算一下 12 * 8 等于多少。")] }) for msg in result["messages"]: print(f"[{msg.type}] {msg.content}")运行后你会看到消息序列:先是 human 提问,然后 AI 发起 tool_call,接着 tool 返回结果,最后 AI 汇总回答。这就是 Agent 的完整生命周期——模型决策、工具执行、结果回填、再决策,直到给出最终答案。
Agent 的 State 里messages字段用的是追加策略,历史对话会保留;而像中间结果这类字段通常是覆盖。如果你要加短期记忆,可以配 Checkpointer,同一个thread_id再次请求时会恢复之前的 State。入门阶段先用内存版就够:
from langgraph.checkpoint.memory import MemorySaver memory = MemorySaver() agent = create_react_agent(llm, tools, checkpointer=memory) config = {"configurable": {"thread_id": "user-001"}} agent.invoke({"messages": [("human", "北京天气如何?")]}, config) # 第二次调用会带上之前的上下文 agent.invoke({"messages": [("human", "那上海呢?")]}, config)注意MemorySaver存在内存里,进程重启就没了。生产环境要换持久化存储,但学习阶段够用。
6. 本篇常见错排查
报错一:AuthenticationError或 401
最常见的原因是.env没被加载,或者 Key 复制时带了空格。先确认load_dotenv()在读取环境变量之前调用,再打印os.getenv("TAOTOKEN_API_KEY")[:8]看前几位对不对。另外确认base_url写的是https://taotoken.net/api,末尾不要多加/v1之类的路径。
报错二:model not found或 404
config.toml里的model字段要和 TaoToken 侧实际可用的模型名一致。不同供应商模型命名规则不同,别想当然填。先去模型对话页面确认模型名,再回填配置。
报错三:Agent 不调工具,直接瞎编答案
通常是工具 docstring 写得太模糊,模型判断不出该不该调。把 docstring 写清楚:这个工具做什么、输入什么、返回什么。另外temperature太高也会让模型不稳定,调到 0.2 以下试试。
报错四:max_tokens设太小导致回答被截断
max_tokens限制的是输出长度,不是输入。如果模型回答到一半停了,检查这个值。入门调试可以设 2048,稳定后再按业务调小。
报错五:结构化输出报 Schema 校验失败
模型偶尔会返回不符合 Pydantic 定义的字段。可以在with_structured_output里加strict=True(如果模型支持),或者给字段加默认值兜底。另外字段描述Field(description=...)要写清楚,模型靠它理解每个字段含义。
报错六:流式输出中文乱码或断字
stream返回的是 chunk,中文可能被拆到两个 chunk 里。用print(chunk, end="", flush=True)逐块输出即可,不要自己拼接后再打印。如果要做前端渲染,按 chunk 追加到缓冲区。
7. 下一步:把统一 Key 用顺
跑通 Chain 和 Agent 之后,你会发现统一 Key 的价值在切换模型时才真正体现。比如想把config.toml里的model从gpt-4o-mini换成另一个模型,只改一行配置,Chain 和 Agent 都不用动。这就是把模型接入层收敛到一处的好处。
如果你要长期跑编码类任务或复杂 Agent,可以看看 Coding Plan,它在调用额度和并发上更适合持续使用。日常调试模型通不通,用模型对话页面最快。需要管理多个 Key 或查看用量,去控制台。接入文档里有更完整的参数说明和兼容性细节,遇到本文没覆盖的报错可以去翻。
入门阶段不用追求一步到位,先把这条最小链路跑顺:.env放 Key,config.toml放模型配置,llm_factory.py统一初始化,Chain 和 Agent 共用。后面加工具、加记忆、换模型,都是在这个骨架上长出来的。