开启左侧

【学习笔记】Langchain基础

[复制链接]
zoopk0079 发表于 5 天前 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
作者:CSDN博客
文章目录

    1 Langchain基础回顾、LECL、Tool Use、RAG 以及 LangSmith2 conversational agents,ReAct、agent_scratchpad 历史过程信息维护3 LangGraph 基本概念(AgentState、StateGraph,nodes,edges)
      核心概念案例一:Agent Executor
        toolsAgenttoolsgraphs
      案例二:Chat Executor
        agent statenodesGraph

    4 LangGraph 构建复杂 RAG workflow(Self-corrective)
      (Self-Corrective) RAG on LangGraphmodel, retriever & toolsGraph
        statenodesedges & graph

    5 多轮对话 qlora SFT(Multi-turn Conversation)05 LangChain、LangGraph 结构化输出(Structured output),gpt-4o-2024-08-066 基于LangGraph 实现 Reflexion Agent(generator vs. critic)7 [OpenAI API] logprobs 与 top_logprobs 模型输出的置信度(confidence)与困惑度(PPL)
      1 使用`logprobs`来衡量分类任务的置信度2 检索置信度评分以减少幻觉3 Highlighter and bytes parameter4 Autocomplete5 困惑度计算(perplexity, PPL)


https://www.bilibili.com/video/BV1ax4y1s7Sk

1 Langchain基础回顾、LECL、Tool Use、RAG 以及 LangSmith

参考资料:
    https://github.com/langchain-ai/langchain/tree/master/cookbook
  • https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel
      https://python.langchain.com/v0.1/docs/expression_language/why/
    https://ai.plainenglish.io/understanding-large-language-model-based-agents-27bee5c82cec
  1. import os
  2. from dotenv import load_dotenv
  3. # LANGCHAIN_TRACING_V2=true # 免费# LANGCHAIN_API_KEY= # 免费# OPENAI_API_KEY= # 付费,但可以用别的
  4. load_dotenv()
复制代码

  • 所谓的 agent 开发,LLMs workflows,GenAI 时代的软件工程
      丰富的生态,workflows 的复杂,手撸的效率非常低,而且不好维护,Input -> Processing -> Output

  • 与 AutoGen 等相比,更多地面向开发者,面向软件工程
      LangGraph:multi-agents workflowsLangSmith 也在更多地弥补中间过程显示的不足

  • 推荐 《大模型应用开发 动手做AI Agent》(https://www.bilibili.com/opus/935785456083140628)
      面向开发者,第一本系统而全面,可以做一个很好的入门

LCEL (LangChain Expression Language)
    LangChain 重写了 |(__or__)运算符,Chain 之所在,即通过|将所有东西串成一个链RunnablePassthrough: RunnablePassthrough 允许你将输入数据直接传递而不做任何更改(identity),通常与 RunnableParallel 一起使用,将数据传递到新的键中。LangChain 的应用 RunnablePassthrough 作为一个占位符,可以在需要时填充数据,比如在公司名称尚未确定时先留空,后续再填入。
  • 所谓的最佳实践
      python:遍历,也可以用 list comprehension对于 matlab:也可以遍历,也可以整理成 matrix,直接矩阵矢量乘法;

  1. # 自带的|运算符,是二进制位运算# (2 | 3) > 32|3>2# True2|3# 3
复制代码
  1. from langchain_core.runnables import(
  2.     RunnablePassthrough,
  3.     RunnableLambda,
  4.     RunnableParallel
  5. )
  6. os.environ["LANGCHAIN_PROJECT"]='lcel_test'# 设置一个新的projectfrom langchain_openai import ChatOpenAI
  7. from langchain_core.prompts import ChatPromptTemplate
  8. from langchain_core.output_parsers import StrOutputParser
  9. prompt = ChatPromptTemplate.from_template("Tell me a short joke about {topic}")
  10. output_parser = StrOutputParser()
  11. llm = ChatOpenAI(model="gpt-3.5-turbo")# lcel
  12. chain =({"topic": RunnablePassthrough()}| prompt
  13.     | llm
  14.     | output_parser
  15. )
  16. chain.invoke("ice cream")# 'Why did the ice cream truck break down?\n\nBecause it had too many "scoops"!'
复制代码
这里prompt的输入就是{"topic": RunnablePassthrough()},这样上一步的输出是下一步的输入,如此成chain
比如直接写成常见的串行语法:
  1. prompt.invoke({'topic':'ice cream'})# ChatPromptValue(messages=[HumanMessage(content='Tell me a short joke about ice cream')])
  2. llm.invoke(prompt.invoke({'topic':'ice cream'}))# AIMessage(content='Why did the ice cream truck break down?\n\nIt had too many "scoops" of ice cream!', response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 15, 'total_tokens': 37}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-fb46f4ce-665b-45f7-9fd0-9dd559465fcc-0', usage_metadata={'input_tokens': 15, 'output_tokens': 22, 'total_tokens': 37})
  3. output_parser.invoke(llm.invoke(prompt.invoke({'topic':'ice cream'})))# 'Why did the ice cream truck break down? It had too many "scoops" on board!'
复制代码
runnables
    RunnablePassthrough(): identity
  1. # ex1
  2. chain = RunnablePassthrough()| RunnablePassthrough ()| RunnablePassthrough ()
  3. chain.invoke("hello")# 'hello'# ex2
  4. chain = RunnablePassthrough()| RunnableLambda(lambda x: x.upper())
  5. chain.invoke("hello")# 'HELLO'# ex3
  6. chain = RunnablePassthrough()| RunnableLambda(lambda x: x.upper())| RunnablePassthrough()
  7. chain.invoke("hello")# 'HELLO'
复制代码

2 conversational agents,ReAct、agent_scratchpad 历史过程信息维护

一些概念:

  • 过程信息(所谓的对话式 agent,conversational agents,就是带记忆)
      stateless LLMs => with memory (maintaining previous interactions)
    • intermediate_steps: list of actions/tools (input, output)
        agent_scratchpad(草稿本):list of tool messages这个具体而言就是一些定义好的函数

    • 第一次 query-resp 之后,才会有这些过程信息
        第一次:intermediate_steps: [], agent_scratchpad: ""


  • chat history
      messages (openai 的 api)system, user, assistant, user assistant …
    langsmith: 监控所有的 messages (input, output)
前一种是隐式的,后者是显式的。
ReAct(Reasoning & Acting)
    https://arxiv.org/abs/2210.03629
  • ReAct(经典、且general、应用广泛的 prompt)

    • User query => (Thought -> Action (Action Input) -> Observation(Action Ouptut)) * N
        Action input => output, function decision & calling
      multi-step/hops reasoning/interacting跟 function calling(LangChain 中的 tools)天然地适配;

  1. from langchain import hub
  2. prompt = hub.pull("hwchase17/react")type(prompt), prompt
  3. """
  4. (langchain_core.prompts.prompt.PromptTemplate,
  5. PromptTemplate(input_variables=['agent_scratchpad', 'input', 'tool_names', 'tools'], metadata={'lc_hub_owner': 'hwchase17', 'lc_hub_repo': 'react', 'lc_hub_commit_hash': 'd15fe3c426f1c4b3f37c9198853e4a86e20c425ca7f4752ec0c9b0e97ca7ea4d'}, template='Answer the following questions as best you can. You have access to the following tools:\n\n{tools}\n\nUse the following format:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Observation can repeat N times)\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n\nBegin!\n\nQuestion: {input}\nThought:{agent_scratchpad}'))
  6. """
复制代码
  1. # 一个完整的 prompt 模板,历史对话/函数调用(输入输出)信息不断地追加在这一个 prompt 中,而不是维护成 messages listprint(prompt.template)"""
  2. Answer the following questions as best you can. You have access to the following tools:
  3. {tools}
  4. Use the following format:
  5. Question: the input question you must answer
  6. Thought: you should always think about what to do
  7. Action: the action to take, should be one of [{tool_names}]
  8. Action Input: the input to the action
  9. Observation: the result of the action
  10. ... (this Thought/Action/Action Input/Observation can repeat N times)
  11. Thought: I now know the final answer
  12. Final Answer: the final answer to the original input question
  13. Begin!
  14. Question: {input}
  15. Thought:{agent_scratchpad}
  16. """
复制代码
    Thought: you should always think about what to doAction: the action to take, should be one of [{tool_names}]Action Input: the input to the actionObservation: the result of the action… (this Thought/Action/Action Input/Observation can repeat N times)
LangChain Projects
    AgentExecutor:agent that is using tools
  1. next_action = agent.get_action(...)while next_action != AgentFinish:
  2.   observation = run(next_action)
  3.   next_action = agent.get_action(..., next_action, observation)return next_action
复制代码
  1. os.environ["LANGCHAIN_PROJECT"]='conversational_agents'from langchain_openai import ChatOpenAI
  2. from langchain.tools import tool
  3. from langchain.agents import AgentExecutor, create_react_agent
  4. @tooldefget_employee_id(name):"""
  5.   To get employee id, it takes employee name as arguments
  6.   name(str): Name of the employee
  7.   """
  8.   fake_employees ={"Alice":"E001","Bob":"E002","Charlie":"E003","Diana":"E004","Evan":"E005","Fiona":"E006","George":"E007","Hannah":"E008","Ian":"E009","Jasmine":"E010"}return fake_employees.get(name,"Employee not found")# Custom tool for the Agent # ValueError: Function must have a docstring if description not provided.@tooldefget_employee_salary(employee_id):"""
  9.   To get the salary of an employee, it takes employee_id as input and return salary
  10.   """
  11.   employee_salaries ={"E001":56000,"E002":47000,"E003":52000,"E004":61000,"E005":45000,"E006":58000,"E007":49000,"E008":53000,"E009":50000,"E010":55000}return employee_salaries.get(employee_id,"Employee not found")
  12. prompt = hub.pull("hwchase17/react")
  13. llm = ChatOpenAI(model='gpt-4o')
  14. tools =[get_employee_salary, get_employee_id]
  15. agent = create_react_agent(llm, tools, prompt)
  16. agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
  17. agent_executor.invoke({"input":"What is the Salary of Evan?"})
复制代码
输出结果:
  1. > Entering new AgentExecutor chain...
  2. To determine Evan's salary, I first need to obtain his employee ID.
  3. Action: get_employee_id
  4. Action Input:"Evan"E005Now that I have Evan's employee ID, I can retrieve his salary using this ID.
  5. Action: get_employee_salary
  6. Action Input: E00545000I now know the final answer.
  7. Final Answer: The salary of Evan is45000.> Finished chain.{'input':'What is the Salary of Evan?','output':'The salary of Evan is 45000.'}
复制代码
first query-resp
LangSmith: https://smith.langchain.com/
LangSmith是评估大模型能力好坏的评估工具,能够量化评估基于大模型的系统的效果。LangSmith通过记录langchain构建的大模型应用的中间过程,从而能够更好的调整提示词等中间过程做优化。
使用的Prompt:
  1. Answer the following questions as best you can. You have access to the following tools:
  2.     get_employee_salary(employee_id)- To get the salary of an employee, it takes employee_id asinputandreturn salary
  3.     get_employee_id(name)- To get employee id, it takes employee name as arguments
  4.     name(str): Name of the employee
  5.     Use the following format:
  6.     Question: the input question you must answer
  7.     Thought: you should always think about what to do
  8.     Action: the action to take, should be one of [get_employee_salary, get_employee_id]
  9.     Action Input: the input to the action
  10.     Observation: the result of the action
  11.     ...(this Thought/Action/Action Input/Observation can repeat N times)
  12.     Thought: I now know the final answer
  13.     Final Answer: the final answer to the original input question
  14.     Begin!
  15.     Question: What is the Salary of Evan?
  16.     Thought:
复制代码
  1. To determine Evan's salary, I first need to obtain his employee ID.
  2. Action: get_employee_id
  3. Action Input:"Evan"
复制代码

  • get_employee_id
      input: Evanoutput: E005

second query-resp
  1. Answer the following questions as best you can. You have access to the following tools:
  2.     get_employee_salary(employee_id)- To get the salary of an employee, it takes employee_id asinputandreturn salary
  3.     get_employee_id(name)- To get employee id, it takes employee name as arguments
  4.     name(str): Name of the employee
  5.     Use the following format:
  6.     Question: the input question you must answer
  7.     Thought: you should always think about what to do
  8.     Action: the action to take, should be one of [get_employee_salary, get_employee_id]
  9.     Action Input: the input to the action
  10.     Observation: the result of the action
  11.     ...(this Thought/Action/Action Input/Observation can repeat N times)
  12.     Thought: I now know the final answer
  13.     Final Answer: the final answer to the original input question
  14.     Begin!
  15.     Question: What is the Salary of Evan?
  16.     Thought:To determine Evan's salary, I first need to obtain his employee ID.
  17.     Action: get_employee_id
  18.     Action Input:"Evan"
  19.     Observation: E005
  20.     Thought:
复制代码
  1. Now that I have Evan's employee ID, I can retrieve his salary using this ID.
  2. Action: get_employee_salary
  3. Action Input: E005
复制代码
get_employee_salary
    input: E005output: 45000
finally
  1. Answer the following questions as best you can. You have access to the following tools:
  2. get_employee_salary(employee_id)- To get the salary of an employee, it takes employee_id asinputandreturn salary
  3. get_employee_id(name)- To get employee id, it takes employee name as arguments
  4. name(str): Name of the employee
  5. Use the following format:
  6. Question: the input question you must answer
  7. Thought: you should always think about what to do
  8. Action: the action to take, should be one of [get_employee_salary, get_employee_id]
  9. Action Input: the input to the action
  10. Observation: the result of the action
  11. ...(this Thought/Action/Action Input/Observation can repeat N times)
  12. Thought: I now know the final answer
  13. Final Answer: the final answer to the original input question
  14. Begin!
  15. Question: What is the Salary of Evan?
  16. Thought:To determine Evan's salary, I first need to obtain his employee ID.
  17. Action: get_employee_id
  18. Action Input:"Evan"
  19. Observation: E005
  20. Thought: Now that I have Evan's employee ID, I can retrieve his salary using this ID.
  21. Action: get_employee_salary
  22. Action Input: E005
  23. Observation:45000
  24. Thought:
复制代码
  1. I now know the final answer.
  2. Final Answer: The salary of Evan is45000.
复制代码

显式地操作agent_scratchpad与intermediate_steps
https://python.langchain.com/v0.1/docs/modules/agents/how_to/custom_agent/
    format_to_openai_tool_messages
      https://api.python.langchain.com/en/latest/_modules/langchain/agents/format_scratchpad/openai_tools.html#format_to_openai_tool_messages

  1. os.environ["LANGCHAIN_PROJECT"]='memory_agents'@tooldefget_word_length(word:str)->int:"""Returns the length of a word."""returnlen(word)
  2. get_word_length.invoke("abc")# 3
  3. tools =[get_word_length]from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
  4. prompt = ChatPromptTemplate.from_messages([("system","You are very powerful assistant, but don't know current events",),("user","{input}"),
  5.         MessagesPlaceholder(variable_name="agent_scratchpad"),])
  6. llm = ChatOpenAI(model="gpt-4o", temperature=0)
  7. llm_with_tools = llm.bind_tools(tools)from langchain.agents.format_scratchpad.openai_tools import(
  8.     format_to_openai_tool_messages,)from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser
  9. # runnable Sequence,根据需要循环执行这 4 步;
  10. agent =({"input":lambda x: x["input"],"agent_scratchpad":lambda x: format_to_openai_tool_messages(
  11.             x["intermediate_steps"]),}| prompt
  12.     | llm_with_tools
  13.     | OpenAIToolsAgentOutputParser())
  14. agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
  15. process_list =list(agent_executor.stream({"input":"How many letters in the word eudca"}))"""
  16. > Entering new AgentExecutor chain...
  17. Invoking: `get_word_length` with `{'word': 'eudca'}`
  18. 5The word "eudca" has 5 letters.
  19. > Finished chain.
  20. """
复制代码
上面的process_list变量形如:
  1. [{'actions':[ToolAgentAction(tool='get_word_length', tool_input={'word':'eudca'}, log="\nInvoking: `get_word_length` with `{'word': 'eudca'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls':[{'index':0,'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','function':{'arguments':'{"word":"eudca"}','name':'get_word_length'},'type':'function'}]}, response_metadata={'finish_reason':'tool_calls','model_name':'gpt-4o-2024-05-13','system_fingerprint':'fp_3aa7262c27'},id='run-94428a05-fa61-4853-8eeb-7c12f68b447e', tool_calls=[{'name':'get_word_length','args':{'word':'eudca'},'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','type':'tool_call'}], tool_call_chunks=[{'name':'get_word_length','args':'{"word":"eudca"}','id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','index':0,'type':'tool_call_chunk'}])], tool_call_id='call_P3LQGFQ50JvKMtLpLXJ2SSsZ')],'messages':[AIMessageChunk(content='', additional_kwargs={'tool_calls':[{'index':0,'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','function':{'arguments':'{"word":"eudca"}','name':'get_word_length'},'type':'function'}]}, response_metadata={'finish_reason':'tool_calls','model_name':'gpt-4o-2024-05-13','system_fingerprint':'fp_3aa7262c27'},id='run-94428a05-fa61-4853-8eeb-7c12f68b447e', tool_calls=[{'name':'get_word_length','args':{'word':'eudca'},'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','type':'tool_call'}], tool_call_chunks=[{'name':'get_word_length','args':'{"word":"eudca"}','id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','index':0,'type':'tool_call_chunk'}])]},{'steps':[AgentStep(action=ToolAgentAction(tool='get_word_length', tool_input={'word':'eudca'}, log="\nInvoking: `get_word_length` with `{'word': 'eudca'}`\n\n\n", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls':[{'index':0,'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','function':{'arguments':'{"word":"eudca"}','name':'get_word_length'},'type':'function'}]}, response_metadata={'finish_reason':'tool_calls','model_name':'gpt-4o-2024-05-13','system_fingerprint':'fp_3aa7262c27'},id='run-94428a05-fa61-4853-8eeb-7c12f68b447e', tool_calls=[{'name':'get_word_length','args':{'word':'eudca'},'id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','type':'tool_call'}], tool_call_chunks=[{'name':'get_word_length','args':'{"word":"eudca"}','id':'call_P3LQGFQ50JvKMtLpLXJ2SSsZ','index':0,'type':'tool_call_chunk'}])], tool_call_id='call_P3LQGFQ50JvKMtLpLXJ2SSsZ'), observation=5)],'messages':[FunctionMessage(content='5', name='get_word_length')]},{'output':'The word "eudca" has 5 letters.','messages':[AIMessage(content='The word "eudca" has 5 letters.')]}]
复制代码
换一个例子:
  1. from langchain_core.prompts import MessagesPlaceholder
  2. MEMORY_KEY ="chat_history"
  3. prompt = ChatPromptTemplate.from_messages([("system","You are very powerful assistant, but bad at calculating lengths of words.",),
  4.         MessagesPlaceholder(variable_name=MEMORY_KEY),("user","{input}"),
  5.         MessagesPlaceholder(variable_name="agent_scratchpad"),])from langchain_core.messages import AIMessage, HumanMessage
  6. chat_history =[]
  7. agent =({"input":lambda x: x["input"],"agent_scratchpad":lambda x: format_to_openai_tool_messages(
  8.             x["intermediate_steps"]),"chat_history":lambda x: x["chat_history"],}| prompt
  9.     | llm_with_tools
  10.     | OpenAIToolsAgentOutputParser())
  11. agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
  12. input1 ="how many letters in the word educa?"
  13. result = agent_executor.invoke({"input": input1,"chat_history": chat_history})
  14. chat_history.extend([
  15.         HumanMessage(content=input1),
  16.         AIMessage(content=result["output"]),])
  17. agent_executor.invoke({"input":"is that a real word?","chat_history": chat_history})"""
  18. > Entering new AgentExecutor chain...
  19. Invoking: `get_word_length` with `{'word': 'educa'}`
  20. 5The word "educa" has 5 letters.
  21. > Finished chain.
  22. > Entering new AgentExecutor chain...
  23. "Educa" is not a standard English word. It might be a truncated form of "education" or a name, but it is not commonly recognized as a standalone word in English.
  24. > Finished chain.
  25. {'input': 'is that a real word?',
  26. 'chat_history': [HumanMessage(content='how many letters in the word educa?'),
  27.   AIMessage(content='The word "educa" has 5 letters.')],
  28. 'output': '"Educa" is not a standard English word. It might be a truncated form of "education" or a name, but it is not commonly recognized as a standalone word in English.'}
  29. """
复制代码

  • messages
      systemhuman (user)ai (assistant)human (user)ai (assistant)…

  1. chat_history =[]# https://smith.langchain.com/hub/hwchase17/openai-functions-agent# input_variables=['agent_scratchpad', 'input'], optional_variables=['chat_history']
  2. prompt = hub.pull('hwchase17/openai-functions-agent')
  3. agent =({"input":lambda x: x["input"],"agent_scratchpad":lambda x: format_to_openai_tool_messages(
  4.             x["intermediate_steps"]),"chat_history":lambda x: x["chat_history"],}| prompt
  5.     | llm_with_tools
  6.     | OpenAIToolsAgentOutputParser())
  7. agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
  8. input1 ="how many letters in the word dadebfdr?"
  9. result = agent_executor.invoke({"input": input1,"chat_history": chat_history})
  10. chat_history.extend([
  11.         HumanMessage(content=input1),
  12.         AIMessage(content=result["output"]),])
  13. agent_executor.invoke({"input":"is that a real word?","chat_history": chat_history})
复制代码
输出结果:
  1. > Entering new AgentExecutor chain...
  2. Invoking: `get_word_length` with `{'word':'dadebfdr'}`
  3. 8The word "dadebfdr" has 8 letters.> Finished chain.> Entering new AgentExecutor chain...
  4. No,"dadebfdr" does not appear to be a real word in the English language. It seems to be a random string of letters.> Finished chain.{'input':'is that a real word?','chat_history':[HumanMessage(content='how many letters in the word dadebfdr?'),
  5.   AIMessage(content='The word "dadebfdr" has 8 letters.')],'output':'No, "dadebfdr" does not appear to be a real word in the English language. It seems to be a random string of letters.'}
复制代码

3 LangGraph 基本概念(AgentState、StateGraph,nodes,edges)


  • langgraph studio
      https://www.youtube.com/watch?v=pLPJoFvq4_M
    https://github.com/langchain-ai/langgraph/blob/main/examples/reflexion/reflexion.ipynbhttps://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/force-calling-a-tool-first.ipynb
所有用到的包
  1. import os
  2. from typing import TypedDict, Annotated, List, Union, Sequence
  3. import operator
  4. import json
  5. from dotenv import load_dotenv
  6. from langchain import hub
  7. from langchain.tools import BaseTool, StructuredTool, Tool, tool
  8. from langchain.tools.render import format_tool_to_openai_function
  9. from langchain_core.utils.function_calling import convert_to_openai_function
  10. from langchain_openai.chat_models import ChatOpenAI
  11. from langchain.agents import create_openai_functions_agent
  12. from langchain_core.utils.function_calling import convert_to_openai_function
  13. from langchain_core.agents import AgentAction, AgentFinish
  14. from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, FunctionMessage
  15. from langgraph.graph import END, StateGraph, MessagesState
  16. from langgraph.prebuilt import ToolNode, ToolInvocation
  17. from langgraph.prebuilt.tool_executor import ToolExecutor
  18. os.environ['http_proxy']='http://127.0.0.1:7890'
  19. os.environ['https_proxy']='http://127.0.0.1:7890'
复制代码
核心概念

langchain => langgraph

  • chain: 更多是 linear chain,链式顺序执行;
      LCEL:langchain expression language,典型的线性执行

  • graph:可以有分支/判断(是否到达 AgentFinish),有循环
      ReAct(Reasoning & Acting)其实本质上也不是 linear chain 了;

Graph

  • nodes, edges
      每个节点都是一个函数(即tools)所有节点的输入都是 state,输出会附加一些信息到新的 state 里
    state 沿着 edge 从一个 node 到另一个 node 时,state 会发生变化(携带上一个 node 的执行输出信息)
LangGraph的执行中间过程同样可以在LangSmith里查看
案例一:Agent Executor
  1. classAgentState(TypedDict):input:str
  2.    chat_history:list[BaseMessage]
  3.    agent_outcome: Union[AgentAction, AgentFinish,None]# 不断追加和传递
  4.    intermediate_steps: Annotated[list[tuple[AgentAction,str]], operator.add]
复制代码
    TypedDict: 静态类型,可以约定 value 类型的字典
tools

第一个tool的回答作为第二个tool的输入
Tools are interfaces that an agent can use to interact with the world. They combine a few things:
    The name of the toolA description of what the tool isJSON schema of what the inputs to the tool areThe function to call
  1. import random
  2. @tool("upper_case", return_direct=True)defto_upper_case(input:str)->str:"""Returns the input as all upper case."""returninput.upper()@tool("random_number", return_direct=True)defrandom_number_maker(input:str)->str:"""Returns a random number between 0-100."""return random.randint(0,100)
  3. tools =[to_upper_case,random_number_maker]
  4. random_number_maker.run('random')
  5. to_upper_case.run('hello WORLD')
复制代码
Agent
  1. # https://smith.langchain.com/hub/hwchase17/openai-functions-agent
  2. prompt = hub.pull('hwchase17/openai-functions-agent')
  3. llm = ChatOpenAI(model="gpt-4o", streaming=True)
  4. prompt
  5. # ChatPromptTemplate(input_variables=['agent_scratchpad', 'input'], optional_variables=['chat_history'], input_types={'chat_history': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]], 'agent_scratchpad': typing.List[typing.Union[langchain_core.messages.ai.AIMessage, langchain_core.messages.human.HumanMessage, langchain_core.messages.chat.ChatMessage, langchain_core.messages.system.SystemMessage, langchain_core.messages.function.FunctionMessage, langchain_core.messages.tool.ToolMessage]]}, partial_variables={'chat_history': []}, metadata={'lc_hub_owner': 'hwchase17', 'lc_hub_repo': 'openai-functions-agent', 'lc_hub_commit_hash': 'a1655024b06afbd95d17449f21316291e0726f13dcfaf990cc0d18087ad689a5'}, messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')), MessagesPlaceholder(variable_name='chat_history', optional=True), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')), MessagesPlaceholder(variable_name='agent_scratchpad')])
复制代码
上面的输出表明模型其实并没有使用tools,因为这只是一个step,不是整个pipeline,下面我们看如何执行整个graph
  1. agent = create_openai_functions_agent(llm, tools, prompt)# langchain_core.runnables.base.RunnableSequence
  2. inputs ={"input":"give me a random number and then write in words and make it upper case.","chat_history":[],"intermediate_steps":[]}
  3. agent_outcome = agent.invoke(inputs)# AgentActionMessageLog(tool='random_number', tool_input={'input': 'Generate a random number'}, log="\nInvoking: `random_number` with `{'input': 'Generate a random number'}`\n\n\n", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{"input":"Generate a random number"}', 'name': 'random_number'}}, response_metadata={'finish_reason': 'function_call', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3aa7262c27'}, id='run-e2652fc2-03db-4c6c-9b26-310d80e0f06a-0')])
复制代码
tools
  1. tool_executor = ToolExecutor(tools)# tool_executor = ToolNode(tools)defrun_agent(state):
  2.     agent_outcome = agent.invoke(state)# AgentState.agent_outcome: 当前输出,决策动作return{"agent_outcome": agent_outcome}defexecute_tools(state):
  3.     agent_action = state['agent_outcome']
  4.     output = tool_executor.invoke(agent_action)print(f"The agent action is {agent_action}")print(f"The tool result is: {output}")# AgentState.intermediate_steps: 追加逻辑return{"intermediate_steps":[(agent_action,str(output))]}defshould_continue(state):ifisinstance(state['agent_outcome'], AgentFinish):return"end"return"continue"
复制代码
graphs
  1. workflow = StateGraph(AgentState)
  2. workflow.add_node("agent", run_agent)
  3. workflow.add_node("action", execute_tools)
  4. workflow.set_entry_point("agent")# 条件分支
  5. workflow.add_conditional_edges("agent",
  6.     should_continue,{"continue":"action","end": END
  7.     })
  8. workflow.add_edge('action','agent')
  9. graph = workflow.compile()from IPython.display import Image, display
  10. try:
  11.     display(Image(graph.get_graph().draw_mermaid_png()))except Exception:# This requires some extra dependencies and is optionalpass
复制代码
【学习笔记】Langchain基础-1.png


这个过程跟networkx还有dgl是很像的,就是定义节点,定义边,然后可以可视化整个LangGraph
【学习笔记】Langchain基础-2.png
【学习笔记】Langchain基础-3.png
【学习笔记】Langchain基础-4.png
【学习笔记】Langchain基础-5.png
【学习笔记】Langchain基础-6.png
【学习笔记】Langchain基础-7.png
【学习笔记】Langchain基础-8.png
【学习笔记】Langchain基础-9.png
【学习笔记】Langchain基础-10.png
【学习笔记】Langchain基础-11.png
【学习笔记】Langchain基础-12.png
【学习笔记】Langchain基础-13.png
【学习笔记】Langchain基础-14.png
【学习笔记】Langchain基础-15.png
【学习笔记】Langchain基础-16.png
【学习笔记】Langchain基础-17.png
【学习笔记】Langchain基础-18.png
【学习笔记】Langchain基础-19.png
【学习笔记】Langchain基础-20.png
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题
阅读排行更多+

Powered by Discuz! X3.4© 2001-2013 Discuz Team.( 京ICP备17022993号-3 )