1. 项目缘起:为什么你的LangChain应用总在“严谨”与“创意”间摇摆?
如果你刚开始用LangChain构建AI应用,大概率遇到过这种困境:当你需要一个严谨、准确的答案时,比如从文档里提取一个具体的日期或数字,你调用的模型却给你来了一段天马行空的“创作”;而当你希望它写点有创意的文案或故事时,它又变得死板、啰嗦,像在背诵说明书。这种“拧巴”的体验,根源往往不在于模型本身不行,而在于我们默认的“一个模型包打天下”的粗暴用法。
在AI应用开发的早期,为了快速验证想法,我们通常会选择一个模型(比如GPT-3.5或GPT-4),然后通过精心设计提示词(Prompt)来引导它完成所有任务。这就像让一位既精通法律条文又擅长写诗的专家,同时处理合同审查和广告文案。虽然理论上可行,但效率低下,且极易“精神分裂”——为了满足“严谨”的要求,提示词会变得冗长、充满限制,从而扼杀了“创意”;反之,为了激发“创意”,又不得不牺牲一部分准确性和可控性。
更关键的是,调参的复杂性呈指数级上升。你需要在一个提示词里平衡两种截然不同的任务目标,调整温度(temperature)、top_p等参数时往往顾此失彼。温度调低了,创意枯竭;温度调高了,严谨性崩塌。这就是为什么标题说“90%的AI新手不会调参”——不是他们不想,而是在单模型架构下,这几乎是一个无解的多目标优化难题。
我最初也深陷其中,直到将软件工程中“单一职责”和“流水线”的思想引入AI应用架构。与其让一个模型“精神分裂”,不如为“严谨”和“创意”分别匹配合适的“专家”模型,并通过LangChain的智能路由(Routing)或条件链(Conditional Chains)将它们串联起来,形成一条高效、可控的“双模型流水线”。这套方案的核心价值在于:用架构设计替代复杂的提示词工程和参数微调,让代码结构本身来保证不同任务的质量。接下来,我将手把手带你实现这套流水线,并深入每个环节的设计逻辑与避坑要点。
2. 架构核心:理解“严谨”与“创意”任务的技术分水岭
在动手写代码之前,我们必须从技术层面厘清“严谨型任务”和“创意型任务”的本质区别。这决定了后续的模型选型、提示词设计和流水线编排策略。很多新手失败的第一步,就是错误地定义了任务的边界。
2.1 严谨型任务的技术特征与模型需求
严谨型任务的核心是“确定性输出”和“事实准确性”。典型场景包括:
- 信息提取(Information Extraction):从非结构化文本(如报告、邮件)中提取结构化信息,如人名、日期、金额、产品型号。
- 摘要(Summarization):对长文档进行客观、无偏见的浓缩,要求不丢失关键事实。
- 分类(Classification):将文本归入预定义的类别,如情感分析(正面/负面/中性)、意图识别。
- 问答(QA):基于给定上下文(Context)回答问题,答案必须严格源自上下文。
这类任务对模型的要求是:
- 强指令跟随能力:模型必须严格遵守提示词中的格式、规则和约束。
- 低随机性:输出应尽可能一致、可重复。温度(temperature)参数通常需要设置得很低(如0.1或0)。
- 强大的上下文理解与推理能力:需要准确理解文本中的逻辑关系和事实细节。
- 对“幻觉”(Hallucination)的低容忍度:模型不能凭空捏造信息。
因此,为严谨型任务选择模型时,我们更倾向于那些在指令微调(Instruction Tuning)和强化学习人类反馈(RLHF)上表现突出,且已知具有较低幻觉率的模型。例如,OpenAI的GPT-4系列、Anthropic的Claude 3 Opus,或专门为代码、推理任务优化的DeepSeek-Coder等。它们的共同点是逻辑严谨,但可能相对“保守”和“昂贵”。
2.2 创意型任务的技术特征与模型需求
创意型任务的核心是“发散性思维”和“新颖性”。典型场景包括:
- 内容生成(Content Generation):撰写营销文案、博客文章、故事、诗歌。
- 头脑风暴(Brainstorming):生成产品创意、活动方案、故事大纲。
- 风格转换(Style Transfer):将一段文字改写成另一种风格(如正式变口语化,严肃变幽默)。
- 对话与角色扮演(Chat & Role-playing):模拟特定角色进行开放域对话。
这类任务对模型的要求恰恰相反:
- 丰富的想象力和语言多样性:能够生成新颖、不落俗套的文本。
- 高随机性与探索性:需要一定的“灵感火花”,温度(temperature)参数可以设置得较高(如0.7-1.0)。
- 对格式和规则的灵活处理:不必严格遵循固定模板,允许即兴发挥。
- 对事实准确性的要求相对宽松:在创作场景中,适当的虚构和夸张是被允许甚至鼓励的。
为此,我们可能会选择那些在创意写作评测中表现优异,或者参数规模更大、训练数据更偏向开放域对话和文学作品的模型。例如,一些开源的、未经严格事实对齐的模型,或者在创意写作任务上微调过的模型变体。它们的优点是“有灵气”、“便宜”,但可能逻辑性稍弱,容易偏离主题。
2.3 双模型流水线的设计哲学
理解了上述分水岭,双模型流水线的设计就呼之欲出了:任务路由(Task Routing)。整个流水线的第一步,不是一个模型开始工作,而是一个“调度器”来分析用户输入,判断这属于“严谨”还是“创意”任务,然后将任务分发给对应的“专家”模型。
这样做的好处是降维打击:
- 专业化:每个模型只需在其擅长的领域做到极致,无需妥协。
- 成本优化:创意任务可能使用更便宜、更快的模型,而只在需要高精度时调用昂贵的模型。
- 参数简化:每个模型可以使用其任务类型的最优默认参数(如严谨任务temperature=0.1,创意任务temperature=0.8),无需动态调整。
- 可维护性:可以独立升级或更换流水线中的任何一个模型,而不会影响另一个。
接下来的章节,我们将把这个架构落地为代码。
3. 环境搭建与核心工具链选型
工欲善其事,必先利其器。在开始构建流水线前,我们需要搭建一个稳定、可复现的开发环境,并选择一套高效的工具链。这里我会给出一个兼顾生产可靠性和开发便利性的方案,并解释每个选择背后的原因。
3.1 基础环境与依赖管理
强烈建议使用虚拟环境来隔离项目依赖,避免包冲突。这里以Python的venv为例:
# 创建项目目录并进入 mkdir langchain-dual-pipeline && cd langchain-dual-pipeline # 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate接下来,创建requirements.txt文件。这里的选择至关重要:
langchain==0.1.0 langchain-openai==0.0.5 langchain-community==0.0.10 pydantic>=2.0.0 python-dotenv>=1.0.0- 为什么是
langchain==0.1.0?LangChain处于快速迭代期,API变动频繁。0.1.x是一个相对稳定的LTS版本,社区资料丰富,避免了最新版可能带来的意外Break Change。这是我们追求“严谨性”的一环。 langchain-openai与langchain-community:LangChain v0.1.x采用了模块化架构。核心框架langchain只包含最基本的功能,集成具体模型(如OpenAI)需要单独的包langchain-openai,而一些社区贡献的组件、工具则放在langchain-community中。这种设计让依赖更清晰。pydantic>=2.0.0:LangChain大量使用Pydantic进行数据验证和设置管理。Pydantic v2在性能和功能上有巨大提升,是必选项。python-dotenv:用于安全地管理API密钥等环境变量,是生产级应用的基本安全要求。
安装依赖:pip install -r requirements.txt
3.2 模型服务选型与配置
我们的流水线需要两个(或两类)模型。为了演示的通用性和可访问性,我选择OpenAI的模型作为示例,但你完全可以替换为Claude、Gemini或本地部署的Ollama模型。
- 严谨型任务模型:GPT-4。它在复杂推理、指令跟随和减少幻觉方面是目前综合表现最好的选择之一。虽然成本高,但对于关键任务值得投资。
- 创意型任务模型:GPT-3.5-Turbo。它在创意写作、对话生成上性价比极高,响应速度快,足以满足大多数创意需求。
注意:在实际生产中,你可以根据成本、延迟和具体任务效果进行更精细的搭配。例如,对于极度严谨的金融摘要,可能使用Claude 3 Opus;对于简单的文案润色,可能使用更便宜的GPT-3.5-Turbo-Instruct甚至开源模型。
将你的OpenAI API密钥保存在项目根目录的.env文件中:
OPENAI_API_KEY=sk-your-actual-api-key-here然后在代码中通过dotenv加载:
from dotenv import load_dotenv load_dotenv() # 这会读取.env文件中的变量到环境变量 import os api_key = os.getenv("OPENAI_API_KEY")3.3 初始化模型客户端
在LangChain中,我们通过ChatOpenAI类来调用模型。这里的关键是为不同任务创建不同的模型实例,并预设好各自的参数。
from langchain_openai import ChatOpenAI # 严谨型任务模型:GPT-4,低温,保证确定性 strict_llm = ChatOpenAI( model="gpt-4-turbo-preview", # 或 "gpt-4" temperature=0.1, # 关键参数:低温度,输出稳定 max_tokens=2000, # 根据任务设定合理的上限 request_timeout=60, # 设置超时,避免长时间挂起 ) # 创意型任务模型:GPT-3.5-Turbo,较高温度,激发多样性 creative_llm = ChatOpenAI( model="gpt-3.5-turbo", temperature=0.8, # 关键参数:较高温度,输出多样 max_tokens=1500, request_timeout=30, )实操心得:request_timeout参数经常被忽略,但在生产环境中至关重要。网络波动或API服务不稳定可能导致请求挂起,设置超时可以让你的应用更健壮,并有机会进行重试或降级处理。给GPT-4设置更长的超时是合理的,因为它的推理通常更耗时。
4. 实现智能任务路由:流水线的“大脑”
流水线的核心是“路由”(Routing),即判断一个用户查询应该交给strict_llm还是creative_llm。一个简单的if-else判断看似可行,但面对千变万化的自然语言,规则会迅速变得难以维护。更优雅的方式是让一个AI模型(或一个轻量级分类器)来帮我们做这个判断。
4.1 设计路由提示词(Router Prompt)
我们设计一个提示词,让模型根据查询内容将其分类。这里我们依然使用creative_llm(GPT-3.5-Turbo)来完成路由判断,因为它成本低、速度快,且分类本身不需要极强的严谨性。
from langchain.prompts import ChatPromptTemplate from langchain.schema.output_parser import StrOutputParser # 定义路由分类的提示词模板 ROUTER_PROMPT_TEMPLATE = """ 你是一个智能任务分类器。请根据用户的问题,判断它属于以下哪一类任务: A. 严谨型任务:需要精确、基于事实、逻辑严密、无歧义的答案。通常涉及信息提取、总结、分类、基于给定上下文的问答、代码生成、数据分析等。例如:“从这段会议纪要里提取所有行动项和负责人”、“总结这篇科研论文的核心发现”、“将这条用户反馈归类为‘功能请求’、‘Bug报告’还是‘咨询’”。 B. 创意型任务:需要想象力、发散思维、创造性表达或开放性回答。通常涉及写作、头脑风暴、创意生成、角色扮演、风格转换等。例如:“为我们的新产品写一句吸引人的广告语”、“构思三个关于时间旅行的短篇小说开头”、“用幽默的方式解释什么是区块链”。 请只输出单个字母 A 或 B,不要输出任何其他文字。 用户问题:{question} """ router_prompt = ChatPromptTemplate.from_template(ROUTER_PROMPT_TEMPLATE) # 构建路由链 router_chain = router_prompt | creative_llm | StrOutputParser()为什么这样设计提示词?
- 明确角色:赋予模型“分类器”的明确角色,聚焦其任务。
- 清晰定义:用具体的描述和例子来界定“严谨”和“创意”,减少模型误判。
- 格式化输出:强制要求输出“A”或“B”,便于后续程序化处理。
StrOutputParser用于解析模型的文本输出。 - 使用创意模型:路由判断允许一定的模糊性,使用更快更便宜的模型是合理的成本权衡。
4.2 构建路由函数
接下来,我们创建一个函数,它接受用户问题,调用路由链,并返回对应的模型实例。
async def route_question(question: str): """ 路由用户问题到合适的模型。 返回: (model_type, llm_instance) """ try: # 异步调用路由链,提高并发性能 response = await router_chain.ainvoke({"question": question}) response = response.strip().upper() if response == "A": return "strict", strict_llm elif response == "B": return "creative", creative_llm else: # 如果模型没有按格式输出,默认回退到严谨模型(安全选择) print(f"路由输出异常: '{response}', 默认使用严谨模型。") return "strict_fallback", strict_llm except Exception as e: # 路由过程发生错误(如网络问题),也回退到严谨模型 print(f"路由过程出错: {e}, 默认使用严谨模型。") return "strict_fallback", strict_llm避坑要点:
- 错误处理与回退策略:路由本身可能失败(网络、API限流),或者模型可能不按格式输出。我们必须有健壮的回退机制(Fallback)。这里选择回退到
strict_llm,是因为在无法确定意图时,提供一个准确但可能保守的答案,比提供一个充满幻觉的创意答案更安全。这是构建可靠AI应用的关键思维。 - 异步调用:使用
ainvoke而非invoke。在Web服务器或异步框架中,这能避免阻塞,提升整体吞吐量。如果你的应用是纯同步的,用invoke也可。 - 日志记录:打印路由结果和异常,便于后期监控和优化提示词。
4.3 测试路由逻辑
在继续构建完整流水线前,先测试一下我们的“大脑”是否工作正常。
# 简单的测试函数 async def test_router(): test_questions = [ "帮我计算一下这份财务报表中的净利润率是多少?", "为‘智能咖啡杯’想五个有趣的社交媒体标签。", "阅读下面这段客户支持对话,总结用户遇到的核心问题是什么?[对话文本...]", "写一首关于秋天落叶的俳句。", ] for q in test_questions: model_type, llm = await route_question(q) print(f"问题: {q[:50]}...") print(f" 路由结果: {model_type}") print("-" * 40) # 运行测试 import asyncio asyncio.run(test_router())预期输出中,财务计算和问题总结应被路由到strict,而想标签和写俳句应被路由到creative。如果发现误判,就需要回头优化ROUTER_PROMPT_TEMPLATE中的描述和例子。这是一个迭代过程。
5. 构建专业化处理链:让“专家”模型各司其职
路由完成后,问题被分发到对应的模型。但直接让模型回答还不够“专业”。我们需要为每类任务构建一个“处理链”(Chain),这个链包含了针对该类任务优化的提示词模板、可能的上下文处理以及输出格式要求。这才是发挥模型最大效力的地方。
5.1 严谨型任务处理链
对于严谨型任务,提示词的核心是结构化、无歧义和可验证。
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate # 系统消息定义角色和核心原则 strict_system_message = SystemMessagePromptTemplate.from_template( """你是一个严谨、准确、注重事实的AI助手。你的职责是提供基于证据、逻辑清晰、无歧义的答案。 你必须遵守以下规则: 1. 严格基于用户提供的信息和上下文进行回答。如果信息不足,请明确说明“根据已有信息无法确定”。 2. 绝对不要捏造、猜测或添加未被证实的信息。 3. 如果涉及计算或推理,请清晰地展示你的步骤。 4. 答案应简洁、直接,优先使用列表、表格等结构化格式呈现。 5. 如果用户问题模糊,请请求澄清,而不是自行假设。 """ ) # 人类消息模板,用于嵌入用户问题和可能的上下文 strict_human_template = """上下文信息(可能为空): {context} 用户问题:{question} 请根据以上信息,提供严谨准确的回答。""" strict_human_message = HumanMessagePromptTemplate.from_template(strict_human_template) # 组合成完整的提示词 strict_prompt = ChatPromptTemplate.from_messages([strict_system_message, strict_human_message]) # 构建严谨型处理链:提示词 -> 模型 -> 输出解析器 strict_chain = strict_prompt | strict_llm | StrOutputParser()关键设计解析:
- 系统消息(System Message):这是设定模型行为基调的关键。我们明确强调了“基于证据”、“不要捏造”、“展示步骤”等原则,这比在用户问题中反复强调更有效。
- 上下文(Context)变量:留出了
{context}占位符。在实际应用中,严谨任务常常需要搭配“检索增强生成(RAG)”。例如,先从向量数据库中检索相关文档片段,作为context提供给模型,让答案有据可依。这是实现严谨性的核心技术。 - 结构化输出倾向:在提示词中鼓励使用列表、表格,这能迫使模型进行更结构化的思考,产出更易解析和使用的答案。
5.2 创意型任务处理链
对于创意型任务,提示词的核心是激发灵感、减少限制、鼓励多样性。
creative_system_message = SystemMessagePromptTemplate.from_template( """你是一个富有创造力、想象力和幽默感的AI助手。你的任务是帮助用户进行头脑风暴、创作和产生新颖的想法。 请发挥你的创意,提供生动、有趣、出人意料的回答。不要被常规思维束缚,鼓励探索不同的角度和风格。 如果用户没有指定风格,你可以自由发挥,尝试不同的口吻(如幽默、诗意、激昂、简洁)。 目标是激发灵感,而不仅仅是提供标准答案。 """ ) creative_human_template = """用户请求:{question} 请发挥你的创意,给出令人印象深刻的回答。""" creative_human_message = HumanMessagePromptTemplate.from_template(creative_human_template) creative_prompt = ChatPromptTemplate.from_messages([creative_system_message, creative_human_message]) # 构建创意型处理链 creative_chain = creative_prompt | creative_llm | StrOutputParser()关键设计解析:
- 解放天性:系统消息使用了“富有创造力”、“想象力”、“幽默感”、“出人意料”等词语,旨在降低模型的“心理防线”,鼓励其跳出标准应答模式。
- 风格鼓励:明确提到可以尝试不同口吻,这给了模型更大的发挥空间。对于创意任务,模糊的指令有时比精确的指令更能产生好结果。
- 简化模板:人类消息模板非常简洁,避免用复杂的规则束缚模型的创意过程。
5.3 可选的输出后处理
有时,我们可能希望对输出进行后处理。例如,对于严谨型任务的输出,我们可能想自动提取其中的关键数据点;对于创意型输出,可能想过滤掉不合适的语言。这可以通过在链的最后添加一个RunnableLambda来实现。
from langchain.schema.runnable import RunnableLambda def post_process_strict(output: str) -> str: """后处理严谨型任务的输出,例如确保没有以‘抱歉’开头的逃避回答""" if output.startswith("抱歉,") or output.startswith("我无法"): # 可以在这里添加更复杂的重试或降级逻辑 return "根据当前信息,我无法提供一个确切的答案。请提供更多细节或上下文。" # 简单的清理:移除多余的空行 return "\n".join(line.strip() for line in output.splitlines() if line.strip()) def post_process_creative(output: str) -> str: """后处理创意型任务的输出,例如确保长度适中""" # 如果输出过长,可以截断并添加省略号(根据需求调整) max_length = 1000 if len(output) > max_length: return output[:max_length] + "...\n【输出因过长被截断】" return output # 将后处理函数加入链中 strict_chain_with_post = strict_chain | RunnableLambda(post_process_strict) creative_chain_with_post = creative_chain | RunnableLambda(post_process_creative)后处理不是必须的,但它为流水线增加了额外的控制层和鲁棒性。
6. 组装完整流水线与实战测试
现在,我们将路由器和两个处理链组装起来,形成一个完整的、端到端的双模型流水线。这里我们会创建一个主函数,它接收用户输入,自动完成路由、分发、处理和返回结果的全过程。
6.1 构建主流水线函数
async def dual_model_pipeline(question: str, context: str = "") -> dict: """ 双模型流水线主函数。 参数: question: 用户问题 context: 可选的上下文信息(用于严谨型任务) 返回: dict: 包含路由结果、使用的模型类型和最终答案 """ # 1. 路由 model_type, llm_instance = await route_question(question) # 2. 根据路由结果选择处理链并执行 if model_type.startswith("strict"): # 严谨型任务,传入上下文 print(f"[流水线] 执行严谨型任务,使用模型: {llm_instance.model_name}") answer = await strict_chain_with_post.ainvoke({"context": context, "question": question}) else: # creative # 创意型任务,通常不需要额外上下文 print(f"[流水线] 执行创意型任务,使用模型: {llm_instance.model_name}") answer = await creative_chain_with_post.ainvoke({"question": question}) # 3. 返回结构化结果 return { "question": question, "routed_to": model_type, "model_used": llm_instance.model_name, "answer": answer }这个函数清晰地体现了流水线的三个阶段:路由(Route)、执行(Execute)、返回(Return)。它返回一个字典,包含了完整的执行轨迹,便于调试和日志记录。
6.2 综合实战测试
让我们用几个更复杂的例子来测试整个流水线,模拟真实场景。
async def run_integration_tests(): # 测试用例1:需要严谨分析的财务问题(带上下文) context_finance = """ 公司2023年第四季度财报摘要: - 营业收入:人民币1,200万元 - 营业成本:人民币700万元 - 税费:人民币100万元 - 净利润:人民币400万元 """ question1 = "基于提供的财报,计算毛利率和净利率,并列出计算步骤。" # 测试用例2:创意营销任务 question2 = "为我们新推出的‘AI智能笔记本’(能自动整理会议纪要并生成待办事项)构思三条不同风格的广告语,分别针对职场精英、学生和科技爱好者。" # 测试用例3:模糊问题,看路由如何判断 question3 = "分析一下当前新能源汽车市场的竞争格局。" test_cases = [ (question1, context_finance), (question2, ""), (question3, ""), ] for q, ctx in test_cases: print("\n" + "="*60) print(f"测试问题: {q}") if ctx: print(f"附带上下文长度: {len(ctx)} 字符") result = await dual_model_pipeline(q, ctx) print(f"路由决策: {result['routed_to']} (模型: {result['model_used']})") print(f"答案:\n{result['answer']}") print("="*60) # 运行集成测试 asyncio.run(run_integration_tests())预期结果与观察点:
- 问题1:应被路由到
strict(GPT-4)。答案应包含清晰的计算步骤:“毛利率 = (营业收入 - 营业成本) / 营业收入 = (1200-700)/1200 ≈ 41.67%”;“净利率 = 净利润 / 营业收入 = 400/1200 ≈ 33.33%”。输出应该是数字和列表格式,非常结构化。 - 问题2:应被路由到
creative(GPT-3.5-Turbo)。答案应提供三条风格迥异的广告语,例如对职场精英突出“效率”,对学生突出“学习助手”,对科技爱好者突出“黑科技”。语言应该生动、有感染力。 - 问题3:这是一个有趣的边界案例。“分析竞争格局”既有需要事实依据的严谨一面,又可能需要一些概括性的、观点性的创意表达。路由器的判断取决于提示词中例子的偏向。它很可能被路由到
strict,因为“分析”一词更偏向于严谨推理。观察这里的路由结果,有助于我们进一步优化路由提示词。
6.3 处理边界案例与优化
测试中可能会暴露一些问题:
- 路由模糊:像“分析竞争格局”这类问题。优化方法是在
ROUTER_PROMPT_TEMPLATE中增加更多此类边界案例的说明,例如:“如果问题要求基于公开事实和数据进行系统性分析,即使需要一些概括,也属于严谨型任务(A)。” - 创意任务不够“炸”:如果觉得创意输出平淡,可以调整
creative_llm的temperature到0.9或1.0,或者在系统消息中加入更具体的激发语句,如“请给出至少一个让人眼前一亮、出乎意料的点子”。 - 严谨任务输出啰嗦:如果GPT-4的回答过于详细,可以在
strict_system_message中增加“答案请尽可能精炼,聚焦于核心信息”的指令。
7. 进阶:从流水线到智能体(Agent)与性能考量
基础流水线已经能解决大部分问题。但对于更复杂的场景,比如一个任务中同时包含严谨和创意环节,或者需要动态调用工具(如计算器、搜索引擎),我们可以引入LangChain的智能体(Agent)概念,将双模型流水线升级为更强大的智能体系统。
7.1 设计一个两阶段智能体
假设用户请求是:“帮我分析一下特斯拉和比亚迪最近一年的股价走势,并用一个生动的比喻来总结它们的竞争关系。” 这个任务明显包含两个阶段:1) 严谨的数据查找与分析(需要工具);2) 创意的比喻生成。
我们可以设计一个智能体,它首先使用strict_llm作为“大脑”,调用搜索工具获取股价数据并进行初步分析;然后,将分析结果传递给creative_llm,让它生成比喻。
from langchain.agents import AgentExecutor, create_react_agent from langchain.tools import Tool # 假设我们有一个能获取股价信息的工具(这里用模拟函数代替) from langchain.agents import load_tools # 注意:以下代码为概念演示,需要安装相关包并配置工具API # 1. 为严谨模型配置工具(例如:搜索、计算) # 这里需要你根据实际情况安装和配置真实工具,如SerpAPI、Wikipedia等 # tools = load_tools(["serpapi", "llm-math"], llm=strict_llm) # 2. 创建严谨分析智能体 # strict_agent = create_react_agent(strict_llm, tools) # strict_agent_executor = AgentExecutor(agent=strict_agent, tools=tools, verbose=True) # 3. 模拟一个分析结果 analysis_result = """ 特斯拉(TSLA)与比亚迪(BYD)过去一年股价分析: - 特斯拉:年初约110美元,经历大幅波动,年底约180美元,年度涨幅约64%。波动性较大,受马斯克言论、宏观经济影响显著。 - 比亚迪:年初约25美元(港股折算),走势相对稳健,年底约32美元,年度涨幅约28%。受中国电动车市场增长和公司强劲销量支撑。 总体而言,特斯拉像一艘装备精良但颠簸的太空船,冲劲足但起伏大;比亚迪像一列稳步加速的高铁,路线稳健,势头持续。 """ # 4. 将分析结果交给创意模型进行比喻润色 creative_prompt_for_agent = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template("你是一个擅长用比喻的作家。"), HumanMessagePromptTemplate.from_template(""" 请基于以下严谨的金融分析,创作一个生动、贴切、令人印象深刻的比喻,来总结这两家公司的竞争关系。 分析原文: {analysis} 请只输出你构思的比喻,不要重复分析内容。 """) ]) creative_chain_for_agent = creative_prompt_for_agent | creative_llm | StrOutputParser() async def two_stage_agent_demo(): # 第一阶段:严谨分析(此处模拟结果) # factual_analysis = strict_agent_executor.invoke({"input": "分析特斯拉和比亚迪最近一年的股价走势"}) print("第一阶段(严谨分析)完成。") # 第二阶段:创意生成 final_metaphor = await creative_chain_for_agent.ainvoke({"analysis": analysis_result}) print("\n生成的比喻:") print(final_metaphor) # asyncio.run(two_stage_agent_demo())这个架构将流水线从“选择模型”提升到了“编排工作流”,能力更强大。
7.2 性能、成本与监控考量
在生产环境中部署双模型流水线,还需考虑以下几点:
- 延迟:路由本身增加了一次API调用,会带来额外的延迟(约0.5-2秒)。对于延迟敏感的应用,可以考虑:
- 使用更快的模型(如GPT-3.5-Turbo-Instruct)或本地轻量级分类模型进行路由。
- 实现路由缓存,对相似的问题直接返回缓存的路由结果。
- 成本:路由调用和创意任务使用GPT-3.5-Turbo,成本较低;严谨任务使用GPT-4,成本高。需要监控各模型的Token使用量,优化提示词以减少不必要的消耗。对于内部工具,可以权衡是否所有严谨任务都需要GPT-4。
- 监控与评估:需要记录每次请求的路由决策、使用的模型、Token消耗和响应时间。更重要的是,建立评估机制,例如通过人工抽样或自动化评分(如答案与标准答案的相似度),来持续优化路由提示词和各专业链的提示词。
- 降级与熔断:当某个模型API(如GPT-4)不可用时,流水线应能自动降级(如将所有任务路由到GPT-3.5-Turbo,并提示用户当前为降级模式)。这需要在外层添加异常捕获和降级逻辑。
7.3 扩展性:支持更多模型和任务类型
当前架构是二元的(strict/creative)。很容易扩展为多元分类。例如,你可以定义更多任务类型:
code: 代码生成与解释,使用Code Llama或GPT-4-Turbo。reasoning: 复杂逻辑推理,使用Claude 3 Opus或GPT-4。translation: 翻译任务,使用专门的翻译模型。
只需修改路由提示词,增加类别定义和例子,并在主函数中增加对应的处理链即可。这种架构的优势在于其清晰的模块化,使得添加新的“专家”模型变得非常简单。
经过以上七个部分的拆解与实践,我们从“为什么需要双模型”的痛点出发,逐步实现了从环境准备、模型选型、智能路由、专业化链构建到完整流水线组装和进阶扩展的全过程。这套代码的价值不在于用了多高级的模型,而在于它提供了一种用工程化思维解决AI调参难题的范式。它将复杂的、相互冲突的调参目标,分解为独立的、可优化的子模块,让每个部分都能在其专精的领域达到最佳状态。下次当你发现提示词越来越长、参数怎么调都不对劲时,不妨想想:是不是该让不同的模型专家来协同工作了?