LangChain-AI应用开发框架(六)

目录

一.聊天模型--结构化输出

1.with_structured_output()

a.返回Pydantic对象

b.返回TypedDict(返回字典的写法)

c.返回JSON

d.选择输出格式

2.实用场景

a.场景1:作为信息提取器

b.场景2:使用"少样本提示"来增强信息提取能力

c.场景3:与工具结合使用

[方式1:使用 with_structured_output() 的 tools 参数](#方式1:使用 with_structured_output() 的 tools 参数)

二.聊天模型--流式传输

1.stream()同步传输

2.astream()异步传输

a.异步相关概念

b.使用

3.使用StrOutputParser解析模型的输出

4.自定义流式输出解析器

5.深度探索流式传输

a.SSE协议介绍

b.LangChain流式传输流程分析

通过源码分析流程

LangChain请求OpenAI使用什么协议?

LangChain如何支持流式传输?

[OpenAI返回的块是什么格式,如何转换成 AIMessageChunk ?](#OpenAI返回的块是什么格式,如何转换成 AIMessageChunk ?)

三.使用LangSmith跟踪LLM应用


一.聊天模型--结构化输出

python 复制代码
model = ChatOpenAI()
response = model.invoke("告诉我关于苹果公司的最新消息。")
print(response.content)
# 输出: "苹果公司于昨⽇发布了新款iPhone...其股价上涨了2%..." 
python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel,Field
from typing import Optional

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)

print(model.invoke("将一个关于唱歌笑话,10个字").content)

1.with_structured_output()

a.返回Pydantic对象

python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel, Field
from typing import Optional

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.7
)

# 你的模型完全正确 ✅
class Joke(BaseModel):
    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(default=None, description="1-10分,这个笑话几分?")

model_with_structured = model.with_structured_output(Joke)

# 关键:必须让 AI 返回你模型里的三个字段!!!
result = model_with_structured.invoke("""
请讲一个关于唱歌的笑话。
必须严格返回JSON,只包含以下三个字段:
1. setup:笑话开头
2. punchline:笑话笑点
3. rating:1-10分评分

不要任何多余文字、解释、标点、符号,只返回纯JSON!
""")

print(result)
python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel, Field
from typing import Optional, List

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.7
)

# 笑话模型
class Joke(BaseModel):
    """给用户讲的一个笑话"""
    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(default=None, description="1-10分,这个笑话几分?")

# 数据模型(包含笑话列表)
class Date(BaseModel):
    """获取关于笑话的数据列表"""
    jokes: List[Joke]

model_with_structured = model.with_structured_output(Date)

# ✅ 关键修复:告诉AI返回 { "jokes": [ {...} ] } 格式!
result = model_with_structured.invoke("""
请生成1个关于唱歌和跳舞的笑话。
必须返回严格的JSON格式,结构如下:
{
  "jokes": [
    {
      "setup": "笑话开头",
      "punchline": "笑话笑点",
      "rating": 评分(1-10)
    }
  ]
}
只返回纯JSON,不要任何多余文字、解释、标点!
""")

print(result)

国内AI,可能对于我们with_structured_output(),支持性没有那么好,所以很多内容我们要写在提示词里面(我们可以尝试换国外的大模型,入OpenAI就可以不用写提示词,直接使用)

b.返回TypedDict(返回字典的写法)

python 复制代码
from typing import TypedDict
class User(TypedDict):
 name: str
 age: int
 email: str
 is_active: bool = True # 默认值 
python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel, Field
from typing import Optional, List, TypedDict

from typing import Annotated  # 必须是这个!

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.7
)


class Joke(TypedDict):
    """给用户讲的一个笑话"""
    setup: Annotated[str,...,"这个笑话的开头"]
    punchline: Annotated[str,...,"这个笑话的妙语"]
    rating: Annotated[Optional[int], None, "从1-10分,给这个笑话评分"]

model_with_structured = model.with_structured_output(Joke)

result = model_with_structured.invoke("""
    讲一个关于跳舞的笑话,
    必须返回JSON格式,包含三个字段:setup、punchline、rating
    不要任何多余文字,只返回JSON!
""")

print(result)
python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel, Field
from typing import Optional, List, TypedDict

from typing import Annotated  # 必须是这个!

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.7
)


class Joke(TypedDict):
    """给用户讲的一个笑话"""
    setup: Annotated[str,...,"这个笑话的开头"]
    punchline: Annotated[str,...,"这个笑话的妙语"]
    rating: Annotated[Optional[int], None, "从1-10分,给这个笑话评分"]

model_with_structured = model.with_structured_output(Joke,include_raw=True)

result = model_with_structured.invoke("""
    讲一个关于跳舞的笑话,
    必须返回JSON格式,包含三个字段:setup、punchline、rating
    不要任何多余文字,只返回JSON!
""")

print(result)

这样就将完整的AI大模型调用的结果全部展现出来

c.返回JSON

python 复制代码
from langchain_openai import ChatOpenAI
import os
from pydantic import BaseModel, Field
from typing import Optional, List, TypedDict
from typing import Annotated

api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.7
)

json_schema = {
    "title": "joke",
    "description": "给⽤⼾讲⼀个笑话。",
    "type": "object",
    "properties": {
        "setup": {
            "type": "string",
            "description": "这个笑话的开头",
        },
        "punchline": {
            "type": "string",
            "description": "这个笑话的妙语",
        },
        "rating": {
            "type": "integer",
            "description": "从1到10分,给这个笑话评分",
            "default": None,
        },
    },
    "required": ["setup", "punchline"],
}

model_with_structured = model.with_structured_output(json_schema, include_raw=True)

# 👇 提示词我帮你写好了,强制按 JSON 格式输出
result = model_with_structured.invoke("""
讲一个关于唱歌、跳舞的笑话。
必须严格按照 JSON 格式返回,只包含 setup、punchline、rating 三个字段。
不要加任何多余文字、解释、标点符号,只返回纯 JSON。
""")

print(result)

d.选择输出格式

python 复制代码
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Optional, Union
import os

# 你的 GLM-5 配置
api_key = os.getenv("ZHIPU_API_KEY")
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key,
    temperature=0.1
)

# 结构定义
class Joke(BaseModel):
    """给⽤⼾讲⼀个笑话。"""
    setup: str = Field(description="笑话开头")
    punchline: str = Field(description="笑话妙语")
    rating: Optional[int] = Field(default=None, description="1-10分")

class ConversationalResponse(BaseModel):
    """友善对话回应"""
    response: str = Field(description="对话回复")

class FinalResponse(BaseModel):
    final_output: Union[Joke, ConversationalResponse]

# 绑定结构化输出
structured_model = model.with_structured_output(FinalResponse)

# ==============================
# 🔥🔥🔥 强制 GLM 必须套 final_output!(这次绝对听话)
# ==============================
prompt1 = """
【严格格式命令,必须遵守】
你必须返回 JSON,且最外层必须有一个字段叫:final_output
根据用户输入选择内容:
- 要讲笑话 → final_output 填写 Joke 结构(setup、punchline、rating)
- 正常聊天 → final_output 填写 ConversationalResponse 结构(response)
只返回纯 JSON,绝对不要加```json,不要加任何文字、解释、标点!

用户输入:给我讲一个关于唱歌的笑话
"""

prompt2 = """
【严格格式命令,必须遵守】
你必须返回 JSON,且最外层必须有一个字段叫:final_output
根据用户输入选择内容:
- 要讲笑话 → final_output 填写 Joke 结构(setup、punchline、rating)
- 正常聊天 → final_output 填写 ConversationalResponse 结构(response)
只返回纯 JSON,绝对不要加```json,不要加任何文字、解释、标点!

用户输入:你好
"""

# 执行
result = structured_model.invoke(prompt1)
print(result)

print("-" * 50)

result = structured_model.invoke(prompt2)
print(result)

这里glm-5对于我们的Union支持也不是很好,所以我们要在提示词里面写上要的内容

但是对于OpenAI,我们只要写如下即可:

python 复制代码
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Optional, Union

# 定义⼤模型 
model = ChatOpenAI(model="gpt-4o-mini")


class Joke(BaseModel):
    """给⽤⼾讲⼀个笑话。"""
    setup: str = Field(description="这个笑话的开头")
    punchline: str = Field(description="这个笑话的妙语")
    rating: Optional[int] = Field(
        default=None, description="从1到10分,给这个笑话评分"
    )


class ConversationalResponse(BaseModel):
    """以对话的⽅式回应。待⼈友善,乐于助⼈。"""
    response: str = Field(description="对⽤⼾查询的会话响应")


class FinalResponse(BaseModel):
    final_output: Union[Joke, ConversationalResponse]


structured_model = model.with_structured_output(FinalResponse)
result = structured_model.invoke("给我讲⼀个关于唱歌的笑话")
print(result)
result = structured_model.invoke("你好")
print(result)

不需要额外写提示词

2.实用场景

a.场景1:作为信息提取器

python 复制代码
from langchain_openai import ChatOpenAI
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.messages import HumanMessage, SystemMessage

# 定义⼤模型 
model = ChatOpenAI(model="gpt-4o-mini")


class Person(BaseModel):
    """⼀个⼈的信息。"""
    # 注意: 
    # 1. 每个字段都是 Optional "可选的" ------ 允许 LLM 在不知道答案时输出 None。 
    # 2. 每个字段都有⼀个 description "描述" ------ LLM使⽤这个描述。 
    # 有⼀个好的描述可以帮助提⾼提取结果。 
    name: Optional[str] = Field(default=None, description="这个⼈的名字")
    hair_color: Optional[str] = Field(default=None, description="如果知道这个⼈头发的颜⾊")
    skin_color: Optional[str] = Field(default=None, description="如果知道这个⼈的肤⾊")
    height_in_meters: Optional[str] = Field(default=None, description="以⽶为单位的⾼度")
structured_model = model.with_structured_output(schema=Person)
messages = [
    SystemMessage(content="你是⼀个提取信息的专家,只从⽂本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null"),
    HumanMessage(content="史密斯⾝⾼6英尺,⾦发。")
]
result = structured_model.invoke(messages)
print(result)

b.场景2:使用"少样本提示"来增强信息提取能力

后面再进行讲解,类似于如下:

代码后续再写

c.场景3:与工具结合使用

方式1:使用 with_structured_output() 的 tools 参数
python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os
from langchain_tavily import TavilySearch
from pydantic import BaseModel, Field

print("我是通过OpenAI接口调用的智谱大模型")
api_key = os.getenv("ZHIPU_API_KEY")
#1. 定义OpenAI模型
#默认从系统环境读取OPENAI_API_KEY(要提前配置环境变量)
model = ChatOpenAI(
    model="glm-5",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)


tool = TavilySearch(max_results=4)
model_with_tools = model.bind_tools([tool])

messages = [
    HumanMessage("北京今天的天气怎么样?")
]
messages.append(HumanMessage("""
根据以上搜索结果,生成结构化回答。
【严格遵守】
1. 只返回纯 JSON,**绝对不要加 ```json、```、任何文字、任何解释**
2. 只返回结构:{"query":"...", "findings":"..."}
3. 不要多余字符,不要换行多余
"""))

ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)


for tool_call in ai_message.tool_calls:
    tool_message = tool.invoke(tool_call)
    messages.append(tool_message)

class SearchResult(BaseModel):
    """结构化搜索对象"""
    query: str = Field(description="搜索查询")
    findings: str = Field(description="查询结果摘要")

model_with_structured = model_with_tools.with_structured_output(SearchResult)
print(model_with_structured.invoke(messages))

二.聊天模型--流式传输

python 复制代码
model = ChatOpenAI()
model.invoke("讲⼀个1000字的笑话")
python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os

print("我是通过OpenAI接口调用的智谱大模型")
api_key = os.getenv("ZHIPU_API_KEY")
#1. 定义OpenAI模型
#默认从系统环境读取OPENAI_API_KEY(要提前配置环境变量)
model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)

print(model.invoke("写一段关于春天的作文,1000个字左右").content)

这样的话,等待的时间就非常的长了

我们能采用流式传输进行操作

1.stream()同步传输

代码实现流式传输:

python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os

print("我是通过OpenAI接口调用的智谱大模型")
api_key = os.getenv("ZHIPU_API_KEY")
#1. 定义OpenAI模型
#默认从系统环境读取OPENAI_API_KEY(要提前配置环境变量)
model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)

#返回迭代器
for chunk in model.stream("写一段关于春天的作文,1000个字左右"):
    print(chunk.content,end="|")

# print(model.invoke("写一段关于春天的作文,1000个字左右").content)

这样就是流式输出的

python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os

print("我是通过OpenAI接口调用的智谱大模型")
api_key = os.getenv("ZHIPU_API_KEY")
#1. 定义OpenAI模型
#默认从系统环境读取OPENAI_API_KEY(要提前配置环境变量)
model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)

#返回迭代器
chunks = []
for chunk in model.stream("写一段关于春天的作文,1000个字左右"):
    chunks.append(chunk)
    # print(chunk.content,end="|",flush=True)
tmp_chunks = chunks[0] + chunks[1] + chunks[2]
print(tmp_chunks)

# print(model.invoke("写一段关于春天的作文,1000个字左右").content)

2.astream()异步传输

a.异步相关概念

python 复制代码
import time
def boil_water():
 print("开始煮⽔...")
 time.sleep(5) # 模拟阻塞等待5秒 
 print("⽔开了!")
def send_message():
 print("开始发短信...")
 time.sleep(2) # 模拟阻塞等待2秒 
 print("短信发送成功!")
# 主程序 
def main():
 boil_water() # 先花5秒煮⽔,期间什么也不能做 
 send_message() # ⽔开后再花2秒发短信 
main()

协程就是一个线程里面的执行分支

python 复制代码
import asyncio

# 定义协程
async def boil_water_async():
    print("开始煮水...")
    await asyncio.sleep(5)  # 关键! await 表示"等待这个操作完成,但期间让事件循环去做别的事"
    print("水开了!")

async def send_message_async():
    print("开始发短信...")
    await asyncio.sleep(2)  # 同样,等待2秒,但让出控制权
    print("短信发送成功!")

# 主协程,并发运行两个任务
async def main():
    # 并发执行煮水和发短信
    task1 = asyncio.create_task(boil_water_async())
    task2 = asyncio.create_task(send_message_async())
    await task1
    await task2

# 启动事件循环
asyncio.run(main())
python 复制代码
# 主程序(也是⼀个协程) 
async def main():
 # 创建两个任务,并交给事件循环去调度 
 task1 = asyncio.create_task(boil_water_async())
 task2 = asyncio.create_task(send_message_async())
 # 等待两个任务都完成 
 await task1
 await task2
# 它负责创建事件循环,并将第⼀个协程(主程序)放⼊其中运⾏。 
asyncio.run(main())

b.使用

python 复制代码
from langchain_openai import ChatOpenAI
# 定义⼤模型 
model = ChatOpenAI(model="gpt-4o-mini")
# 异步调⽤ 
async def async_stream():
 print("=== 异步调⽤ ===")
 async for chunk in model.astream("讲⼀个50字的笑话"):
 print(chunk.content, end="|", flush=True)
import asyncio
asyncio.run(async_stream())
python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os
import asyncio

api_key = os.getenv("ZHIPU_API_KEY")

model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)

async def async_stream():
    print("===异步调用===")
    async for chunk in  model.astream("写一段关于春天的作文,1000字"):
        print(chunk.content,end="|",flush=True)

asyncio.run(async_stream())

3.使用StrOutputParser解析模型的输出

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 定义⼤模型 
model = ChatOpenAI(model="gpt-4o-mini")
# 定义输出解析器 
parser = StrOutputParser()
# 定义链 
chain = model | parser
for chunk in chain.stream("写⼀段关于爱情的歌词,需要5句话"):
 print(chunk, end="|", flush=True)
python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
import os


api_key = os.getenv("ZHIPU_API_KEY")

#组件1:聊天模型
model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)
#组件2:输出解析器
parser = StrOutputParser()
#定义链
chain = model | parser

for chunk in chain.stream("写一段关于爱情的歌词,5句话左右"):
    # 使用parser,结果就是str
    print(chunk,end="|",flush=True)

4.自定义流式输出解析器

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from typing import Iterator, List

# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 定义输出解析器
parser = StrOutputParser()

# 定义生成器
def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:
    buffer = ""
    for chunk in input:
        buffer += chunk
        while "。" in buffer:
            # 只要缓冲区中包含句号,就找到第一个句号的位置
            stop_index = buffer.index("。")
            # 将句号之前的内容(去除首尾空格)作为一个句子放入列表中并产出
            yield [buffer[:stop_index].strip()]
            # 更新缓冲区,保留句号之后的内容
            buffer = buffer[stop_index + 1 :]
        yield [buffer.strip()]

# 定义链
chain = model | parser | split_into_list

for chunk in chain.stream("写一份关于爱情的歌词,需要5句话,每句话用句号分割"):
    print(chunk, end="|", flush=True)
python 复制代码
from typing import List, Iterator

from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
import os


api_key = os.getenv("ZHIPU_API_KEY")

#组件1:聊天模型
model = ChatOpenAI(
    model="glm-4",
    base_url="https://open.bigmodel.cn/api/paas/v4/",
    api_key=api_key
)
#组件2:输出解析器
parser = StrOutputParser()

#自定义生成器
def split_into_list(input: Iterator[str]) -> Iterator[List[str]]:
    buffer = ""
    for chunk in input:
        buffer += chunk
        #遇到句号,就刷新
        while "。" in buffer:
            #找到。的位置
            stop_index = buffer.index("。")
            #yield用于创造生成器
            yield [buffer[:stop_index].strip()]
            buffer = buffer[stop_index + 1:]
    yield [buffer.strip()]


#定义链
chain = model | parser | split_into_list

for chunk in chain.stream("写一段关于爱情的歌词,5句话左右,每句话用中文,句号隔开"):
    # 使用parser,结果就是str
    print(chunk,end="|",flush=True)

5.深度探索流式传输

a.SSE协议介绍

python 复制代码
Content-Type: text/event-stream;charset=utf-8
Connection: keep-alive

b.LangChain流式传输流程分析

通过源码分析流程
python 复制代码
# ===================== 导入依赖 =====================
from typing import Iterator, List, Optional, Any
from langchain_core.callbacks import CallbackManagerForLLMRun
from langchain_core.outputs import ChatGenerationChunk
from langchain_core.messages import BaseMessage

# ===================== 核心流式方法 =====================
def _stream(
    self,
    messages: List[BaseMessage],        # 传入的对话消息
    stop: Optional[List[str]] = None,    # 停止词
    run_manager: Optional[CallbackManagerForLLMRun] = None,  # 回调管理器
    **kwargs: Any                       # 其他参数
) -> Iterator[ChatGenerationChunk]:     # 返回:流式迭代器(一块一块输出)
    
    # 强制开启流式输出(关键:告诉模型要边生成边返回)
    kwargs["stream"] = True
    
    # 是否开启流式token统计(默认开启)
    stream_usage = self._should_stream_usage(**kwargs)
    
    # 构建请求参数(把消息、停止词等拼成API需要的格式)
    payload = self._get_request_payload(messages, stop=stop, **kwargs)

    try:
        # 如果需要指定返回格式(如JSON),用专用流式接口
        if "response_format" in payload:
            # 请求流式接口
            response_stream = self.root_client.beta.chat.completions.stream(**payload)
            context_manager = response_stream
        else:
            # 普通流式请求
            response = self.client.create(**payload)
            context_manager = response

        # 进入流式响应上下文
        with context_manager as response:
            # 标记是否是第一个数据块(用于处理元信息)
            is_first_chunk = True
            
            # 遍历模型返回的每一块数据(核心:一块一块来)
            for chunk in response:
                # 把模型返回的原始块 → 转换成 LangChain 标准块
                generation_chunk = self._convert_chunk_to_generation_chunk(
                    chunk,
                    self.default_chunk_class,
                    self.base_generation_info if is_first_chunk else {},
                )
                
                # 如果块为空,跳过
                if generation_chunk is None:
                    continue

                # 回调:告诉外部有新的token生成了
                if run_manager is not None:
                    run_manager.on_llm_new_token(
                        generation_chunk.text, chunk=generation_chunk
                    )
                
                # ===================== 核心 =====================
                # 输出这一块数据!!!
                # 外面的 for chunk in chain.stream() 就是接收这里 yield 的内容
                yield generation_chunk

                # 第一个块处理完,标记为False
                is_first_chunk = False

    # 捕获API请求错误
    except Exception as e:
        self._handle_openai_bad_request(e)
        raise e
LangChain请求OpenAI使用什么协议?
python 复制代码
# 导入 openai 库,用于继承其默认 HTTP 客户端类
import openai
# 导入 os 库,用于读取环境变量(代码中隐含使用,需补充导入)
import os
from typing import Optional, Any

# 定义一个同步 HTTP 客户端包装类,继承自 openai 的 DefaultHttpClient
# 作用:在 openai 原生客户端基础上,增加安全的资源回收逻辑
class _SyncHttpxClientWrapper(openai.DefaultHttpClient):
    """Borrowed from openai._base_client"""

    # 析构方法:对象被销毁时自动调用,用于资源清理
    def __del__(self) -> None:
        # 如果客户端已经关闭,直接返回,无需重复操作
        if self.is_closed:
            return

        try:
            # 主动调用 close() 方法,关闭 HTTP 连接,释放资源
            # 避免连接泄漏,提升程序稳定性
            self.close()
        except Exception:  # noqa: S110
            # 捕获所有异常,即使关闭失败也不影响程序运行
            # noqa: S110 是代码检查工具的忽略标记,避免"捕获泛型异常"的警告
            pass


# 定义构建同步 HTTP 客户端的工厂函数
# 作用:统一创建符合配置的 _SyncHttpxClientWrapper 实例
def _build_sync_httpx_client(
    base_url: Optional[str], timeout: Any
) -> _SyncHttpxClientWrapper:
    # 按优先级确定 base_url:
    # 1. 优先使用传入的 base_url 参数
    # 2. 若参数为空,读取环境变量 OPENAI_BASE_URL
    # 3. 若环境变量也为空,使用默认的 OpenAI 官方 API 地址
    return _SyncHttpxClientWrapper(
        base_url=base_url
        or os.environ.get("OPENAI_BASE_URL")
        or "https://api.openai.com/v1",
        timeout=timeout,
    )
LangChain如何支持流式传输?
OpenAI返回的块是什么格式,如何转换成 AIMessageChunk ?
python 复制代码
# 将模型返回的chunk 转换为 LangChain 标准的 ChatGenerationChunk
def _convert_chunk_to_generation_chunk(
    self,
    chunk: dict,
    default_chunk_class: type[BaseMessageChunk],
    base_generation_info: Optional[dict]
) -> Optional[ChatGenerationChunk]:
    # 获取choices字段,兼容普通流式与beta流式两种格式
    choices = (
        chunk.get("choices", [])
        or chunk.get("chunk", {}).get("choices", [])
    )
    
    # 取第一个结果
    choice = choices[0]
    # 如果delta为空,直接返回None
    if choice["delta"] is None:
        return None

    # 把delta转换为消息块
    message_chunk = _convert_delta_to_message_chunk(
        choice["delta"], default_chunk_class
    )

    # 构建生成信息
    generation_info = {**base_generation_info} if base_generation_info else {}

    # 封装并返回标准的 ChatGenerationChunk
    generation_chunk = ChatGenerationChunk(
        message=message_chunk, generation_info=generation_info or None
    )
    return generation_chunk


# 将OpenAI格式的delta 转换为 LangChain 消息块
def _convert_delta_to_message_chunk(
    _dict: Mapping[str, Any], default_class: type[BaseMessageChunk]
) -> BaseMessageChunk:
    id_ = _dict.get("id")
    role = cast(str, _dict.get("role"))
    content = cast(str, _dict.get("content")) or ""
    additional_kwargs: dict = {}

    # 处理旧版 function_call
    if _dict.get("function_call"):
        function_call = dict(_dict["function_call"])
        if "name" in function_call and function_call["name"] is None:
            function_call["name"] = ""
        additional_kwargs["function_call"] = function_call

    # 处理新版 tool_calls
    tool_call_chunks = []
    if raw_tool_calls := _dict.get("tool_calls"):
        additional_kwargs["tool_calls"] = raw_tool_calls
        try:
            tool_call_chunks = [
                tool_call_chunk(
                    name=rtc["function"].get("name"),
                    args=rtc["function"].get("arguments"),
                    id=rtc.get("id"),
                    index=rtc["index"],
                )
                for rtc in raw_tool_calls
            ]
        except KeyError:
            pass

    # 根据角色返回对应消息块
    if role == "user" or default_class == HumanMessageChunk:
        return HumanMessageChunk(content=content, id=id_)
    elif role == "assistant" or default_class == AIMessageChunk:
        return AIMessageChunk(
            content=content,
            additional_kwargs=additional_kwargs,
            id=id_,
            tool_call_chunks=tool_call_chunks,
        )
    elif role in ("system", "developer") or default_class == SystemMessageChunk:
        if role == "developer":
            additional_kwargs = {"__openai_role__": "developer"}
        else:
            additional_kwargs = {}
        return SystemMessageChunk(
            content=content, id=id_, additional_kwargs=additional_kwargs
        )
    elif role == "function" or default_class == FunctionMessageChunk:
        return FunctionMessageChunk(content=content, name=_dict["name"], id=id_)
    elif role == "tool" or default_class == ToolMessageChunk:
        return ToolMessageChunk(
            content=content, tool_call_id=_dict["tool_call_id"], id=id_
        )
    elif role or default_class == ChatMessageChunk:
        return ChatMessageChunk(content=content, role=role, id=id_)
    else:
        return default_class(content=content, id=id_)

三.使用LangSmith跟踪LLM应用

LangSmith链接:https://smith.langchain.com/

他就会自动追踪我们所有的调用的代码

相关推荐
摸鱼仙人~2 小时前
AI检索——基础 RAG vs. 检索 Agent对比
人工智能
witAI2 小时前
**AI仿真人剧制作2025推荐,专业团队与创新技术引领未来**
人工智能·python
Deepoch2 小时前
Deepoc具身模型开发板:无人机集群去中心化协同的VLA中枢
人工智能·无人机·具身模型·deepoc
北京耐用通信2 小时前
耐达讯自动化CC-Link IE转EtherCAT网关:让工业自动化更简单
人工智能·科技·物联网·自动化·信息与通信
热爱生活的猴子2 小时前
训练与推理时 Tokenizer Padding 用法笔记
人工智能·笔记·机器学习
北京耐用通信2 小时前
架桥记:耐达讯自动化CC-Link IE转EtherCAT的工业协议融合实战
人工智能·科技·物联网·网络协议·自动化
♪-Interpretation2 小时前
第五节:Python的流程控制语句
python
黎阳之光2 小时前
数智技术如何赋能空天地一体化,领跑低空经济新赛道
大数据·人工智能·算法·安全·数字孪生
cd_949217212 小时前
商业航天步入“快车道”,星思半导体迎来广阔发展前景
人工智能