当你第一次接触LangChain时,可能会被它的六大组件搞得眼花缭乱。但我要告诉你,Models IO(模型输入输出)组件绝对是其中最值得优先掌握的部分。为什么?因为无论你构建什么AI应用,最终都要解决两个基本问题:如何告诉模型你的需求(Prompt),以及如何处理模型的回答(Output)。
我在实际项目中见过太多开发者,他们费尽心思调用了强大的语言模型,却因为Prompt设计不当,得到的回答总是不尽如人意。比如有个团队想让AI生成产品描述,结果收到的是一堆杂乱无章的句子。问题出在哪?就是因为他们直接把原始需求扔给了模型,没有经过专业的Prompt设计。
想象一下,你请人帮忙写篇文章。如果你只说"写篇关于健康的文章",对方可能无从下手。但如果你说"请用通俗易懂的语言,面向30-40岁女性,写一篇800字左右的健康饮食指南,要包含早餐建议",结果就会好很多。与AI交流也是同样的道理。
LangChain提供了强大的PromptTemplate类,让我们能系统化地设计提示词。来看个实际案例:
python复制from langchain.prompts import PromptTemplate
# 基础模板示例
product_template = PromptTemplate(
input_variables=["product", "style"],
template="用{style}风格写一段关于{product}的商品描述,突出其核心卖点,不超过100字"
)
# 使用模板
prompt = product_template.format(product="智能手表", style="科技感十足")
print(prompt)
这个简单的模板就能生成:"用科技感十足风格写一段关于智能手表商品描述,突出其核心卖点,不超过100字"。当我们将这个提示词发送给AI时,得到的回复会远比直接问"写个智能手表描述"要精准得多。
但静态模板还不够。在实际业务中,我们经常需要根据用户输入动态调整Prompt。LangChain的FewShotPromptTemplate就能实现这个功能:
python复制from langchain.prompts import FewShotPromptTemplate, PromptTemplate
# 示例集合
examples = [
{"query": "价格多少", "response": "您想知道哪款产品的价格呢?"},
{"query": "有优惠吗", "response": "我们目前有以下促销活动..."}
]
# 单个示例的格式
example_prompt = PromptTemplate(
input_variables=["query", "response"],
template="用户问:{query}\n客服答:{response}"
)
# 构建FewShot模板
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
prefix="你是一个电商客服助手,请根据以下对话示例回应用户",
suffix="用户问:{input}\n客服答:",
input_variables=["input"]
)
# 使用动态Prompt
print(prompt.format(input="什么时候发货"))
这种动态Prompt特别适合客服、问答等场景。当用户问"什么时候发货"时,AI会参考之前的示例,给出符合客服风格的回应,而不是干巴巴的物流信息。
想让AI更好地完成任务?给它一个明确的角色!我在项目中测试过,带角色的Prompt效果能提升40%以上。看看这个例子:
python复制from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
# 定义角色模板
template = ChatPromptTemplate.from_messages([
("system", "你是一位资深营养师,擅长用简单易懂的方式解释健康概念"),
("human", "请向50岁左右的男性解释{concept},用日常生活中的例子说明")
])
llm = ChatOpenAI()
response = llm(template.format_messages(concept="血糖指数"))
print(response.content)
这个Prompt中,我们不仅指定了AI的角色(资深营养师),还限定了受众(50岁男性)和表达方式(日常例子)。结果AI给出的解释会非常贴近真实营养师的沟通风格,而不是冷冰冰的医学定义。
当Prompt变得越来越复杂时,直接写在代码里就不好维护了。LangChain支持将Prompt存储在外部文件中。我在实际项目中常用YAML格式:
yaml复制# diet_prompt.yaml
_type: prompt
input_variables: ["age", "gender", "health_goal"]
template: |
作为{age}岁的{gender},我的健康目标是{health_goal}。
请制定一份为期一周的饮食计划,要求:
- 列出每日三餐建议
- 标注主要营养成分
- 避免我不耐受的食物
- 提供简单的烹饪方法
然后在代码中加载使用:
python复制from langchain.prompts import load_prompt
diet_prompt = load_prompt("diet_prompt.yaml")
filled_prompt = diet_prompt.format(
age="45",
gender="男性",
health_goal="控制血糖同时增肌"
)
这种方式特别适合企业级应用,可以让非技术人员也能参与Prompt的优化调整。
模型返回的原始文本就像未加工的矿石,我们需要提炼出有价值的信息。LangChain的PydanticOutputParser是我最常用的工具:
python复制from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
# 定义期望的数据结构
class Recipe(BaseModel):
name: str = Field(description="菜谱名称")
ingredients: List[str] = Field(description="食材列表,包含用量")
steps: List[str] = Field(description="烹饪步骤")
cooking_time: int = Field(description="预计烹饪时间(分钟)")
# 创建解析器
parser = PydanticOutputParser(pydantic_object=Recipe)
# 在Prompt中加入格式指示
prompt = PromptTemplate(
template="根据以下要求提供一份菜谱:{query}\n{format_instructions}",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
# 组合使用
llm = OpenAI()
_input = prompt.format_prompt(query="简单易做的家常豆腐做法")
output = llm(_input.to_string())
# 解析输出
parsed = parser.parse(output)
print(f"菜名:{parsed.name}")
print(f"所需时间:{parsed.cooking_time}分钟")
这个解析器会确保AI的输出符合我们定义的Recipe结构,大大简化了后续的数据处理工作。
AI的输出并不总是完美的,我们需要有容错机制。LangChain提供了RetryOutputParser来自动修复问题:
python复制from langchain.output_parsers import RetryWithErrorOutputParser
# 模拟一个有缺失的输出
bad_output = '{"name": "麻婆豆腐", "ingredients": ["豆腐300g"]}'
# 创建重试解析器
retry_parser = RetryWithErrorOutputParser.from_llm(
parser=parser,
llm=OpenAI(temperature=0)
)
# 自动修复
fixed_output = retry_parser.parse_with_prompt(bad_output, _input)
print(fixed_output)
在我的测试中,这种自动修复机制能解决约80%的输出格式问题,显著提高了系统的稳定性。
让我们把这些技术整合起来,构建一个能处理复杂查询的问答系统。系统流程如下:
关键代码实现:
python复制from typing import Literal
from langchain.chains import LLMChain
class QASystem:
def __init__(self):
self.llm = ChatOpenAI()
self.setup_templates()
def setup_templates(self):
# 定义不同问题类型的模板
self.templates = {
"definition": self.create_definition_template(),
"comparison": self.create_comparison_template(),
"step_by_step": self.create_step_template()
}
def create_definition_template(self):
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个{domain}专家,请用简单易懂的方式解释术语"),
("human", "请解释什么是{term}?用{level}能理解的语言,举1-2个例子")
])
return LLMChain(llm=self.llm, prompt=prompt)
def classify_question(self, question: str) -> Literal["definition", "comparison", "step_by_step"]:
# 简化的分类逻辑,实际项目可以用机器学习模型
if "是什么" in question or "什么是" in question:
return "definition"
elif "比较" in question or "区别" in question:
return "comparison"
else:
return "step_by_step"
def answer(self, question: str, domain: str = "科技", level: str = "高中生"):
q_type = self.classify_question(question)
chain = self.templates[q_type]
if q_type == "definition":
response = chain.run(domain=domain, term=question, level=level)
elif q_type == "comparison":
items = question.split("比较")[-1].split("和")
response = chain.run(item1=items[0], item2=items[1])
else:
response = chain.run(task=question)
return response
在实际部署这类系统时,我总结了几个关键优化点:
python复制from langchain.cache import InMemoryCache
from langchain.callbacks import StreamingStdOutCallbackHandler
# 启用缓存
langchain.llm_cache = InMemoryCache()
# 流式输出示例
llm = OpenAI(
streaming=True,
callbacks=[StreamingStdOutCallbackHandler()],
temperature=0
)
在长期使用LangChain的过程中,我踩过不少坑,这里分享几个典型问题的解决方法:
问题1:Prompt太长导致截断
解决方案:使用提示词压缩技巧,比如:
问题2:输出格式不稳定
解决方案:
问题3:响应时间过长
解决方案:
当Prompt效果不理想时,我的调试流程通常是:
python复制# 调试日志示例
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def debug_prompt(prompt):
logger.info(f"Final prompt sent to LLM:\n{prompt}")
response = llm(prompt)
logger.info(f"Raw LLM response:\n{response}")
return response
在使用LangChain处理用户输入时,务必注意:
python复制from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate
# 安全过滤示例
def safe_input(user_input: str) -> str:
# 移除可能的Prompt注入尝试
for marker in ["```", "'''", "系统指令", "忽略之前"]:
user_input = user_input.replace(marker, "")
return user_input[:500] # 限制长度
parser = CommaSeparatedListOutputParser()
prompt = PromptTemplate(
template="列出与{query}相关的3个安全关键词",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
safe_query = safe_input(user_query)
response = llm(prompt.format(query=safe_query))