LangChain 模型创建与调用实战:从 init_chat_model 到企业级多模型接入
写在前面:这篇文章解决什么问题
这篇博客围绕《第02章:模型的创建与调用》展开,核心目标不是简单记录“怎么调用一次大模型”,而是帮助你从“能跑通模型调用”进一步升级到“能在项目中稳定、可维护、可扩展地接入模型”。
学习 LangChain 的模型调用时,很多人一开始只关注两件事:填 API Key、调用invoke()。这当然能跑起来,但距离企业级开发还差几层能力:配置如何隔离?模型如何切换?调用超时怎么办?Token 成本怎么统计?同步调用、流式调用、批量调用分别适合什么场景?返回结果除了文本内容以外还能提供什么工程价值?
本文会围绕这些问题展开,重点掌握两条主线:
- 模型初始化:如何选择模型提供商、配置 API Key/Base URL、使用
init_chat_model或专用 Chat Class 初始化模型。 - 模型调用:如何使用
invoke、stream、batch、batch_as_completed和异步调用,并理解消息格式与AIMessage返回对象。
读完后,你应该不仅能写出一个调用模型的 Demo,还能开始思考如何在真实项目中设计一个更专业的模型接入层。
一、从 Model I/O 到 Chat Model:为什么现在重点是对话模型
早期 LangChain 常用Model I/O来描述大模型调用过程,可以拆成三段:
- Prompt Template:把用户输入、系统指令、变量组织成模型能理解的提示词。
- Model:真正调用大模型。
- Output Parser:把模型输出解析成程序可继续消费的数据。
这套思路到现在依然重要,但模型本身的形态已经发生变化。早期大模型更多是“补全模型”,本质上是在已有文本后面续写内容;现在主流应用更多使用“对话模型”,也就是 Chat Model。Chat Model 天然支持 system、user、assistant 等角色,更适合指令跟随、多轮对话、工具调用和结构化输出。
因此,学习 LangChain 的模型调用时,应该优先掌握 Chat Model,而不是把模型简单理解成一个“输入字符串、输出字符串”的函数。更准确的理解是:你给模型一组带角色的消息,模型返回一个包含文本、元数据、Token 使用量、工具调用信息的消息对象。
二、模型初始化的三种理解视角
模型初始化看似只是创建一个对象,但如果从工程角度看,它至少包含三个问题:调用谁家的模型、配置写在哪里、模型部署在哪里。
2.1 按模型提供商理解:调用谁家的模型
LangChain 本身不提供大模型,它是一个调用与编排框架,真正的模型来自不同提供商或本地运行环境。常见选择包括:
| 类型 | 示例 | 适合场景 |
|---|---|---|
| 专有模型平台 | OpenAI、Anthropic、DeepSeek、智谱、通义千问 | 生产项目、稳定能力、云端服务 |
| OpenAI-compatible 平台 | 阿里云百炼兼容模式、CloseAI、OpenRouter 等 | 用统一协议接入多个模型 |
| 本地模型 | Ollama + Llama/Qwen/DeepSeek-R1 等 | 学习、隐私数据、离线实验、低成本验证 |
企业项目中很少永远只用一个模型。你可能在开发环境使用本地 Ollama,在测试环境使用便宜模型,在生产环境使用稳定模型,在不同业务场景里按成本、速度和效果做路由。因此,初始化代码最好不要和具体业务逻辑强绑定。
2.2 按配置来源理解:参数写在哪里
调用模型通常离不开三个核心配置:
- model name:模型名称,例如
deepseek-chat、qwen-plus、llama3.1。 - api key:访问模型平台的密钥。
- base url:模型服务地址,尤其是第三方平台或 OpenAI-compatible 服务。
不推荐把这些信息硬编码在 Python 文件中,原因很直接:
- 容易泄露密钥。
- 不方便区分本地、测试、生产环境。
- 更换模型或平台时需要改业务代码。
- 代码上传仓库后存在安全风险。
更推荐的方式是:本地开发使用.env,生产环境使用环境变量或专门的密钥管理服务。
DEEPSEEK_API_KEY=your_deepseek_api_key DEEPSEEK_BASE_URL=https://api.deepseek.com DASHSCOPE_API_KEY=your_dashscope_api_key DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v12.3 按部署位置理解:在线模型还是本地模型
在线模型和本地模型的取舍不是“谁更高级”,而是“谁更适合当前场景”。
在线模型的优势是能力强、更新快、稳定性高、接入门槛低,适合正式业务、复杂推理和高质量生成。但它依赖网络和平台账户,也会带来成本、限流、数据合规等问题。
本地模型通过 Ollama 等工具运行在自己的机器或内网环境中,适合学习实验、隐私数据处理、离线开发、低成本原型验证。但本地模型受硬件资源影响明显,效果、速度、上下文长度都可能不如云端强模型。
企业项目中常见的做法是:开发和调试阶段可以优先使用本地或低成本模型,生产关键链路再切换到质量更高的在线模型。
三、推荐主线:使用 init_chat_model 统一初始化模型
LangChain 中初始化聊天模型有两类常见方式:
- 使用模型提供商专用类,例如
ChatDeepSeek、ChatOpenAI、ChatTongyi、ChatOllama。 - 使用统一入口
init_chat_model。
如果你的目标是学习和项目开发,我更推荐把init_chat_model作为主线。它的价值在于:用更统一的方式初始化不同模型,减少业务代码对具体提供商类的依赖。
3.1 最小初始化示例
下面是一个使用 DeepSeek 模型的示例。代码里没有写死 API Key,而是从环境变量读取。
importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_model load_dotenv(override=True)model=init_chat_model(model="deepseek:deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"),base_url=os.getenv("DEEPSEEK_BASE_URL"),temperature=0.2,timeout=30,max_tokens=1000,max_retries=2,)response=model.invoke("用一句话解释 LangChain 是什么")print(response.content)这里最关键的是model="deepseek:deepseek-chat"。前半部分可以理解为提供商标识,后半部分是具体模型名称。实际项目中,如果 LangChain 无法根据模型名称自动判断提供商,就需要显式传入model_provider。
3.2 使用 .env 管理 API Key 和 Base URL
python-dotenv的作用是把.env文件中的配置加载到进程环境变量中:
fromdotenvimportload_dotenv load_dotenv(override=True)override=True表示.env文件里的值会覆盖当前环境里已经存在的同名变量。这个选项在本地调试时很方便,但在生产环境要谨慎,因为生产环境通常希望由部署平台或密钥系统统一注入变量。
对于 OpenAI-compatible 服务,初始化时经常需要显式传入model_provider="openai":
importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_model load_dotenv(override=True)qwen_model=init_chat_model(model="qwen-plus",model_provider="openai",api_key=os.getenv("DASHSCOPE_API_KEY"),base_url=os.getenv("DASHSCOPE_BASE_URL"),temperature=0.3,)print(qwen_model.invoke("列出 3 个企业级 LLM 应用场景").content)这里的model_provider="openai"不代表你一定在调用 OpenAI 官方模型,而是代表该服务使用 OpenAI 兼容协议。很多国内外平台都提供这种兼容模式,让开发者可以用较统一的客户端方式接入不同模型。
3.3 关键参数:temperature、max_tokens、timeout、max_retries
初始化模型时,除了模型名、密钥和服务地址,还应该重点理解这些参数:
| 参数 | 作用 | 实战建议 |
|---|---|---|
temperature | 控制输出随机性 | 分类、抽取、代码解释用低温;创意写作用中高温 |
max_tokens | 限制最大输出长度 | 控制成本和响应长度,避免无限生成 |
timeout | 请求超时时间 | Web 服务必须设置,避免请求长期挂起 |
max_retries | 失败重试次数 | 应对临时网络波动或服务抖动 |
temperature越低,输出越稳定,适合企业应用中的分类、信息抽取、结构化生成、代码解释等场景。temperature越高,输出越发散,适合头脑风暴、文案创作、故事生成等场景。
max_tokens不只是“限制回答字数”,还和成本治理有关。企业应用中通常需要给不同任务设置不同的 Token 上限,例如标题生成可以很短,报告生成可以更长。
四、补充方式:使用模型提供商专用 Chat Class
除了init_chat_model,LangChain 也为许多模型提供商提供了专用 Chat Class。以 DeepSeek 为例:
importosfromdotenvimportload_dotenvfromlangchain_deepseekimportChatDeepSeek load_dotenv(override=True)model=ChatDeepSeek(model="deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"),api_base=os.getenv("DEEPSEEK_BASE_URL"),temperature=0.2,)print(model.invoke("请介绍模型提供商专用类的优缺点").content)这种方式的优点是直观、明确,可以直接使用某个提供商暴露的特殊参数。缺点是模型切换成本更高,业务代码会更依赖具体提供商。
还要注意:不同集成类的参数名可能不同。例如某些 OpenAI-compatible 初始化习惯使用base_url,而某些提供商类可能使用api_base。这类差异在 Demo 阶段不明显,但在项目封装时很容易踩坑。
简单判断方式:
- 如果你正在写学习 Demo 或只接一个固定平台,专用类很直观。
- 如果你希望未来切换模型、做多模型路由、统一封装调用层,优先考虑
init_chat_model。
五、本地模型调用:Ollama 与 ChatOllama
Ollama 可以让你在本地运行开源模型。常见命令包括:
ollama pull llama3.1 ollama run llama3.1 ollama list在 LangChain 中,可以继续使用init_chat_model初始化 Ollama 模型:
fromlangchain.chat_modelsimportinit_chat_model local_model=init_chat_model(model="llama3.1",model_provider="ollama",temperature=0.5,)print(local_model.invoke("用中文解释本地模型适合哪些开发场景").content)实际模型名称要以你本地ollama list的结果为准。例如你本地安装的是qwen2.5:7b,那初始化时就应该使用对应名称。
本地模型特别适合这些场景:
- 学习 LangChain 调用流程,不想消耗云端 Token。
- 处理不方便发送到外部平台的隐私数据。
- 在弱网或离线环境做实验。
- 为线上模型调用层设计本地替身,方便开发调试。
但本地模型不等于生产可直接替代云端强模型。你仍然需要评估回答质量、响应速度、硬件成本、并发能力和上下文长度。
六、模型调用方式全景
模型初始化解决的是“拿到一个可调用对象”,真正使用时还要根据业务场景选择合适的调用方式。
6.1 invoke:最基础的同步调用
invoke是最基础、最容易理解的调用方式:输入一段文本或一组消息,等待模型生成完整结果后返回。
messages=[("system","你是一名资深 Python 后端工程师,回答要强调工程可落地性。"),("human","在项目中接入大模型时,为什么不应该硬编码 API Key?"),]ai_msg=model.invoke(messages)print(ai_msg.content)invoke适合:
- 简单问答。
- 文本分类。
- 信息抽取。
- 摘要生成。
- 命令行脚本或离线任务。
它的特点是调用方会一直等待,直到完整结果返回。如果模型输出很长,用户会明显感到等待。
6.2 stream:流式输出,改善长文本体验
stream会把模型输出拆成一个个 chunk 逐步返回。对于聊天界面、长文本生成、技术博客生成等场景,流式输出能显著改善体验。
messages=[("system","你是一名技术博客作者。"),("human","写一段关于 LangChain 模型调用方式的开场白。"),]forchunkinmodel.stream(messages):print(chunk.content,end="",flush=True)流式输出并不会让模型实际生成得更快,但它能让用户更早看到内容,降低感知延迟。需要注意的是,不同模型提供商对流式输出的支持程度可能不同。
6.3 batch:批量处理独立任务
当你有多个互不依赖的输入时,可以使用batch批量调用。
questions=["什么是 temperature?","什么是 max_tokens?","timeout 和 max_retries 分别解决什么问题?",]results=model.batch(questions)forquestion,resultinzip(questions,results):print("问题:",question)print("回答:",result.content)batch的一个重要特点是:返回结果顺序与输入顺序一致。这对批量摘要、批量分类、批量解释日志、批量生成标题等任务很有用。
6.4 batch_as_completed:谁先完成先返回
如果每个任务耗时差异较大,并且你希望谁先完成就先处理谁,可以使用batch_as_completed。
questions=["用一句话解释 LangChain。","列出 5 个模型调用的常见错误。","写一段较长的企业级 LLM 接入建议。",]forindex,resultinmodel.batch_as_completed(questions):print(f"第{index}个任务完成:")print(result.content)它的返回顺序不一定等于输入顺序,所以必须保留index。这类模式适合后台批处理、队列任务、批量内容生产等场景。
6.5 async 调用:在高并发服务中的意义
在 Web 服务中,如果模型调用是阻塞的,请求线程或事件循环可能会被长时间占用。异步调用可以帮助你更好地处理并发任务。
importasyncioasyncdefmain():messages=[("system","你是一名 AI 应用架构师。"),("human","为什么 Web 服务中更常见异步模型调用?"),]ai_msg=awaitmodel.ainvoke(messages)print(ai_msg.content)asyncio.run(main())异步不是为了让单次模型调用一定更快,而是为了让服务在等待外部 I/O 时不阻塞其他请求。对于 FastAPI、异步任务队列、多模型并发评估等场景,这一点非常重要。
七、消息格式与返回对象:不要只把模型当字符串函数
7.1 字符串输入
最简单的方式是直接传字符串:
response=model.invoke("请用一句话解释什么是 Chat Model")print(response.content)这种方式适合快速测试,但它不适合复杂业务,因为你无法清晰表达 system 指令、历史对话和 assistant 消息。
7.2 role message 输入
更推荐的方式是传入带角色的消息列表:
messages=[("system","你是一名严谨的技术导师。"),("human","请解释 LangChain 模型调用为什么通常是无状态的。"),]response=model.invoke(messages)print(response.content)这里的 system 消息负责定义模型行为,human 消息代表用户问题。如果要做多轮对话,就需要把历史消息一起传入。模型本身不会天然记住你上一次调用时说了什么,除非你把上下文再次传给它,或者使用额外的状态管理机制。
7.3 LangChain Message 对象
LangChain 还提供了更明确的消息对象:
fromlangchain.messagesimportAIMessage,HumanMessage,SystemMessage conversation=[SystemMessage("你是一个严谨的技术导师。"),HumanMessage("请解释模型调用为什么通常是无状态的。"),]ai_msg=model.invoke(conversation)print(ai_msg.content)当代码规模变大时,显式消息对象比字符串 tuple 更清晰,也更便于类型提示和封装。
7.4 AIMessage 中的 content、usage_metadata 和 response_metadata
invoke返回的通常不是普通字符串,而是AIMessage对象。最常用字段是content:
ai_msg=model.invoke(conversation)print(ai_msg.content)print(ai_msg.usage_metadata)print(ai_msg.response_metadata)你应该重点关注:
content:模型生成的正文。usage_metadata:Token 使用情况,常用于成本统计。response_metadata:模型名、结束原因、服务商返回信息等,常用于排查问题。tool_calls:如果涉及工具调用,这里会包含模型请求调用工具的信息。
很多初学者只打印content,这在 Demo 里没问题,但在企业项目中远远不够。你需要知道一次调用用了多少 Token、耗时多久、由哪个模型完成、是否触发截断、是否命中错误或限流。
八、企业级项目开发中必须补上的能力
8.1 配置与密钥管理
企业项目里,配置管理应该遵循几个原则:
- API Key 不进入代码仓库。
- 本地、测试、生产环境配置分离。
- 生产环境优先使用部署平台环境变量或密钥管理系统。
- 日志中不要打印完整密钥、请求头或敏感输入。
.env很适合本地学习和开发,但它不是完整的密钥治理方案。真正上线时,还要结合 CI/CD、容器平台、云厂商密钥服务或公司内部配置中心。
8.2 统一模型工厂
当项目里接入多个模型时,可以设计一个简单的模型工厂,把模型选择和初始化集中起来。
importosfromdotenvimportload_dotenvfromlangchain.chat_modelsimportinit_chat_model load_dotenv(override=True)MODEL_CONFIGS={"deepseek":{"model":"deepseek:deepseek-chat","api_key_env":"DEEPSEEK_API_KEY","base_url_env":"DEEPSEEK_BASE_URL",},"qwen":{"model":"qwen-plus","model_provider":"openai","api_key_env":"DASHSCOPE_API_KEY","base_url_env":"DASHSCOPE_BASE_URL",},"local":{"model":"llama3.1","model_provider":"ollama",},}defcreate_chat_model(name:str,*,temperature:float=0.2):config=MODEL_CONFIGS[name]kwargs={"model":config["model"],"temperature":temperature,"timeout":30,"max_retries":2,}if"model_provider"inconfig:kwargs["model_provider"]=config["model_provider"]if"api_key_env"inconfig:kwargs["api_key"]=os.getenv(config["api_key_env"])if"base_url_env"inconfig:kwargs["base_url"]=os.getenv(config["base_url_env"])returninit_chat_model(**kwargs)对于一个只有几十行的学习脚本,这样封装可能显得多余。但在企业项目中,它能带来几个好处:
- 切换模型不需要改业务代码。
- 本地、测试、生产可以使用不同模型。
- 便于统一设置超时、重试、温度和 Token 限制。
- 便于后续加入 fallback、路由和灰度策略。
8.3 超时、重试、降级与观测
模型调用本质上是外部服务调用,所以你要按调用外部 API 的标准来设计它:
- 设置
timeout,避免请求长期卡住。 - 设置
max_retries,应对短暂网络抖动。 - 对关键业务设计 fallback,例如主模型失败时降级到备用模型。
- 记录调用耗时、输入长度、输出长度、模型名、错误类型。
一个简单的观测封装可以这样写:
importtimedefinvoke_with_metrics(model,messages):started_at=time.perf_counter()ai_msg=model.invoke(messages)elapsed=time.perf_counter()-started_atreturn{"content":ai_msg.content,"usage":ai_msg.usage_metadata,"metadata":ai_msg.response_metadata,"elapsed_seconds":round(elapsed,3),}真实项目里还需要把这些信息写入日志、指标系统或链路追踪系统。否则当用户反馈“AI 回答很慢”或“成本突然变高”时,你很难定位问题。
8.4 Token 成本统计
模型调用的成本通常和 Token 数量相关。AIMessage的usage_metadata可以帮助你记录:
- 输入 Token 数。
- 输出 Token 数。
- 总 Token 数。
你可以按用户、接口、业务场景、模型提供商统计调用成本。例如:
- 哪个功能最耗 Token?
- 哪类提示词输出过长?
- 是否需要给某些任务降低
max_tokens? - 是否可以把简单任务路由到更便宜的模型?
这就是从 Demo 走向企业级应用必须补上的成本意识。
8.5 在线模型与本地模型的选择策略
可以用下面的思路做选择:
| 场景 | 推荐选择 |
|---|---|
| 学习 LangChain 调用流程 | 本地 Ollama 或低成本在线模型 |
| 高质量内容生成 | 能力更强的在线模型 |
| 隐私数据初步处理 | 本地模型或企业内网部署模型 |
| 高并发简单分类 | 低成本快速模型 |
| 关键业务决策辅助 | 稳定、可观测、经过评测的模型 |
不要只看模型效果,也要看延迟、成本、稳定性、合规和可维护性。
九、常见误区与排查清单
学习和项目实践中,可以重点避开这些坑:
- 把 API Key 写死在代码或博客里:这是最常见也最危险的问题。
- 混淆参数名:例如某些类使用
api_base,某些初始化方式使用base_url。 - 误解
model_provider="openai":它可能只是表示 OpenAI-compatible 协议,不等于调用 OpenAI 官方模型。 - 以为模型天然记得历史对话:多数模型调用是无状态的,多轮对话需要显式传入历史消息或使用状态管理。
- 只读取
response.content:忽略usage_metadata和response_metadata会让成本统计和问题排查变困难。 - 在 Web 服务中大量同步阻塞调用:高并发场景应考虑异步调用、任务队列或流式返回。
- 本地 Ollama 示例不确认模型是否存在:调用前先用
ollama list检查本地模型名称。 - 没有设置超时和重试:外部模型服务可能波动,工程代码不能假设每次都稳定成功。
排查模型调用问题时,可以按这个顺序检查:
- 环境变量是否正确加载。
- API Key 是否有效且没有过期。
- Base URL 是否与平台文档一致。
- 模型名称是否正确。
- 当前模型提供商是否支持该调用方式,例如 streaming。
- 返回对象的
response_metadata是否包含错误、截断或限流信息。 - 本地模型是否已经通过 Ollama 下载并启动。
十、总结:从会调用模型到会设计模型接入层
LangChain 的模型创建与调用可以分成两个层次来学习。
第一层是“会用”:知道如何安装依赖、配置 API Key、初始化模型、调用invoke()拿到回答。
第二层是“会设计”:知道如何统一初始化不同模型,如何管理配置和密钥,如何选择在线或本地模型,如何使用stream改善体验,如何用batch提升批处理效率,如何记录 Token 成本和响应元数据,如何在 Web 服务中避免同步阻塞。
对于个人学习,建议你先用init_chat_model跑通一个在线模型和一个 Ollama 本地模型,再分别练习invoke、stream、batch。对于项目开发,建议尽早抽象出模型工厂和调用封装,把模型接入层从业务逻辑中拆出来。
后续可以继续学习这些方向:
- Prompt Template:让提示词可复用、可测试。
- Output Parser / Structured Output:让模型输出可被程序可靠消费。
- Runnable 链式组合:把 prompt、model、parser 串成可观测流程。
- LangSmith 或日志系统:跟踪调用链、成本和错误。
- RAG、Tool Calling、Agent:在模型调用层稳定后,再构建更复杂的 AI 应用。
真正的企业级 LLM 开发,不只是“调用一个模型”,而是围绕模型调用建立一套稳定、安全、可观测、可演进的工程体系。