大模型接入(1)-单轮对话
·
一次性输出全部结果
同步对话:先写个能跑的测试脚本
client = OpenAI(
# 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx"
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="自己的",
)
def llm_chat(messages,model="qwen3.7-plus"):
completion = client.chat.completions.create(
# 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
model=model,
messages=messages
# temperature=0.7,
# max_completion_tokens = 100
)
return completion.choices[0].message.content
if __name__ == '__main__':
messages= [{"content":"你是一个游戏专家","role":"system"},{"content":"最近比较多的射击类的游戏有哪些?","role":"user"}]
res=llm_chat(messages)
print(res)
client是整个对话的"发件人",所有请求都从它发出去,全局只需要建一个。
llm_chat()是我们的核心工具函数:入参是对话数组 + 模型名,出参是模型吐出来的纯文本。把它单独抽出来,本地脚本、接口、定时任务都能复用。
system消息很关键,它决定模型的语气和能力边界;user才是真正的问题。
- 注释掉的两行是常用旋钮:
temperature控随机性,max_completion_tokens控长度,真上线建议都打开。
跑一下 python xxx.py,能打印出游戏列表就说明链路通了,可以往下走。
把同步问答封成异步接口
脚本跑通了,但总不能让前端每次都来跑你的 py 文件。下面用 FastAPI 把它变成一个 HTTP 接口。
client = AsyncOpenAI(
# 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx"
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="自己的",
)
@llm_day01_router.post("/case1", summary="LLM-DAY01-CASE1")
async def case1_api(llmCase1Request: LLMCase1):
completion = await client.chat.completions.create(
model="qwen3.7-plus",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": llmCase1Request.question}
],
temperature=0.7,
)
ai_reply = completion.choices[0].message.content
return {
"code":1,
"message":"请求成功",
"data":{
"ai_reply":ai_reply
}
}
LLMCase1:用 Pydantic 定义的入参结构,前端 JSON 里只要带question字段就行,FastAPI 会自动校验类型。
AsyncOpenAIvsOpenAI:接口场景必须用异步客户端。同步客户端在等模型返回的那几秒会霸占 worker,并发一上来就全堵了;异步的能把这段时间让给其他请求。
case1_api是真正处理请求的函数:await ...create(...)发起调用,completion.choices[0].message.content取正文,最后包成{code, message, data}这种前后端都舒服的格式返回。
summary是接口文档(Swagger)里显示的说明,方便别人看你的 API。
启动:uvicorn main:app --reload,然后 POST http://127.0.0.1:8000/case1,body 传 {"question": "你好"} 就能拿到回复。
流式输出
同步接口的问题是:用户点完发送,要等模型把整段话憋完才一次性返回,慢一点的模型能卡好几秒,体验很差。流式就是让字一个一个往外蹦。
打字机效果(测试脚本)
# 1. 准备工作:初始化客户端
client = OpenAI(
# 建议通过环境变量配置API Key,避免硬编码。
api_key=os.environ["DASHSCOPE_API_KEY"],
# API Key与地域强绑定,请确保base_url与API Key的地域一致。
base_url="1",
)
# 2. 发起流式请求
completion = client.chat.completions.create(
model="qwen3.7-plus",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "请介绍一下自己"}
],
stream=True,
stream_options={"include_usage": True}
)
# 3. 处理流式响应
# 用列表暂存响应片段,最后 join 比逐次 += 字符串更高效
content_parts = []
print("AI: ", end="", flush=True)
for chunk in completion:
if chunk.choices:
content = chunk.choices[0].delta.content or ""
print(content, end="", flush=True)
content_parts.append(content)
elif chunk.usage:
print("\n--- 请求用量 ---")
print(f"输入 Tokens: {chunk.usage.prompt_tokens}")
print(f"输出 Tokens: {chunk.usage.completion_tokens}")
print(f"总计 Tokens: {chunk.usage.total_tokens}")
full_response = "".join(content_parts)
# print(f"\n--- 完整回复 ---\n{full_response}")
stream=True让服务端一边生成一边推数据,客户端边收边打,所以你会看到字是"流"出来的。
delta.content是这一小片增量内容,None的时候代表这一帧没有文本(比如纯 usage 帧),所以用or ""兜底。
stream_options={"include_usage": True}是 optional 的,开了之后流尾会多一个chunk.usage帧,方便你统计花了多少 token——要算钱的话这个必须有。
content_parts收集全部片段,后面如果想把完整回答写库、做摘要,直接用full_response。
把流式输出封成 SSE 接口
最后一步,把流式搬到接口上,用 SSE(Server-Sent Events)推给前端,前端就能做实时打字机了。
client = AsyncOpenAI(
# 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx"
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="htmode/v1",
)
#流式输出
async def stream_chunk(user_question:str):
# 2. 发起流式请求
completion = await client.chat.completions.create(
model="qwen3.7-plus",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": user_question}
],
stream=True,
stream_options={"include_usage": True}
)
async for chunk in completion:
if chunk.choices:
choice = chunk.choices[0]
if choice.delta:
delta = choice.delta
if delta.content:
yield f"data: {delta.content}\n\n"
yield "data: [DONE]\n\n"
@llm_day01_router.post("/case2", summary="LLM-DAY01-CASE2")
async def case2_api(llmCase2Request: LLMCase1):
return StreamingResponse(
content=stream_chunk(llmCase2Request.question),
media_type="text/event-stream")
stream_chunk()是个 async 生成器(函数里有yield)。它被StreamingResponse包起来后,FastAPI 会边调边把数据推给客户端,全程不占着内存攒完整结果。
- SSE 的报文格式有讲究:
data: 内容\n\n,前缀data:加结尾两个换行,前端用EventSource或fetch读流时才能正确切片。少一个\n前端都可能收不到。
yield "data: [DONE]\n\n"是约定俗成的结束标记,前端拿到它就可以关掉加载动画、把输入框解锁。
media_type="text/event-stream"不能漏,它告诉 HTTP 这一路是事件流而不是普通 JSON。
case2_api本身很薄:拿到问题 → 交给生成器 → 用StreamingResponse包一下返回。真正的活都在stream_chunk里。
更多推荐


所有评论(0)