Appearance
前两篇我们把模型「调起来」了。但真正决定效果的,往往不是代码,而是你怎么对模型说话。这一篇讲提示词工程:把任务说清楚、让模型稳定吐出可被程序解析的 JSON,并用两个金融领域的实战案例(文本分类、信息抽取)串起来。代码只用上一篇的 openai SDK,零新依赖,顶部可切换 Python / Node.js。
一个好提示词的四要素
给模型下达任务时,越明确越稳定。一个可靠的提示词通常包含:角色(你是谁)、任务(做什么)、约束(规则、边界、输出格式)、示例(给一两个范例,也就是 few-shot)。其中「输出格式」对做应用尤其关键——因为程序要接收模型的输出继续处理,我们需要的不是一段散文,而是结构化数据。
让模型稳定输出 JSON
如果只在提示词里说「请返回 JSON」,模型偶尔会夹带解释文字或用 ```json 代码块包裹,导致 JSON.parse 失败。稳妥的做法有两层:一是提示词里明确「只返回 JSON,不要任何多余文字」;二是打开接口的 JSON 模式(response_format),强制输出合法 JSON。注意:使用 JSON 模式时,提示词里必须出现 “json” 字样,这是接口的硬性要求。
python
import os, json
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def ask_json(system: str, user: str) -> dict:
resp = client.chat.completions.create(
model="qwen-plus",
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
response_format={"type": "json_object"}, # 强制返回合法 JSON
temperature=0, # 抽取/分类类任务用 0 更稳定
)
return json.loads(resp.choices[0].message.content)
data = ask_json(
system="你是信息抽取助手,只输出 json,不要任何多余文字。",
user='从这句话抽取城市和温度,返回 json:{"city":..., "temp":...}。原文:今天杭州 28 度。',
)
print(data, type(data))javascript
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.DASHSCOPE_API_KEY,
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
});
async function askJson(system, user) {
const resp = await client.chat.completions.create({
model: "qwen-plus",
messages: [
{ role: "system", content: system },
{ role: "user", content: user },
],
response_format: { type: "json_object" }, // 强制返回合法 JSON
temperature: 0, // 抽取/分类类任务用 0 更稳定
});
return JSON.parse(resp.choices[0].message.content);
}
const data = await askJson(
"你是信息抽取助手,只输出 json,不要任何多余文字。",
'从这句话抽取城市和温度,返回 json:{"city":..., "temp":...}。原文:今天杭州 28 度。'
);
console.log(data, typeof data);temperature=0 让输出尽量确定,这对分类、抽取这类「有标准答案」的任务很重要;而 response_format 保证了拿到的一定是能 parse 的合法 JSON。这两点是把大模型接进业务系统的基础。
案例一:金融文本分类
把一段财经新闻标题自动归类到「利好 / 利空 / 中性」。要点是在提示词里固定候选标签,并要求模型只从中选择,避免它自由发挥出奇怪的类别。
python
def classify(title: str) -> dict:
system = (
"你是金融舆情分类助手。请判断新闻对相关公司股价的影响,"
"只能从 [利好, 利空, 中性] 三个标签中选一个。"
"只输出 json,格式:{\"label\": \"利好/利空/中性\", \"reason\": \"简短理由\"}。"
)
return ask_json(system, f"新闻标题:{title}")
for t in ["某公司季度净利润同比大增 120%", "监管部门对某银行开出千万罚单"]:
print(t, "=>", classify(t))javascript
async function classify(title) {
const system =
"你是金融舆情分类助手。请判断新闻对相关公司股价的影响," +
"只能从 [利好, 利空, 中性] 三个标签中选一个。" +
'只输出 json,格式:{"label": "利好/利空/中性", "reason": "简短理由"}。';
return askJson(system, `新闻标题:${title}`);
}
for (const t of ["某公司季度净利润同比大增 120%", "监管部门对某银行开出千万罚单"]) {
console.log(t, "=>", await classify(t));
}案例二:金融信息抽取(含 few-shot)
从一段研报文字里抽取结构化字段(公司、评级、目标价)。这类任务给一个示例能显著提升稳定性——这就是 few-shot:把「输入→输出」的样例放进提示词,让模型照着格式套。
python
def extract(report: str) -> dict:
system = (
"你是研报信息抽取助手,只输出 json。"
"字段:company(公司名), rating(评级), target_price(目标价, 数字, 没有则 null)。\n"
"示例:\n"
"输入:我们首次覆盖 A 公司,给予买入评级,目标价 45 元。\n"
'输出:{"company": "A公司", "rating": "买入", "target_price": 45}'
)
return ask_json(system, f"输入:{report}\n输出:")
print(extract("维持 B 公司增持评级,上调目标价至 88.5 元。"))javascript
async function extract(report) {
const system =
"你是研报信息抽取助手,只输出 json。" +
"字段:company(公司名), rating(评级), target_price(目标价, 数字, 没有则 null)。\n" +
"示例:\n" +
"输入:我们首次覆盖 A 公司,给予买入评级,目标价 45 元。\n" +
'输出:{"company": "A公司", "rating": "买入", "target_price": 45}';
return askJson(system, `输入:${report}\n输出:`);
}
console.log(await extract("维持 B 公司增持评级,上调目标价至 88.5 元。"));一点工程经验
即便开了 JSON 模式,健壮的代码也应该对 parse 做 try/catch,并在失败时重试或降级——大模型终究有概率不按预期来。字段越少、指令越具体、给了示例,稳定性越高。真正上生产时,还会把「预期结构」用 JSON Schema 或校验库(Python 的 pydantic、Node 的 zod)再校验一遍,这也是下一篇 LangChain 的 OutputParser 要帮我们做的事。
小结与预告
这一篇的核心就一句话:把输出结构化。你学会了用 response_format 稳定拿 JSON、用固定标签做分类、用 few-shot 做抽取。这些技巧和框架无关,是所有 LLM 应用的地基。
但你可能已经感觉到:提示词、解析、重试、模板拼接……手写会越来越啰嗦。下一篇《LangChain 入门》,我们请出专门的框架来收拾这些重复劳动——用 PromptTemplate 管模板、用 LCEL 把「模板→模型→解析」串成一条链、用 OutputParser 自动解析输出。