Day 12:LangChain 流式输出——一行 for 循环的事
回顾 Day 5 的痛
原生 SDK 的流式输出要:判delta是否为 None、判delta.content是否为 None、处理只在第一个 chunk 出现的role、自己拼full_reply……
LangChain 把这些脏活都封装了。
完整代码
importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAI load_dotenv()llm=ChatOpenAI(model=os.getenv("YUMC_MODEL_NAME"),api_key=os.getenv("YUMC_MODEL_API_KEY"),base_url=os.getenv("YUMC_MODEL_BASE_URL"),streaming=True,# 声明用流式模式)print("----流式输出测试")forchunkinllm.stream("请给我讲一个关于程序员的笑话,要简短一点。"):print(chunk.content,end="",flush=True)print("\n----结束----")没了。真的没了。
逐段拆解
①llm.stream(...)
LLM 对象自带.stream()方法,直接传字符串(或消息列表)就行。它是一个生成器,for循环每轮拿到一个chunk(一个AIMessageChunk对象)。
②chunk.content
对比原生 SDK 的chunk.choices[0].delta.content——LangChain 已经帮你拆好包了,而且content为空时它就是空字符串"",不用判 None(打印空字符串无副作用)。
③streaming=True
在构造参数里声明支持流式。注意:对 OpenAI 系模型来说,调用.stream()时即使不写这个参数通常也能正常流式;但显式写上是个好习惯,部分集成场景依赖它。
想要完整回复?也是三行
full_reply=""forchunkinllm.stream("讲个笑话"):full_reply+=chunk.contentprint(chunk.content,end="",flush=True)# full_reply 现在就是完整文本,可以存历史、写库LangChain 消息 vs 原生字典
顺便熟悉一下新面孔:原生 SDK 里消息是字典{"role": "user", "content": "..."},LangChain 里是对象:HumanMessage、AIMessage、SystemMessage(明天正式用)。对象的好处是有类型提示、有方法,IDE 能自动补全。
今日踩坑点
- 对
chain用了.stream()传错结构:如果流式的是链,输入格式要和.invoke()一致(字典),不是裸字符串。 flush=True还是不能省:这是 Python 的 print 缓冲问题,跟用不用框架无关。- 以为 chunk 是字符串:它是消息块对象,要取
.content;直接 print 会看到对象表示。
今日小练习
- 必做:把 Day 10 的
chain = prompt | llm改成流式调用chain.stream({"user_input": "..."}),验证链也能流式。 - 进阶:流式输出时同步统计已输出的字符数,结束时打印"共输出 N 字符"。
明日预告
Day 13:回到 Day 6 的老话题——记忆。这次用 LangChain 的 Message 对象实现,顺便封装一个像样的chat()函数。
附录:完整练习代码
langchainTestStreaming.py
importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAI load_dotenv()llm=ChatOpenAI(model=os.getenv("YUMC_MODEL_NAME"),api_key=os.getenv("YUMC_MODEL_API_KEY"),base_url=os.getenv("YUMC_MODEL_BASE_URL"),streaming=True)print("----流式输出测试")forchunkinllm.stream("请给我讲一个关于程序员的笑话,要简短一点。"):print(chunk.content,end="",flush=True)print("\n----结束----")