AI Agent 30天速成|Day1 核心知识点全解析

AI Agent 全日制30天速成|Day1 核心知识点全解析

前言

本文是AI Agent 30天速成系列的第一天学习笔记,聚焦大模型基础核心概念、异步LLM调用封装、提示工程核心技巧与SSE流式接口开发,所有代码可直接运行,适合AI Agent开发入门学习者。

一、今日核心学习目标

  1. 吃透大模型Token、上下文窗口、核心调参、流式输出等基础概念
  2. 封装兼容OpenAI标准的异步LLM调用工具类(适配通义千问、DeepSeek)
  3. 掌握提示工程核心技巧,强制模型输出标准JSON格式
  4. 实现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_urlapi_key,即可用一套代码对接所有模型。

2. 异步调用核心(Python后端重点)

  • 同步requests:阻塞线程,高并发场景性能极差
  • aiohttp异步:适配多并发对话、批量向量生成、流式输出,需封装超时控制、自动重试、异常捕获、请求限速

3. 提示工程三大核心

  1. CoT思维链:在Prompt中加入「请一步步思考,再给出最终答案」,减少推理类幻觉
  2. Few-shot少样本学习:提供1~3条标准输入输出示例,对齐输出格式
  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)

五、实操练习任务

  1. 替换代码中的API Key,运行llm_client.py测试JSON结构化输出,观察Pydantic校验效果
  2. 启动FastAPI服务,访问http://127.0.0.1:8000/docs调试流式接口
  3. 修改temperature参数(0/0.9),对比模型输出随机性差异
  4. 构造模糊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种方案?

  1. Prompt强约束:明确格式要求,禁止多余内容
  2. 接口参数指定:使用response_format=json_object(OpenAI)或厂商对应参数
  3. Function Calling:将JSON定义为函数参数Schema
  4. 后处理兜底:正则提取JSON+Pydantic校验+解析失败重试
  5. 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开发的核心底层能力。后续将基于此拓展多轮对话、记忆模块、工具调用等进阶内容,建议反复调试代码,吃透面试高频考点。