AI Agent 全日制30天速成|Day1 核心知识点全解析
前言
本文是AI Agent 30天速成系列的第一天学习笔记,聚焦大模型基础核心概念、异步LLM调用封装、提示工程核心技巧与SSE流式接口开发,所有代码可直接运行,适合AI Agent开发入门学习者。
一、今日核心学习目标
- 吃透大模型Token、上下文窗口、核心调参、流式输出等基础概念
- 封装兼容OpenAI标准的异步LLM调用工具类(适配通义千问、DeepSeek)
- 掌握提示工程核心技巧,强制模型输出标准JSON格式
- 实现SSE流式返回简易接口,夯实Agent对话底层基础
二、核心理论知识点
1. 大模型基础核心概念
(1)Token与上下文窗口
- Token:文本处理最小单位,中文1个汉字≈2token,英文1单词≈1token;是计费、上下文长度限制的核心单位。
- 上下文窗口:模型单次请求输入+输出的Token总量上限(如qwen-turbo为128k),超限会导致文本截断、上下文丢失、输出不完整。
(2)高频调参(面试必考)
| 参数 | 作用 | 业务取值建议 |
|---|---|---|
| temperature | 控制输出随机性,0=完全确定,1=自由发散 | 知识库/工具调用:0.00.1;文案创作:0.70.9 |
| top_p | 核采样,控制候选词范围 | 工具场景0.1,创作场景0.8 |
| max_tokens | 限制单次返回最大输出长度 | 工具查询设512,长文档总结设2048 |
(3)两种输出模式
- 同步complete:一次性返回完整结果,适合简单问答、批量处理场景
- 流式stream(SSE):分段实时输出,前端聊天框、Agent产品必备
(4)统一接口规范
国内主流模型均兼容OpenAI v1接口格式,仅需更换base_url和api_key,即可用一套代码对接所有模型。
2. 异步调用核心(Python后端重点)
- 同步requests:阻塞线程,高并发场景性能极差
- aiohttp异步:适配多并发对话、批量向量生成、流式输出,需封装超时控制、自动重试、异常捕获、请求限速
3. 提示工程三大核心
- CoT思维链:在Prompt中加入「请一步步思考,再给出最终答案」,减少推理类幻觉
- Few-shot少样本学习:提供1~3条标准输入输出示例,对齐输出格式
- 结构化输出约束:强制返回JSON格式,适配Function Calling、RAG检索、工具参数解析场景
三、今日开发难点 & 解决方案
难点1:流式SSE分段乱码、截断、合并错乱
- 按
data:分隔流式返回块,过滤空行、[DONE]结束标记 - 缓冲区缓存不完整分片,拼接完整后再解析JSON
- 封装全局复用的流式解析工具函数
难点2:模型返回自然文字而非标准JSON
- Prompt明确禁止额外描述,仅返回JSON
- 正则提取{}内的JSON片段
- Pydantic模型二次校验,解析失败自动重调模型
难点3:多模型base_url、鉴权头不统一
- 配置类预设各模型独立参数,代码仅传模型名称自动匹配
难点4:异步并发触发平台限流
- 信号量控制并发数
- 捕获429限流异常,实现指数退避重试
四、可直接运行的完整代码
1. 通用LLM异步封装类(llm_client.py)
python
import aiohttp
import asyncio
import re
from pydantic import BaseModel, Field
from typing import Optional, AsyncGenerator
# 模型配置映射
MODEL_CONFIG = {
"qwen-turbo": {
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
"api_key": "你的通义千问key"
},
"deepseek-chat": {
"base_url": "https://api.deepseek.com/v1/chat/completions",
"api_key": "你的deepseek key"
}
}
# 结构化输出校验模型示例
class OrderQueryResp(BaseModel):
order_name: str = Field(description="订单名称")
order_price: float = Field(description="订单价格")
delivery_status: str = Field(description="配送状态")
class AsyncLLMClient:
def __init__(self, model_name: str = "qwen-turbo"):
self.model = model_name
self.conf = MODEL_CONFIG[model_name]
self.semaphore = asyncio.Semaphore(5) # 最大并发5
async def _request(self, payload: dict, stream: bool = False):
headers = {
"Authorization": f"Bearer {self.conf['api_key']}",
"Content-Type": "application/json"
}
timeout = aiohttp.ClientTimeout(total=60)
async with self.semaphore:
async with aiohttp.ClientSession(timeout=timeout) as session:
try:
async with session.post(self.conf["base_url"], json=payload, headers=headers) as resp:
if resp.status != 200:
err = await resp.text()
raise Exception(f"模型接口异常: {resp.status}, {err}")
if stream:
return self._stream_parse(resp)
return await resp.json()
except aiohttp.ClientError as e:
raise Exception(f"网络请求失败: {str(e)}")
async def _stream_parse(self, response) -> AsyncGenerator[str, None]:
buffer = ""
async for chunk in response.content.iter_chunked(1024):
buffer += chunk.decode("utf-8")
# 分割流式块
while "data:" in buffer:
idx = buffer.find("data:")
end_idx = buffer.find("\n\n", idx)
if end_idx == -1:
break
data_block = buffer[idx+5:end_idx].strip()
buffer = buffer[end_idx+2:]
if data_block == "[DONE]":
return
try:
import json
data = json.loads(data_block)
content = data["choices"][0]["delta"].get("content", "")
if content:
yield content
except:
continue
async def chat_sync(self, prompt: str, temperature: float = 0.1) -> str:
payload = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature,
"stream": False
}
res = await self._request(payload)
return res["choices"][0]["message"]["content"]
async def chat_stream(self, prompt: str, temperature: float = 0.1):
payload = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature,
"stream": True
}
async for text in await self._request(payload, stream=True):
yield text
# 强制JSON输出封装
async def chat_json(self, prompt: str, resp_model: BaseModel) -> BaseModel:
json_prompt = f"""
{prompt}
严格要求:仅返回标准JSON,禁止任何解释、注释、markdown、多余文字。
JSON字段要求:{resp_model.model_json_schema()}
"""
raw = await self.chat_sync(json_prompt, temperature=0.0)
# 正则提取JSON
match = re.search(r"\{.*\}", raw, re.S)
if not match:
# 解析失败重试一次
raw = await self.chat_sync(json_prompt, temperature=0.0)
match = re.search(r"\{.*\}", raw, re.S)
if not match:
raise Exception("模型无法输出合法JSON")
json_str = match.group()
return resp_model.model_validate_json(json_str)
# 测试入口
async def test_client():
client = AsyncLLMClient("qwen-turbo")
# 测试1:同步JSON结构化输出
res = await client.chat_json(
prompt="模拟一个手机订单信息",
resp_model=OrderQueryResp
)
print("结构化结果:", res.model_dump())
# 测试2:流式输出
print("\n流式输出:")
async for chunk in client.chat_stream("简单介绍AI Agent"):
print(chunk, end="")
if __name__ == "__main__":
asyncio.run(test_client())
2. FastAPI流式对话接口(main.py)
python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
from llm_client import AsyncLLMClient
app = FastAPI(title="Day1 LLM流式服务")
llm = AsyncLLMClient()
# SSE封装生成器
async def stream_generator(user_prompt: str):
yield "data: 开始生成回答\n\n"
async for text in llm.chat_stream(user_prompt):
yield f"data: {text}\n\n"
yield "data: [DONE]\n\n"
@app.get("/chat/stream")
async def chat_stream(prompt: str):
return StreamingResponse(
stream_generator(prompt),
media_type="text/event-stream"
)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", reload=True)
五、实操练习任务
- 替换代码中的API Key,运行
llm_client.py测试JSON结构化输出,观察Pydantic校验效果 - 启动FastAPI服务,访问
http://127.0.0.1:8000/docs调试流式接口 - 修改
temperature参数(0/0.9),对比模型输出随机性差异 - 构造模糊Prompt,验证模型JSON输出重试逻辑
六、高频面试题 & 标准答案
基础问答
1. 什么是Token?上下文窗口超限会出现什么问题?
Token是大模型处理文本的最小单位(中文1字≈2token,英文1单词≈1token),是计费和上下文限制的核心单位。
上下文窗口超限后果:输入被截断导致上下文丢失、多轮对话失忆;输出不完整、逻辑断裂;接口返回400错误;长文本生成任务提前终止。
2. temperature参数作用?工具调用为何推荐0~0.1?
temperature控制输出随机性(0~2),值越低输出越确定,值越高越发散。
工具调用要求参数精准,高随机性会导致参数非法、格式错乱,0~0.1接近贪心采样,能最大化结构化参数准确率。
3. 同步输出与流式SSE的区别及适用场景?
- 同步输出:一次性返回完整结果,适合后端调用、批量任务、离线处理
- 流式SSE:分段实时输出,适合前端聊天界面、长文本生成,降低用户等待感知
4. 主流模型兼容OpenAI接口的原因?
OpenAI是行业事实标准,兼容后可一套代码对接多模型、复用生态工具链、降低学习成本、灵活选型对比效果。
5. 大模型接口异常及处理方案?
- 超时:设置分级超时,搭配指数退避重试
- 429限流:信号量控并发、速率限制、指数退避重试
- 401鉴权失败:校验密钥,触发告警不重试
- 格式异常:正则提取+Pydantic校验+自动重试
工程实操题
1. 强制模型返回标准JSON的3种方案?
- Prompt强约束:明确格式要求,禁止多余内容
- 接口参数指定:使用
response_format=json_object(OpenAI)或厂商对应参数 - Function Calling:将JSON定义为函数参数Schema
- 后处理兜底:正则提取JSON+Pydantic校验+解析失败重试
- 设
temperature=0降低随机性
2. 流式SSE分片截断的解决方案?
- 缓冲区缓存不完整分片,按
data:拆分完整块后解析 - 仅处理完整的SSE协议行,捕获单条解析异常不中断整体流
- 全量拼接后二次校验格式
3. requests同步 vs aiohttp异步(并发场景)
| 维度 | requests(同步) | aiohttp(异步) |
|---|---|---|
| 并发模型 | 多线程/多进程,阻塞IO,切换开销大 | 单线程协程,非阻塞IO,切换开销极低 |
| 并发上限 | 线程数受限,高并发资源占用高 | 单线程支撑上千并发,资源占用低 |
| 开发成本 | 逻辑简单,调试容易 | 需async/await改造,调试复杂度高 |
| 适用场景 | 低并发脚本、简单单次调用 | 高并发批量调用、服务端后端、Agent多工具并行 |
4. 控制并发防止429限流的方案?
- 信号量限制并发数
- 令牌桶/漏桶控制QPS
- 捕获429后指数退避重试
- 连接池复用减少建连开销
- 限流时降级到备用模型/缓存结果
拓展思考题
1. 多轮对话Token接近上限的优化方案?
- 滑动窗口:仅保留最近N轮对话
- 对话摘要:用模型生成历史对话精简摘要
- 向量检索:仅召回与当前问题相关的历史片段
- 分层记忆:拆分短期/长期记忆,按需提取
- 冗余清洗:精简无效文本
2. 工具调用为何必须结构化JSON输出?
- 机器可直接解析,无需NLP处理
- 格式稳定,降低参数错误概率
- 可前置校验字段/类型,快速发现问题
- 适配通用Agent框架,降低耦合度
- 异常易追溯、易兜底
总结
Day1重点掌握大模型基础概念、异步LLM封装、结构化输出与SSE流式接口开发,这些是AI Agent开发的核心底层能力。后续将基于此拓展多轮对话、记忆模块、工具调用等进阶内容,建议反复调试代码,吃透面试高频考点。