流式传输(stream runnabels)
- 流式传输是什么?
- 流式传输是一种将文本或数据分段传输的技术,通常用于处理大量数据或需要实时显示结果的场景。 - 流式传输有什么用?
- 流式传输可以提高用户体验,因为它允许用户在结果生成的同时看到部分结果,而不是等到整个结果生成完毕。 - 流式传输的实现方式
- 方式一:使用sync
和stream
方法:流式处理默认实现,传输最终输出
- 方式二:使用async
和astream
方式:从链流式传输中间步骤和最终结果
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
True
# 导入mistral模型
from langchain_mistralai import ChatMistralAI
chat_model=ChatMistralAI(model="mistral-large-latest")
chat_model.invoke("你好")
AIMessage(content=‘你好!有什么我可以帮忙的吗?’, additional_kwargs={}, response_metadata={‘token_usage’: {‘prompt_tokens’: 6, ‘total_tokens’: 23, ‘completion_tokens’: 17}, ‘model’: ‘mistral-large-latest’, ‘finish_reason’: ‘stop’}, id=‘run-cf712419-fc7e-453b-9b32-e602f8a81658-0’, usage_metadata={‘input_tokens’: 6, ‘output_tokens’: 17, ‘total_tokens’: 23})
chunks=[]
for chunk in chat_model.stream("简要回答,流式传输是什么"): #.stream()使用流式输出
chunks.append(chunk)
print(chunk.content,end="|",flush=True) #flash=True,刷新缓存,立即显示
print()
print(chunks[0])
||流|式|传|输|是|一|种|数据|传|输|方|式|,|数据|在|传|输|过|程|中|逐|段|发|送|,|而|不|是|一|次|性|传|输|整|个|文|件|。|这|种|方|式|常|用|于|音|频|、|视|频|等|媒|体|内|容|,|因|为|它|允|许|用|户|在|下|载|过|程|中|实|时|播|放|内|容|,|而|不|需|要|等|待|整|个|文|件|下|载|完|成|。|流|式|传|输|可|以|提|高|用|户|体|验|,|减|少|等|待|时|间|,|并|且|有|效|利|用|网|络|带|宽|。||
content=‘’ additional_kwargs={} response_metadata={} id=‘run-32fe98e7-4a0e-42cf-867c-724886cc85e0’
# 在异步环境中使用流式输出
chunks_async=[]
async for chunk in chat_model.astream("简要回答,流式传输是什么"): #async for:异步迭代语法;astream是stream的异步版本
chunks_async.append(chunk)
print(chunk.content,end="|",flush=True)
print()
print(chunks_async[0])
||流式传输|是|一|种数据|传输方|式|,数据|在|传|输过|程中|被|分|割|成小|块|,|逐|块|发|送和|接|收,|而|不需|要等|待|整个|数据|文|件完|全|下|载或|上|传。|常|见|的|应|用包|括在|线|视|频播|放、|音|频|流|和|实|时数据|传|输。|流|式传|输允|许数据在|传|输过|程中即|时被|处|理和|展|示,|提|高|了用|户体|验。||
content=‘’ additional_kwargs={} response_metadata={} id=‘run-db2cb2af-75ba-40a6-ab1c-6335c4db9f51’
# 消息块之间可以相加
chunks[0]+chunks[1]+chunks[2]+chunks[3]+chunks[4]
AIMessageChunk(content=‘流式传’, additional_kwargs={}, response_metadata={}, id=‘run-32fe98e7-4a0e-42cf-867c-724886cc85e0’)
结合提示词模板和链的使用
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt=ChatPromptTemplate.from_template("给我讲一个关于{topic}的笑话")
parser=StrOutputParser() #StrOutputParser:将模型输出转换为字符串的解析器
chain=prompt|chat_model|parser #|:将多个组件连接起来,形成一个链
async for chunk in chain.astream({"topic":"猴子"}):
print(chunk,end="|",flush=True) #由于StrOutputParser(),所以不使用chunk.content
||当|然|可|以!|这|里|有|一个|关|于|猴|子的|笑话:|
有|一天|,一|只|猴子|去|银|行|兑换|香|蕉。|银|行职|员看|到|猴|子拿|着|一|大|袋|香|蕉,|觉|得很|奇|怪,|就|问|猴|子:“|你|为|什么|要|兑换|这么|多香|蕉?|”
|猴子回|答道:“因|为我|听|说香|蕉|在|这|里|很|值|钱,|我|想|用|它|们|换|取一|些现|金。|”
|银行职|员笑|了|笑|,|说|:“|香|蕉在|这|里可|不|值|钱|,|但|是你|的|幽|默感|真|是|无|价|之|宝!|”
|
|猴子|听|了|,|挠|了|挠头|,说|:|“原|来如|此,|看|来我|还|是|去|动|物|园找|工|作好|了,|那|里的|香|蕉可|能更|受|欢迎|。”
|
希|望这|个笑|话能|给你带|来一|些|欢|乐|!||
流式处理
# 使用解析器将输出转换为Json格式
from langchain_core.output_parsers import JsonOutputParser
chain=(chat_model|JsonOutputParser()) #JsonOutputParser:将模型输出转换为JSON格式
async for text in chain.astream(
"以 JSON 格式输出国家/地区 France、Spain 和 Japan 及其人口的列表。"
"使用外部键为 “countries” 的字典,其中包含国家/地区列表。"
"每个国家/地区都应具有键 'name' 和 'population'"
):
print(text,flush=True)
{}
{‘countries’: []}
{‘countries’: [{}]}
{‘countries’: [{‘name’: ‘’}]}
{‘countries’: [{‘name’: ‘Fr’}]}
{‘countries’: [{‘name’: ‘France’}]}
{‘countries’: [{‘name’: ‘France’, ‘population’: 6}]}
…
{‘countries’: [{‘name’: ‘France’, ‘population’: 67081000}, {‘name’: ‘Spain’, ‘population’: 47351567}, {‘name’: ‘Japan’, ‘population’: 1258100}]}
{‘countries’: [{‘name’: ‘France’, ‘population’: 67081000}, {‘name’: ‘Spain’, ‘population’: 47351567}, {‘name’: ‘Japan’, ‘population’: 12581000}]}
{‘countries’: [{‘name’: ‘France’, ‘population’: 67081000}, {‘name’: ‘Spain’, ‘population’: 47351567}, {‘name’: ‘Japan’, ‘population’: 125810000}]}
# 在对解析器处理后的json格式数据利用一个提取函数,获取国家名称
from langchain_core.output_parsers import (
JsonOutputParser,
)
# 提取国家名称的函数
def _extract_country_name(inputs):
if not isinstance(inputs,dict):
return ""
if "countries" not in inputs:
return ""
countries=inputs["countries"] #只要countries键对应的值
if not isinstance(countries,list):
return ""
country_names=[
country.get("name") for country in countries if isinstance(country,dict) #遍历countries列表,如果country是字典,则获取name键对应的值
]
return country_names
chain=chat_model|JsonOutputParser()|_extract_country_name
async for text in chain.astream(
"以 JSON 格式输出国家/地区 France、Spain 和 Japan 及其人口的列表。"
"使用外部键为 “countries” 的字典,其中包含国家/地区列表。"
"每个国家/地区都应具有键 'name' 和 'population'"
):
print(text,flush=True)
[‘France’, ‘Spain’, ‘Japan’]
# 使用异步流式处理(收到一个处理一个,实时性更高)
async def _extract_country_names_streaming(input_stream):
country_names_so_far=set()
async for input in input_stream: #不是一次处理所有数据,然是收到一个处理一个
if not isinstance(input,dict):
continue
if "countries" not in input:
continue
countries=input["countries"]
if not isinstance(countries,list):
continue
for country in countries:
name=country.get("name")
if not name:
continue
if name not in country_names_so_far:
country_names_so_far.add(name)
yield name
chain=chat_model|JsonOutputParser()|_extract_country_names_streaming
async for text in chain.astream(
"以 JSON 格式输出国家/地区 France、Spain 和 Japan 及其人口的列表。"
"使用外部键为 “countries” 的字典,其中包含国家/地区列表。"
"每个国家/地区都应具有键 'name' 和 'population'"
):
print(text,end="|",flush=True)
Fr|France|Spain|Japan|
处理非流式组件
- error:没有openai的key使用不了OpenAIEmbeddings
- 没有检索文档 - 不懂的概念太多以后再学
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
template="""请根据以下内容回答问题:
{context}
提问:{question}
"""
prompt=ChatPromptTemplate.from_template(template)
# 创建一个向量存储
vectorstore=FAISS.from_texts(
["光头强在狗熊岭工作","光头强喜欢砍树"],
embedding=OpenAIEmbeddings()
)
# 创建一个检索器
retriever=vectorstore.as_retriever()
# 使用流式处进行检索
chunks=[chunk for chunk in retriever.stream("光头强喜欢砍树吗?")]
chunks
#创建检索链
retrieval_chain = (
{
"context": retriever.with_config(run_name="Docs"), #设置上下文,使用检索器获取相关文档
"question": RunnablePassthrough(), #直接传递问题
}
| prompt #使用提示词模板
| chat_model #使用模型
| StrOutputParser() #使用字符串解析器
)
for chunk in retrieval_chain.stream({"question":"光头强喜欢砍树吗?"}):
print(chunk,end="|",flush=True)