8年Java开发者AI转型第一周:从零搭建RAG文档问答系统(Day 3-5)
系列:8年Java开发者AI转型第一周
上一篇:Day 1-2:Python速学 + 环境搭建
本期内容:OpenAI API / 国内大模型API调用、Chat Completion结构、Prompt工程、参数调优、Few-shot
gitHub
开发转型,从"Java开发"到"AI应用开发"的核心技能储备
一、背景
上一篇我们搭好了 Python 环境,简单的了解了Python基础和java的区别。
这一期,我们看看:LLM 的 API 到底是什么样子的?request 和 response 的结构是怎样的?system prompt、user prompt 到底有什么区别?temperature、top_p 这些参数调了会怎样?
这些知识,是你后续做任何 AI 应用(RAG、Agent、微调……)的地基。
二、环境搭建
2.1 安装 OpenAI Python SDK
不管你调的是 OpenAI 还是 DeepSeek 还是智谱 GLM,它们都遵循 OpenAI 兼容的 API 协议。所以只需要装一个 SDK:
pip install openai
2.2 配置文件
参考 config_bigmodel.json,我们把不同模型的配置分开:
{
"deepseek": {
"api_key": "sk-xxxxx",
"base_url": "https://api.deepseek.com/v1"
},
"zhipu": {
"api_key": "xxxxx",
"base_url": "https://open.bigmodel.cn/api/paas/v4"
},
"openai": {
"api_key": "sk-xxxxx",
"base_url": "https://api.openai.com/v1"
}
}
注意:
base_url是关键。OpenAI 官方是https://api.openai.com/v1,DeepSeek 是https://api.deepseek.com/v1,智谱是https://open.bigmodel.cn/api/paas/v4。SDK 是一样的,只是 URL 不同。
三、Chat Completion API 的 Request / Response 结构
3.1 最简单的调用
先看一个最基础的请求:
from openai import OpenAI
import json
# 从配置文件读取
with open("config_bigmodel.json", "r", encoding="utf-8") as f:
config = json.load(f)
client = OpenAI(
api_key=config["deepseek"]["api_key"],
base_url=config["deepseek"]["base_url"]
)
# 发送请求
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "user", "content": "你好,介绍一下你自己"}
]
)
print(response.choices[0].message.content)
3.2 Request 结构拆解
上面那段代码,实际发出的 HTTP 请求大致是这样的:
{
"model": "deepseek-v4-pro",
"messages": [
{
"role": "user",
"content": "你好,介绍一下你自己"
}
]
}
核心字段只有两个:
model:指定用哪个模型messages:消息列表,数组中的每条消息都有role和content
3.3 Response 结构拆解
模型返回的 JSON 结构:
{
"id": "chatcmpl-xxx",
"object": "chat.completion",
"created": 1720000000,
"model": "deepseek-v4-pro",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "你好!我是 DeepSeek..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 50,
"total_tokens": 65
}
}
代码里我们只取了 response.choices[0].message.content,但其实还有很多有价值的信息:
| 字段 | 含义 | 用途 |
|---|---|---|
response.id | 请求ID | 排查问题时向客服提供 |
response.choices[0].finish_reason | 停止原因 | "stop"=正常结束,"length"=超出max_tokens |
response.usage.prompt_tokens | 输入token数 | 计费依据 |
response.usage.completion_tokens | 输出token数 | 计费依据 |
四、System / User / Assistant 的角色区别
这是很多人最容易混淆的地方。messages 数组里的每条消息都有一个 role,它决定了这条消息是谁说的。
4.1 三种角色的含义
| role | 含义 | 谁能发 | 类比 |
|---|---|---|---|
system | 系统指令,给模型定规矩 | 只能是开发者 | 老板给员工定的"工作手册" |
user | 用户的提问或指令 | 可以是用户或开发者 | 员工接到的"工作任务" |
assistant | 模型的回复 | 只能是模型(但你可以预填) | 员工的"工作成果" |
4.2 实际代码示例
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
# system: 给模型设定角色和行为边界
{"role": "system", "content": "你是一个专业的Python代码审查专家。你的职责是找出代码中的bug、性能问题和安全漏洞。回答时要指出具体问题位置,并给出修改建议。"},
# user: 用户的问题
{"role": "user", "content": "请审查以下代码:\n\ndef calculate_sum(numbers):\n total = 0\n for i in range(len(numbers)):\n total += numbers[i]\n return total"},
]
)
print(response.choices[0].message.content)
输出可能是:
这段代码功能是计算列表元素之和,虽然逻辑正确,但存在以下问题:
1. 【性能问题】使用索引遍历效率较低
建议改为:for num in numbers: total += num
或直接使用内置函数:return sum(numbers)
2. 【安全隐患】没有输入校验
如果传入非列表类型会抛出TypeError
建议添加类型检查...
4.3 多轮对话:消息是怎么累积的
LLM 没有记忆,每一轮对话都需要你把历史消息全部传回去:
messages = [
{"role": "system", "content": "你是一个数学老师"},
]
# 第一轮
messages.append({"role": "user", "content": "1+1等于几?"})
resp1 = client.chat.completions.create(model="deepseek-v4-pro", messages=messages)
messages.append(resp1.choices[0].message) # 把模型的回复加入历史
# 第二轮
messages.append({"role": "user", "content": "那2+3呢?"})
resp2 = client.chat.completions.create(model="deepseek-v4-pro", messages=messages)
print(resp2.choices[0].message.content)
# 输出: 2+3等于5
关键点:
messages数组会越来越大,这意味着token消耗越来越多,费用也越来越高。实际项目中要注意控制对话历史长度。
五、参数调优:Temperature、Top-p、Max Tokens
这三个参数直接决定模型的输出风格,是 Prompt Engineering 的核心。
5.1 Temperature — 随机性控制
温度越高,输出越"放飞自我";温度越低,输出越"一本正经"。
# 低温度:严谨、确定性强
response_low = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "写一首关于春天的诗"}],
temperature=0.1 # 几乎每次输出都一样
)
# 高温度:创意、多样性强
response_high = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "写一首关于春天的诗"}],
temperature=0.9 # 每次输出都可能不一样
)
| temperature 值 | 效果 | 适用场景 |
|---|---|---|
| 0.0 ~ 0.3 | 高度确定性,几乎不变 | 代码生成、事实问答、数据分析 |
| 0.4 ~ 0.7 | 适度创意 | 通用对话、摘要、翻译 |
| 0.8 ~ 1.0 | 高度创意,可能胡说 | 头脑风暴、故事创作 |
建议:RAG 场景下用
0.0~0.3,因为你要的是准确回答,不是创意。
5.2 Top-p — 核采样阈值
Top-p 控制模型从哪些词里面选下一个词。
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "明天天气怎么样?"}],
top_p=0.9 # 只从累计概率90%的词里面选
)
| top_p 值 | 效果 |
|---|---|
| 0.1 | 只选最可能的10%的词 → 输出非常保守 |
| 0.5 | 选最可能的50%的词 → 适度多样 |
| 0.9 | 选最可能的90%的词 → 比较开放 |
| 1.0 | 所有词都参与 → 最开放 |
Temperature 和 Top-p 的关系:
- 两者可以同时设置,但通常只需要设一个
- Temperature 控制的是"概率分布的形状",Top-p 控制的是"截断哪些词"
- 推荐做法:
temperature=0.7, top_p=0.9是一个不错的默认组合
5.3 Max Tokens — 最大输出长度
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "请详细介绍RAG的原理"}],
max_tokens=2048 # 最多输出2048个token
)
| max_tokens 值 | 适用场景 |
|---|---|
| 100~300 | 简短回答、分类、提取 |
| 500~1000 | 一般问答、摘要 |
| 2000~4096 | 详细解释、代码生成 |
| 8192+ | 长文档生成 |
注意:如果输出被截断(
finish_reason="length"),说明 max_tokens 不够,需要增大。
5.4 综合调优示例
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": "你是一个资深的Java转Python的技术导师,擅长用Java类比讲解Python概念。"},
{"role": "user", "content": "请用Java的概念帮我理解Python的装饰器"}
],
temperature=0.3, # 偏严谨,因为要准确解释概念
top_p=0.9, # 允许一定的表达多样性
max_tokens=1024, # 足够长的回答
stream=False # 非流式,一次性返回
)
print(response.choices[0].message.content)
print(f"消耗tokens: {response.usage.total_tokens}")
六、流式输出(Streaming)
上面的例子都是 stream=False,模型憋完整个答案才一次性返回。但实际产品中,我们更希望边想边说,像打字机一样逐字输出。
6.1 非流式 vs 流式对比
# 非流式:等模型全部生成完才返回
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "写一篇2000字的技术文章"}],
stream=False
)
print(response.choices[0].message.content) # 一次性拿到全部内容
# 流式:模型每生成一部分就推送一次
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "写一篇2000字的技术文章"}],
stream=True
)
full_content = ""
for chunk in response:
# 每个chunk只包含一小段文本
if chunk.choices and chunk.choices[0].delta.content:
content = chunk.choices[0].delta.content
full_content += content
print(content, end="", flush=True) # 实时打印
6.2 实际应用场景
流式输出在以下场景中特别有用:
- 网页聊天界面:用户输入问题后,立刻看到模型在"思考",而不是干等
- 长文档生成:不需要等几千字全部生成完
- 代码补全:IDE 里的 AI 助手,边写边提示
6.3 带 Reasoning 的流式输出
如果你用的是支持思考模式的模型(如 DeepSeek R1),流式输出还会包含 reasoning_content(思考过程)和 content(最终答案):
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[{"role": "user", "content": "如何解决Java中的并发问题?"}],
stream=True,
reasoning_effort="high",
extra_body={"thinking": {"type": "enabled"}}
)
reasoning_content = ""
final_content = ""
for chunk in response:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
# 思考过程(如果模型支持)
if delta.reasoning_content is not None:
reasoning_content += delta.reasoning_content
# 最终答案
if delta.content is not None:
final_content += delta.content
print(delta.content, end="", flush=True)
七、Few-shot Prompting(少样本提示)
7.1 什么是 Few-shot
Few-shot 就是给模型看几个例子,让它学会你的格式或逻辑。就像教小孩做题:"你看,3+2=5,那4+1等于几?"
7.2 经典示例:情感分类
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": "你是一个文本情感分类器,请将用户输入分为正面、负面或中性。"},
# 示例1
{"role": "user", "content": "这个产品真好用!"},
{"role": "assistant", "content": "正面"},
# 示例2
{"role": "user", "content": "物流太慢了,等了两周。"},
{"role": "assistant", "content": "负面"},
# 示例3
{"role": "user", "content": "商品收到了。"},
{"role": "assistant", "content": "中性"},
# 真正要分类的内容
{"role": "user", "content": "客服态度特别好,解决问题很快!"},
],
temperature=0.1, # 分类任务要确定性高
max_tokens=10
)
print(response.choices[0].message.content)
# 输出: 正面
7.3 进阶示例:结构化输出
让模型输出 JSON 格式的数据,方便程序后续处理:
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=[
{"role": "system", "content": """你是一个信息提取助手。请从用户输入中提取姓名、年龄、城市,并以JSON格式返回。
只返回JSON,不要其他内容。格式如下:
{"name": "姓名", "age": 年龄数字, "city": "城市"}"""},
# 示例1
{"role": "user", "content": "我叫张三,今年28岁,住在北京。"},
{"role": "assistant", "content": "{\"name\": \"张三\", \"age\": 28, \"city\": \"北京\"}"},
# 示例2
{"role": "user", "content": "李四,35岁,上海。"},
{"role": "assistant", "content": "{\"name\": \"李四\", \"age\": 35, \"city\": \"上海\"}"},
# 真正要处理的
{"role": "user", "content": "王芳,25岁,广州人。"},
],
temperature=0.0, # 结构化输出要零随机
max_tokens=100
)
import json
result = json.loads(response.choices[0].message.content)
print(result)
# {'name': '王芳', 'age': 25, 'city': '广州'}
7.4 Few-shot 的关键技巧
| 技巧 | 说明 |
|---|---|
| 示例要贴近真实场景 | 给模型看的例子,风格和格式要和真实输入一致 |
| 2-5个示例最佳 | 太少学不到规律,太多浪费token |
| temperature 设低 | 分类/提取类任务用 0.0~0.1 |
| system prompt + 示例配合 | system 定规则,示例给示范 |
八、实战:代码审查 Agent
把我们学到的所有知识整合起来,写一个真正的实用工具——代码审查 Agent。
8.1 完整代码
import os
import json
from openai import OpenAI
if __name__ == "__main__":
# 1. 加载配置
with open("config_bigmodel.json", "r", encoding="utf-8") as f:
config = json.load(f)
client = OpenAI(
api_key=config["deepseek"]["api_key"],
base_url=config["deepseek"]["base_url"]
)
# 2. 读取待审查的代码
code_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"oneWeek", "day1~2", "pythonchan.py"
)
with open(code_path, "r", encoding="utf-8") as f:
code_content = f.read()
# 3. 构建消息(System + User)
messages = [
{
"role": "system",
"content": """你是一个高级Python代码审查专家。你的职责是:
1. 找出潜在的Bug
2. 指出性能问题
3. 发现安全风险
4. 给出重构建议
请按照以下格式回答:
- 【Bug】问题描述 + 所在行 + 修复建议
- 【性能】问题描述 + 优化建议
- 【安全】问题描述 + 加固建议
- 【重构】代码改进建议
如果某类没有问题,可以不输出该类。"""
},
{
"role": "user",
"content": f"请审查以下Python代码:\n\n```\n{code_content}\n```"
}
]
# 4. 发送请求
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
temperature=0.2, # 代码审查要严谨
max_tokens=2048, # 给足输出空间
stream=False
)
# 5. 输出结果
print("=" * 60)
print("代码审查报告")
print("=" * 60)
print(response.choices[0].message.content)
print(f"\n消耗tokens: {response.usage.total_tokens}")
8.2 运行效果
运行后会输出类似这样的审查报告:
============================================================
代码审查报告
============================================================
【Bug】问题描述:
1. 第41行:filename[0:2].isdigit() 在 filename 长度小于2时会报错
所在行:41
修复建议:改为 len(filename) >= 2 and filename[0:2].isdigit()
2. 第28行:finally 块中没有实际逻辑,是多余的
所在行:28
修复建议:删除 finally 块
【性能】问题描述:
1. 第39-48行:手动枚举构建新文件名,可以用 enumerate 简化
优化建议:使用列表推导式
【安全】问题描述:
1. 第55行:os.rename 直接使用用户输入的路径,可能存在路径穿越风险
加固建议:对 folder_path 做合法性校验
【重构】代码改进建议:
1. 将文件命名逻辑抽取为独立函数,提高可测试性
2. 使用 pathlib.Path 替代 os.path,代码更简洁
============================================================
消耗tokens: 1856
九、总结
今天我们一起学了:
| 知识点 | 核心要点 |
|---|---|
| API 调用 | 所有大模型都遵循 OpenAI 兼容协议,换模型只需改 base_url |
| Request/Response | messages 数组 + model 是核心,response 里有 token 消耗信息 |
| System/User/Assistant | system 定规矩,user 提问题,assistant 给答案;多轮对话要传历史消息 |
| 参数调优 | temperature 控随机性,top_p 控词范围,max_tokens 控长度 |
| 流式输出 | stream=True 逐字推送,适合聊天界面 |
| Few-shot | 给模型看几个例子,让它学会你的格式 |
下一步
下期我们将深入 RAG 系统的进阶优化:
- 向量检索的多种策略(相似度、关键词、混合检索)
- Chunking 策略怎么选(500 vs 800 vs 1000?)
- Rerank 重排序:让检索结果更精准
- 如何用 LangChain 简化 RAG 开发
本文代码基于 openai Python SDK,适配 DeepSeek、智谱 GLM 等国内大模型。