月度归档:2026年03月

中间件

中间件(Middleware)是一个在每次请求进入 FastAPI 应用时都会被执行的函数。
它在请求到达实际的路径操作(路由处理函数)之前运行,并且在响应返回给客户端之前再运行一次。

作用: 为每个请求添加统一的处理逻辑(记录日志、身份认证、跨域、设置响应头、性能监控等)

@app.middleware('http')
async def add_process_time_header(request: Request, call_next):
print('process_time_header called')
response = await call_next(request)
print('process_time_header called with response {}'.format(response))

代码如上 在访问根路径时会报错

原因: 缺少return 框架后面会把“返回的响应”当成可执行对象继续处理;现在拿到的是 None ,所以报:

TypeError: ‘NoneType’ object is not callable

修改代码

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
print("process_time_header called")
response = await call_next(request)
print("process_time_header called with response {}".format(response))
return response

再次访问 控制台输出日志

process_time_header called
process_time_header called with response
INFO: 127.0.0.1:60206 – “GET / HTTP/1.1” 200 OK

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
print("process_time_header called")
response = await call_next(request)
print("process_time_header called with response {}".format(response))
return response

@app.middleware("http")
async def middleware_test(request,call):
print("middleware_test start")
res = await call(request)
print("middleware_test end")
return res

多个中间件执行顺序是由下而上的

middleware_test start
process_time_header called
process_time_header called with response
middleware_test end
INFO: 127.0.0.1:60431 – “GET / HTTP/1.1” 200 OK

响应类型

默认情况下,FastAPI会自动将路径操作函数返回的 Python对象(字典、列表、Pydantic 模型等),经由jsonable_encoder 转换为JSON 兼容格式,并包装为 JSONResponse 返回。这省去了手动序列化的步骤,让开发者能更专注于业务逻辑。如果需要返回非 JSON 数据(如 HTML、文件流),FastAP! 提供了丰富的响应类型来返回不同数据

自定义响应格式

response_model 是路径操作装饰器(如 @app.get或 @app.post)的关键参数,它通过一个 Pydantic 模型来严格定义和约束 AP! 端点的输出格式。这一机制在提供自动数据验证和序列化的同时,更是保障数据安全性的第一道防线。

class News(BaseModel):
    id: int
    title: str
    content: str


@app.get("/news/{id}", response_model=News)
async def get_news(id: int):
    return {
        "id": id,
        "title": f"这是第{id}本书",
        "content": "这是一本好书"
    }

参数

不同情况下的参数作用与意义各不相同

1.路径参数

@app.get("/book/{id}")
async def get_book(id: int):
    return {"id": id}

Path

@app.get("/get_name/{name}")
async def get_nanme(name: str = Path(...,max_length=10,min_length=2,description='长度范围2-10')):
    return {"name": name}

通过导入Path 可以控制参数的一些条件

第一个位置的 … 代表该参数必填

max_length和min_length分别限制了最大和最小长度

description 表示注解

2.查询参数

@app.get("/news/news_list")
async def get_news_list(
        page: int = Query(default=1, ge=1, le=10,description='范围在1-10'),
        limit: int = Query(default=5, ge=1, le=10),
        skip: int = Query(default=0, ge=0, le=10),
):
    
    return {"page": page, "limit": limit, "skip": skip}

Query

查询参数可以通过Query来控制

3.请求体参数

class User(BaseModel):
    username: str = Field(...,min_length=2,max_length=10,description='输入用户名,长度2-10')
    password: str = Field(...,min_length=2,max_length=10,description='输入密码,长度2-10')
    age: int

@app.post("/register")
async def register(user: User):
    return user

Filed

这里输入注解时是不提醒的

当输入类型错误时响应会有提示

创建web项目

运行服务

访问/docs (交互式文档)出现报错

可以看到这里解释器版本是3.9

执行命令

python -c "import sys,fastapi,pydantic,pydantic_core,importlib.metadata as m; print(sys.version); print('fastapi',fastapi.__version__); print('pydantic',pydantic.__version__); print('pydantic_core',pydantic_core.__version__); print('typing-inspection',m.version('typing-inspection'))"
3.9.0 (v3.9.0:9cf6752276, Oct  5 2020, 11:29:23) 
[Clang 6.0 (clang-600.0.57)]
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: module 'typing_inspection' has no attribute '__version__'

astAPI 已经移除 3.8 支持,并持续跟进新版本 Python(已提到支持到 3.14)

选择更新解释器版本并且重新配置虚拟环境,这里为了方便,重新创建项目并且选择比较适配的3.11版本

创建完成后重新运行服务并且访问/docs (交互式文档)

.env文件配置命名问题

今天配置.env文件发现一个BUG 代码执行是保存未获取到对应秘钥名称

.env文件内存在该名称

错误原因:

for k, v in os.environ.items ( ) :

if k.isupper ( ) :

先按所需的组合键,再按 Enter 键。

这个问题的原因是 server/settings.py 文件中有一段自动加载环境变量的代码

这段代码只会将 全大写 的环境变量自动注入到 Django 的 settings 中。由于您的变量名 例:Key 包含了小写字母,它被这段逻辑忽略了,导致 settings 对象中没有这个属性。

这个行为是由 server/settings.py 文件底部的这几行代码决定的:

for k, v in os.environ.items():

if k.isupper(): # <— 关键在这里

globals().setdefault(k, v)

所以将 Key 命名修改为 KEY即可

LangChain

  • LangChain 是地基。它定义了最基础的组件:什么是 Model,什么是 Prompt,什么是 Memory,什么是 Tool。
    • CrewAI 和 AutoGen 在底层大量复用了 LangChain 的概念(尤其是 CrewAI,它直接依赖 LangChain 的组件)。
    • 学习策略:只要懂了 LangChain 的核心概念(Chain, Agent, Tool),看 CrewAI 的代码就像看“封装好的高级脚本”,非常容易上手。
  • LangGraph 是关键进化:它是 LangChain 团队为了解决复杂 Agent 流程控制而推出的新核心库,代表了从“链式(Chain)”到“图式(Graph)”的思维转变。

基础概念

  1. 模型(Models):LLM的“大脑”
    模型是LangChain的核心动力,负责理解文本、生成内容。LangChain支持几乎所有主流LLM(比如OpenAI、GPT-4、LLaMA、Claude等),并统一了调用接口。

两种核心模型类型:

LLM:输入文本,输出文本(比如GPT-3.5的text-davinci-003)。

ChatModel:输入“消息列表”(比如用户消息、系统提示),输出“消息”(更适合对话场景,比如GPT-4的gpt-4)。

作用:负责核心的“思考”和“生成”工作,是整个应用的“智能来源”。

  1. 提示(Prompts):给模型的“指令”
    提示词是告诉模型“该做什么”的指令。LangChain的PromptTemplate能帮你动态生成提示词,避免手动拼接字符串的麻烦。

示例:
如果你想让模型生成“给产品起名字”,可以定义一个模板:
PromptTemplate(input_variables=[“product”], template=”给一个{product}起3个有科技感的名字”)
当传入product=”智能手表”时,自动生成提示词:“给一个智能手表起3个有科技感的名字”。

作用:精准控制模型的输出,让模型按你的需求工作(比如摘要、翻译、问答等)。

  1. 链(Chains):组件的“连接器”
    单个模型+提示只能完成简单任务(比如生成一句话),但复杂任务需要多步操作(比如先翻译再总结)。链就是把多个组件(模型、提示、其他链)按顺序组合起来的工具。

常见链类型:

LLMChain:最基础的链,由“提示模板”+“模型”组成,直接输出结果。

SequentialChain:按顺序执行多个链(比如链1的输出作为链2的输入)。

RouterChain:根据输入内容“选择”不同的链执行(比如判断问题类型,再调用对应链)。

示例:用SequentialChain实现“先翻译英文到中文,再总结中文内容”:
链1(翻译)→ 链2(总结),输入英文,输出总结后的中文。

  1. 记忆(Memory):让模型“记住”历史
    默认情况下,LLM是“健忘的”——每次调用都是独立的,不知道之前的对话。记忆组件的作用是保存对话历史,并把历史内容“喂”给模型,让对话有连贯性。

常见记忆类型:

ConversationBufferMemory:简单保存所有对话历史(适合短对话)。

ConversationSummaryMemory:自动总结对话历史(适合长对话,避免内容过长)。

ConversationTokenBufferMemory:按“Token数量”保存历史(防止超过模型输入限制)。

示例:聊天机器人中,用ConversationBufferMemory保存“用户问了什么”和“AI答了什么”,每次新对话时,把历史内容和新问题一起传给模型,模型就知道上下文了。

  1. 工具(Tools):模型的“外挂能力”
    LLM的知识截止到训练数据(比如GPT-4截止到2023年10月),也不会实时计算、查天气。工具是模型可以调用的外部能力,帮模型弥补这些缺陷。

常见工具类型:

搜索引擎(比如Google Search,查实时信息);

计算器(比如llm-math,做精确计算);

数据库(比如SQL工具,查数据库数据);

自定义函数(比如你自己写的“查天气”API)。

作用:让模型从“只能凭记忆回答”变成“能主动调用工具获取信息”。

  1. 代理(Agents):工具的“决策者”
    有了工具,谁来决定“什么时候用什么工具”?——代理。

代理是一个“决策者”:它会分析用户的问题,判断是否需要调用工具(如果需要,调用哪个工具),并根据工具返回的结果生成最终答案。

常见代理类型:

Zero-shot React Description:根据工具的“描述”直接决定调用(适合简单场景);

Conversational React Description:带记忆的代理(适合对话场景,能结合历史决定调用);

Structured Input:处理结构化输入(比如表格数据)的代理。

示例:用户问“今天北京的气温是多少?”
代理分析:“这个问题需要实时数据,模型不知道,所以调用天气工具”→ 调用工具获取气温→ 整理结果回答用户。

UV的使用以及与pip的区别

uv 与 pip 的核心区别

# 1. 创建虚拟环境(如果尚未创建)

uv venv

# 2. 激活虚拟环境

source .venv/bin/activate

# 3. 安装项目依赖(使用 uv 替代 pip,速度飞快)

uv pip install -r requirements.txt(我这里是uv与requirements.txt混用)

可以看到安装时报错了原因是在 macOS 上安装 mysqlclient 失败,因为系统缺少 MySQL 的 C 开发库(header files)构建工具无法找到它们

执行brew install mysql-client pkg-config

# 设置 pkg-config 路径以便找到 mysqlclient

export PKG_CONFIG_PATH=”$(brew –prefix mysql-client)/lib/pkgconfig”

而后重新执行uv pip install -r requirements.txt

安装完成

# 4. 同步开发工具(如 pre-commit,这是目前 uv.lock 管理的内容)

uv sync