Langchain知识

学习资料

模型,提示和输出解析(第1章节)
记忆(第2章节)
链(第3章节)

https://juejin.cn/post/7248599585735114789

基于文档的问答(第4章节)
评估(第5章节)
代理(第6章节)

https://juejin.cn/post/7250669238419439673

langchain官方文档

https://python.langchain.com/docs/modules/model_io/

Langchain Hub网址

https://smith.langchain.com/hub/search?q=hwchase17%2Freact

LangChain模块

LangChain Expression Language (LCEL)

RunnablePassthrough

  • RunnablePassthrough()
    获得当前的输入,通常放在链的开头

  • RunnablePassthrough.assign(aa=一个函数)
    会将当前的输入给函数,并将当前输入与{“aa”:函数的输出}拼接起来

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

runnable = RunnableParallel(
    passed=RunnablePassthrough(),
    extra=RunnablePassthrough.assign(mult=lambda x: x["num"] * 3),
    modified=lambda x: x["num"] + 1,
)

runnable.invoke({"num": 1})

{'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}

RunnableParallel

总的来说,在含有|的链中,用()把链包起来之后就能调用方法了,

  • 如果希望一个输入能同时给好几个函数,则把这些函数写在一个字典里
  • 如果希望前面的输出能作为下一步的输入、且要把前一步的输出和下一步的输出拼起来,就调assign

一般来收最好把含有|的链用()包起来。
在用|构成的链中,直接写的{}相当于被RunnableParallel包裹起来了

  • RunnableParallel(aa=函数fn1, bb=函数fn2)

    同时执行fn1和fn2

  • RunnableParallel({‘aa’:函数fn1, ’bb‘:函数fn2})
    同时执行fn1和fn2

在链中,下面三种等价等价:
{"context": retriever, "question": RunnablePassthrough()}

RunnableParallel({"context": retriever, "question": RunnablePassthrough()})

RunnableParallel(context=retriever, question=RunnablePassthrough())

意味着:
def test(){
	return "ok'
}
prompt | llm | {"answer": StrOutputParser(),  "answer2": test}
等价于
prompt | llm | RunnableParallel({"answer": StrOutputParser(),"answer2": test})
等价于
prompt | llm | RunnableParallel(answer=StrOutputParser(), answer2=test})
说明在|构建的链中,{}里的函数是会收到同样的参数并同时执行的
  • 以下6种写法等价
    总的来说,还是用()把链包裹起来更好,因为这样就允许|换行了
    第二种写法最简洁、好用、易懂
{"questoin":RunnablePassthrough()} | prompt | llm

({"questoin":RunnablePassthrough()} | prompt | llm)  # 用()包起来

RunnableParallel({"questoin":RunnablePassthrough()}) | prompt | llm  ## RunnableParallel的几种等价写法
(RunnableParallel({"questoin":RunnablePassthrough()}) | prompt | llm)

RunnableParallel(questoin=RunnablePassthrough()) | prompt | llm
(RunnableParallel(questoin=RunnablePassthrough()) | prompt | llm)
  • RunnableParallel().assign(aa=函数fn1, bb=函数fn2)
    把RunnableParallel()的输出给函数fn1、fn2,并将输出与{‘aa’:fn1的输出, “bb”:fn2的输出}拼接起来
    注意:在|构建的链中,用()包起来之后就可以东西可以调用方法
下面两种都可以,效果也一样。
({"questoin":RunnablePassthrough()} | prompt | llm).assign()
RunnableParallel({"一个key":({"questoin":RunnablePassthrough()} | prompt | llm)}).assign()

但是({"questoin":RunnablePassthrough()} | prompt | llm)是RunnableSequence
RunnableParallel({"一个key":({"questoin":RunnablePassthrough()} | prompt | llm)})是RunnableParallel
 两者都能调用方法,却不是属于同一个类

({"questoin":RunnablePassthrough()} | prompt | llm).assign() 和 RunnableParallel({"一个key":({"questoin":RunnablePassthrough()} | prompt | llm)}).assign() 都是RunnableSequence
# _inputs = RunnableParallel(
#     test=RunnablePassthrough.assign(
#         question2=lambda x: x["question"] + "ok"
#     )
# )

# {'test': {'question': 'where did he work?', 'question2': 'where did he work?ok'}}
# print(_inputs.invoke({"question": "where did he work?"}))

# _inputs = (
#     RunnablePassthrough.assign(
#         question2=lambda x: x["question"] + "ok"
#     )
# )
#
# print(type(_inputs))

_inputs = (
        {"test": RunnablePassthrough.assign(
            question2=lambda x: x["question"] + "ok"
        )}
)
print(type(_inputs))  # <class 'dict'>
print(type(_inputs['test']))  # <class 'langchain_core.runnables.passthrough.RunnableAssign'>


def demo(w):
    return w
_inputs = (
        {"test": RunnablePassthrough.assign(
            question2=lambda x: x["question"] + "ok"
        )} | RunnableLambda(demo)
)
print(type(_inputs))   # <class 'langchain_core.runnables.base.RunnableSequence'>

# {'test': {'question': 'where did he work?', 'question2': 'where did he work?ok'}}
print(_inputs.invoke({"question": "where did he work?"}))

Model I/O

在这里插入图片描述

Prompt

  • few-shot promt,在prompt中添加多个示例
  • example selector,从多个实例进行挑选
  • message prompt,用于chatmodel的prompt
  • partial prompt,部分填充prompt

Chat Model

  • 自定一个ChatModel

LLM

  • 自定义一个Model

Output Parser

  • 对输出进行格式变换,如转成csv

Retrieval

在这里插入图片描述

Document loaders

用于加载文档

Text Splitting

用于把大文档分割成一块块小的文档

Text embedding models

生成文本嵌入

Vector stores

存储文本嵌入

Retrievers

检索

  • langchain中到底依据什么进行的检索呢?通常都是根据语义相似度来检索的,当然也可以指定使用BM5来检索
  • retriever中支持的有:
    • Contextual Compression:需要一个基本检索器、一个压缩器,会对基本检索器检索到的原始文档再压缩,得到最最相关的
    • Ensemble:有的时候用语义相似度去检索效果好、有的适合用基于BM5的去检索效果好,这个就可以支持指定多个检索器,它返回多个检索器中,检索效果最好的那个检索器的结果
    • Long-Context Reorder:当我们检索到很多很多信息给到大模型时,大模型有个特性就是:它更关心靠前、靠后的信息,这个就是改变信息的顺序,把更重要的信息放在靠前、靠后的位置
    • 还有很多类型,除了内置的还有很多第三方的。

Indexing

索引,为存储好的嵌入绑定索引

Agents

  • Agent Types:Langchain提供了哪些agent
    agent跟chain不同的点在于:chain中所有的要做的事情都是硬编码的,但是agent要做的事情由大模型自己决定。
    注意有些agent只支持调用需要一个参数的tool,例如我们现在在用的react
agent

agent是让大模型自己取决定用什么工具。在agent系统中,llm充当了agent的大脑,此外agent中还有一些模块,以下内容来自这里

  • planning计划
    • 代理将大型任务分解为较小的、可管理的子目标,从而实现对复杂任务的高效处理。
    • 对过去的行为进行自我批评和反思,从错误中吸取教训,并为未来的步骤进行提炼,从而提高最终结果的质量。
  • memory
    • 短期记忆:通过大模型支持的对话历史记录实现
    • 长期记忆:通常由外部知识库支撑。这使得大模型拥有在长时间内保留和调用(无限)信息的能力
  • tools工具
    llm根据目标选择调用外部api来获取大模型中缺乏的信息,例如通过所有工具调用谷歌搜索api获取最新信息
  • action
    llm基于memory、planning、tool给出的下一步应采取的行动
    在这里插入图片描述

Chains

LangChain应用

Pdf助手

fastapi服务器

from fastapi import FastAPI, File, UploadFile, Form
from typing import Annotated
import os

from langchain_helper import *

app = FastAPI()
file_upload_path = "./upload"


@app.get("/")
def read_root():
    return "hello"


@app.post("/upload")  # 上传
def upload_pdf(
        file: Annotated[UploadFile, File()],
        index_name: Annotated[str, Form()]
):
    file_upload_target_path = os.path.join(file_upload_path, file.filename)
    with open(file_upload_target_path, "wb") as f:
        contents = file.file.read()
        f.write(contents)

    load_pdf_and_save_to_index(file_upload_target_path, index_name)

    return {"filename": file.filename, "index_name": index_name}


from pydantic import BaseModel


class Query(BaseModel):
    index_name: str
    query: str


@app.post("query")  # 查询
def query_index(request: Query):
    index_name = request.index_name
    query = request.query
    ans = query_index_lc(index_name, query)
    return {"ans": ans}

启动服务器

uvicorn main:app --reload

langchain_helper.py如下:

主要要注意的是langchain中的类、方法都默认用的是openai的,所以要手动指明大模型是什么

另外:query_with_sources这个方法会自动到hugging_face2上面去下载gpt2的tokenizer,要挂梯子。

from langchain.document_loaders import PyPDFLoader
from langchain.indexes.vectorstore import VectorstoreIndexCreator
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.llms import Tongyi
from langchain.vectorstores import Chroma
from langchain.indexes.vectorstore import VectorStoreIndexWrapper

import os

os.environ["DASHSCOPE_API_KEY"] = "sk-dea0261124ec4df6999f6b2ccd26c9ea"

local_persist_path = r"./vector_store"
embeddings = DashScopeEmbeddings(model="text-embedding-v1")  # embedding模型
llm = Tongyi(model_name="qwen-max")  # Qwen


def get_index_path(index_name):
    return os.path.join(local_persist_path, index_name)


def load_pdf_and_save_to_index(file_path, index_name):
    loader = PyPDFLoader(file_path)  # 加载文档
    # VectorstoreIndexCreator能完成文档分割、向量化、存储
    index = VectorstoreIndexCreator(embedding=embeddings,
                                    vectorstore_kwargs={"persist_directory": get_index_path(index_name)}).from_loaders(
        [loader])
    index.vectorstore.persist()  # 保存到本地


def load_index(index_name):
    # 从磁盘中读取index
    index_path = get_index_path(index_name)
    vector_db = Chroma(
        persist_directory=index_path,
        embedding_function=embeddings
    )

    return VectorStoreIndexWrapper(vectorstore=vector_db)


def query_index_lc(index, query):
    ans = index.query_with_sources(query, llm=llm, chain_type='map_reduce')
    return ans["answer"]


# index_name = 'first'
# index = load_index(index_name, embeddings)
# print(index.query_with_sources("entity", llm=llm, chain_type='map_reduce'))

agent初级使用

用agent做搜索

serpapi需要api-key

from langchain_community.chat_models.tongyi import ChatTongyi
from langchain import hub
from langchain.agents import AgentExecutor, create_react_agent, load_tools

import os
# 使用灵积云的key调用qwen
os.environ["DASHSCOPE_API_KEY"] = "sk-dea0261124ec4df6999f6b2ccd26c9ea"

chatLLM = ChatTongyi(
    streaming=True,
    model_name='qwen-max'
)

tools = load_tools(["serpapi", "llm-math"], llm=chatLLM)

# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/react")

# Construct the ReAct agent
agent = create_react_agent(chatLLM, tools, prompt)

# Create an agent executor by passing in the agent and tools
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

ans1 = agent_executor.invoke({"input": "谁是高启强的扮演者"})
print(ans1)

ans2 = agent_executor.invoke({"input": "高启强的扮演者10年后多少岁?"})
print(ans2)

新闻变相声(gradio展示)

  • 先写好输入url、输出相声剧本的代码
from langchain_community.llms import Tongyi
import os

os.environ["DASHSCOPE_API_KEY"] = "sk-dea0261124ec4df6999f6b2ccd26c9ea"
llm = Tongyi(model_name="qwen-max")  # Qwen

import bs4
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains.summarize import load_summarize_chain
from langchain.prompts import PromptTemplate
from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser


class Line(BaseModel):
    character: str = Field(description="说这句话的角色名字")
    content: str = Field(description="台词的具体内容,不包括角色的名字")


class XiangSheng(BaseModel):
    script: list[Line] = Field(description="一段相声的台词剧本")


def url2doc(url):
    loader = WebBaseLoader(
        web_paths=(url,),
        bs_kwargs=dict(
            parse_only=bs4.SoupStrainer(
                class_=("post-content", "post-title", "post-header")  # 只要类名是指定这些的
            )
        ),
    )

    loader.load()
    text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n"], chunk_size=100, chunk_overlap=20,
                                                   length_function=len)

    data = loader.load_and_split(text_splitter=text_splitter)
    return data[10:20]


def url2script(url, requirement):
    map_prompt_template = """总结下列内容:


    "{text}"


    总结的内容是:"""
    MAP_PROMPT = PromptTemplate(template=map_prompt_template, input_variables=["text"])

    combine_prompt_template = """总结下列内容:


    "{text}"


    总结的内容是:"""
    COMBINE_PROMPT = PromptTemplate(template=combine_prompt_template, input_variables=["text"])

    # 使用map_reduce:
    # sum_chain = load_summarize_chain(llm, chain_type='map_reduce', map_prompt=MAP_PROMPT, combine_prompt=COMBINE_PROMPT)

    # 使用stuff
    sum_chain = load_summarize_chain(llm)

    parser = PydanticOutputParser(pydantic_object=XiangSheng)

    template = """
    我将给你一段文字,请你帮我把这段文字改写成郭德纲和于谦的对口相声剧本。
    文字:{content}
    要求:{requirement}
    {output_instruction}
    """
    prompt = PromptTemplate.from_template(
        template=template,
        input_bariables=["content", "requirement"],
        partial_variables={"output_instruction": parser.get_format_instructions()}
    )
    chain = (prompt | llm | parser)
    script_ = chain.invoke({"content": sum_chain.invoke(url2doc(url)), "requirement": requirement})
    return script_


# url = "https://lilianweng.github.io/posts/2023-06-23-agent/"
# script = url2script(url, "幽默风趣")
# print(script)
  • 写gradio页面的代码
import gradio as gr
from langchain_helper import *

with gr.Blocks() as demo:
    url = gr.Textbox()
    chatbot = gr.Chatbot()
    submit_btn = gr.Button('生成相声')


    def generate_conversation(url):
        xiangsheng: XiangSheng = url2script(url, "幽默风趣")
        chat_history = []

        def parse_line(line: Line):
            """
            parse Lineto String
            :param line:
            :return: str
            """
            if line is None:
                return ''
            return f"{line.character}:{line.content}"

        for i in range(0, len(xiangsheng.script), 2):
            line1 = xiangsheng.script[i]
            line2 = xiangsheng.script[i + 1] if (i + 1) < len(xiangsheng.script) else None
            chat_history.append((parse_line(line1), parse_line(line2)))
        return chat_history


    submit_btn.click(fn=generate_conversation, inputs=url, outputs=chatbot)

if __name__ == "__main__":
    demo.launch()
  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值