4、LLM基础调用:Prompt模板设计、结构化输出(Pydantic)、流式输出
好,咱们直接进入正题。这一章聊的是LLM调用的基本功,说白了就是怎么跟大模型好好说话,并且让它按你的规矩办事。
我刚开始做量化投研Agent的时候,踩过不少坑。最典型的就是:明明问的是同一份财报,模型今天给个表格,明天给段散文,后天直接甩我一句“我不确定”。嗯,这其实就是Prompt设计没到位,输出格式没约束住。
所以这一章,我带你搞定三件事:Prompt模板设计、结构化输出(Pydantic)、流式输出。这三板斧砍下去,你的Agent才算真正有了“职业素养”。
4.1 Prompt模板设计:别让模型猜你的心思
你想想看,如果你跟一个实习生说“帮我看看这份财报”,他可能给你一堆废话。但如果你说“请提取营收、净利润、毛利率,并判断是否超预期”,他就能精准干活。
大模型也一样。Prompt模板就是你的“标准化指令”。
我个人习惯把模板拆成四个部分:
- 角色设定:告诉模型你是谁。比如“你是一名资深量化分析师”。
- 任务描述:具体要做什么。比如“解读以下财报文本”。
- 输出格式:明确要什么结构。比如“返回JSON格式”。
- 示例(Few-shot):给一两个例子,模型学得最快。
举个例子,我在项目中常用的模板长这样:
system_prompt = """
你是一名专业的财报解读助手。
请根据用户提供的财报文本,提取关键财务指标。
输出格式必须为JSON,包含以下字段:
- revenue: 营收(亿元)
- net_profit: 净利润(亿元)
- gross_margin: 毛利率(百分比)
- sentiment: 情绪判断(positive/negative/neutral)
示例:
输入:公司2023年营收100亿元,净利润20亿元,毛利率40%。
输出:{"revenue": 100, "net_profit": 20, "gross_margin": 40, "sentiment": "positive"}
"""
为什么要这么设计?说白了,就是减少模型的“自由发挥空间”。你给的结构越清晰,它越不容易跑偏。
4.2 结构化输出(Pydantic):让数据规规矩矩
Prompt模板虽然能约束格式,但返回的还是字符串。你想想看,如果模型返回的JSON里字段名拼错了,或者类型不对,你的后续代码直接崩掉。
这时候就需要Pydantic上场了。它是Python里做数据验证的利器,能帮你把模型的输出“格式化”成标准对象。
我一般这么用:
from pydantic import BaseModel, Field
from typing import Optional
class FinancialReport(BaseModel):
revenue: float = Field(..., description="营收(亿元)")
net_profit: float = Field(..., description="净利润(亿元)")
gross_margin: Optional[float] = Field(None, description="毛利率(百分比)")
sentiment: str = Field(..., pattern="^(positive|negative|neutral)$")
# 然后让LLM输出JSON,再用Pydantic解析
raw_output = '{"revenue": 100, "net_profit": 20, "gross_margin": 40, "sentiment": "positive"}'
report = FinancialReport.model_validate_json(raw_output)
print(report.revenue) # 100.0
你看,这样一搞,数据就变得规规矩矩了。类型不对?Pydantic直接抛异常。字段缺失?它也能告诉你。
json.loads()做一次宽松解析,再用Pydantic校验。或者直接用model_validate_json(),它内部会先做一次JSON解析。
另外,Pydantic还支持嵌套模型。比如你要解析一份完整的财报,里面包含多个子表:
class BalanceSheet(BaseModel):
total_assets: float
total_liabilities: float
class FullReport(BaseModel):
income: FinancialReport
balance: BalanceSheet
这样你的数据结构就非常清晰了。说白了,Pydantic就是给LLM的输出加了一层“安检门”。
4.3 流式输出:别让用户干等
你有没有遇到过这种情况:调用LLM分析一份长财报,结果等了十几秒才看到结果?用户体验极差。
流式输出(Streaming)就是解决这个问题的。它让模型一边生成一边返回,用户能看到“打字效果”,心理等待时间大大缩短。
我常用的实现方式有两种:
- OpenAI SDK原生支持:设置
stream=True - 自定义生成器:用
yield逐块返回
代码示例:
from openai import OpenAI
client = OpenAI(api_key="your-key")
def stream_analysis(text: str):
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "你是一名财报分析师,请逐句解读。"},
{"role": "user", "content": text}
],
stream=True # 开启流式
)
for chunk in response:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
# 使用
for token in stream_analysis("公司2023年营收增长20%..."):
print(token, end="", flush=True)
嗯,这里要注意一点:流式输出时,不要做后处理。比如你打算把输出转成JSON,那就别用流式,因为JSON是完整的结构,拆开就没意义了。
4.4 知识体系总览
为了让你更直观地理解这三者的关系,我画了一张图:
从这张图你能看出来,这三个模块不是孤立的。Prompt模板是“入口”,决定了模型怎么理解任务;结构化输出是“质检员”,保证数据不出错;流式输出是“体验优化”,让用户不焦虑。
我个人建议,在实际项目中,先把Prompt模板和Pydantic搭好,再考虑流式输出。因为数据准确性永远是第一位的,体验是锦上添花。
好了,这一章的内容就这些。记住:跟大模型打交道,规矩定得越细,后面越省心。