手撕LangChain代码,新手请进

假如你和我一样在准备24年的春招,在前端全栈外,再准备一些AI的内容是非常有必要的。24年是AI红利年,AIGC+各种岗位大厂机会会多些,同意的请点赞。也欢迎朋友们加我微信shunwuyu, 一起交流。

前言

在上篇文章LangGraph 进化LangChain到多代理运行时 - 掘金 (juejin.cn)中,我们编写了一个组合search enginetwitter writter engine的例子。LangChain新推出的核心机制LangGraph,以多代理的方式,把活给干完了。今天,我们将细致学习使用到的代码。请打开LangChain-Tutorials/langgraph_nodes_edges.ipynb at main · sugarforever/LangChain-Tutorials (github.com)

代码

我们将在colab调试我们的代码,colab是Google推出的在线机器学习平台,除了一些模型训练、比较费GPU的任务外,我的学习型demo都可以在这里free跑起来。

安装依赖

css 复制代码
!pip install -q -U langchain langchain_openai langgraph google-search-results
  • 首先!是什么意思呢?pip install 是安装依赖包,我们在写python的时候,好像前面没有加过!。这里的!是指在特定交互环境中(如 Jupyter Notebook、JupyterLab 或 Google Colab)的命令执行标记。用于告诉该环境应当以系统shell来执行紧跟在其后的命令,而不是将其当作Python代码来执行。

  • -q -U 是什么意思

    -q 只显示 错误信息,如果没有,就不显示,让安装以安静模式运行 -U update 表示安装最新版本的包

  • langchain_openai

    我们在玩node的时候,会到npmjs.org去查看包的详细信息。当我们用pip安装包的时候,可以到pypi.org/,去看。

    langchain框架负责核心模块,langchain_openaiLangChain集成了 OpenAI SDK的调用。

    langgraph是新功能,为langchain带来了多代理的编程方式 google-search-results 来自serapi

  • 注册serapi, 并拿到sdk

    我们在开发生成式应用时,多次使用到serapi先从google搜索中拿到数据,再去生成。

  • 环境变量设置
csharp 复制代码
import os # os 模块提供了与操作系统相关的接口, 比如文件、目录,环境变量、进程管理等 这里主要使用环境变量
from google.colab import userdata 

os.environ['SERPAPI_API_KEY'] = userdata.get('GOOGLE_API_KEY') os.environ['OPENAI_API_KEY'] = userdata.get('OPENAI_API_KEY') os.environ["LANGCHAIN_TRACING_V2"] = "true" 
# 项目名
os.environ["LANGCHAIN_PROJECT"] = "LangGraph" 
# LANGCHAIN_API_KEY
os.environ["LANGCHAIN_API_KEY"] = userdata.get('LANGSMITH_API_KEY')

colab 为我们提供了userdata, 统一存放OPENAI_API_KEYGOOGLE_API_KEY等,我们可以在任一notebook里根据key取出userData里的值。

  • LangSmith

    是一个用于构建生产级 LLM 应用程序的平台,用于交付,可以来到LangSmith注册拿到key, 它的使用我们后续开新的文章再细讲。

  • 搜索试运行

java 复制代码
from langchain_community.utilities import SerpAPIWrapper
search = SerpAPIWrapper() 
search.run("Obama's first name?")

langchain_communitylangchain和一些第三方常用库结合的模块。当我们安装了langchaingoogle-search-results后,我们就可以调用langchain_community.utilities提供的SerpAPIWrapper来做搜索工作。可以这样理解,langchain_community.utilities将第三方工具封装得更适合langchain业务,比如在上面的代码里,实例化后的search是可以run的。所以,我们有langchain-opnai, 有SerpAPIWrapper, 第三方工具库由langchain_community.utilities提供。

Barack Hussein Obama II
  • 引入相应的模块
python 复制代码
import functools, operator, requests, os, json 
from langchain.agents import AgentExecutor, create_openai_tools_agent 
from langchain_core.messages import BaseMessage, HumanMessage 
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser 
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder 
from langgraph.graph import StateGraph, END 
from langchain.tools import tool 
from langchain_openai import ChatOpenAI 
from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict

首先,我们在标准库中引入functools, 它提供了一些函数式编程工具,比如等下要用到的装饰器;operator包含了很多预定义的函数,如算术运算add等,高阶函数map等;requests用于发送请求。

接着,我们从agents模块引入 AgentExecutorcreate_openai_tools_agentAgentExecutorLangChain框架中扮演着核心角色,它负责执行和管理不同类型的Agent, 并协调它们之间的交互。create_openai_tools_agent用于创建一个与OpenAI服务集成的Agent。让我们以Agent的方式调用OpenAI。

紧接着,langchain_core提供了相应角色message类,这里引入了BaseMessageHumanMessage。BaseMessage是基类,定义了消息的基本结构和行为,HumanMessage可以用来表示用户的chat。

再者,from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser 输出格式化我们使用了JsonOutputFunctionsParser。LLM返回的是文本,output_parsers格式器提供了openai_functions,JsonOutputFunctionsParser函数会将文本转成json格式的输出。

又有 from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder langchain_coreprompts模块提供了MessagesPlaceholder和ChatPromptTemplate,用于Prompt设计。

接下来, from langgraph.graph import StateGraph, END LangGraph出场了,它是LangChain新功能,让我们从graph里引入StateGraph和结束结点

最后 from langchain_openai import ChatOpenAI在 LangChain 框架中引入一个与 OpenAI 的聊天模型,再后面是一堆的类型维束引入。

  • 实例化LLM
ini 复制代码
llm = ChatOpenAI(model="gpt-4-turbo-preview")

大家也可以使用gpt-3.5-turbo, 但是在处理一些复杂的agent时,gpt-4表现更优异。

  • 定义搜索函数
python 复制代码
from langchain_core.messages import ( AIMessage, BaseMessage, ChatMessage, FunctionMessage, HumanMessage, SystemMessage ) 

@tool("web_search") 
def web_search(query: str) -> str: 
    """Search with Google SERP API by a query""" 
    search = SerpAPIWrapper() 
    return search.run(query)

Message 的类型真多啊,这个交给下次开篇讲,这里我们focus等下生成agent的web_search函数。functools提供的装饰器模式,将web_search装饰成为能够搜索的工具函数。这个函数负责调用SerpAPIWrapper,根据传入的查询参数,拿到google的搜索结果。

  • twitter_writer 工具函数
python 复制代码
@tool("twitter_writer") 
def write_tweet(content: str) -> str: 
"""Based a piece of content, write a tweet.""" 
    chat = ChatOpenAI() 
    messages = [ 
    SystemMessage( content="You are a Twitter account operator." 
    " You are responsible for writing a tweet based on the content given." 
    " You should follow the Twitter policy and make sure each tweet has no more than 140 characters." ), 
    HumanMessage( content=content ), ]
    response = chat(messages) 
    return response.content

装饰器仍然是帮助函数成为创建agent的工具函数。函数通过ChatOpenAI提供的聊天实例,然后准备好消息数组。SystemMessage 是聊天开始前的系统设置,在这里指定了它的角色是Twitter writter, 任务是基于给定任务写一篇tweet, 要求不超过140字。HummanMessage 即用户聊天信息,content是传进来的内容,接着 response = chat(messages) 让聊天模型运行, 并返回结果。这个工具函数主要负责生成。从这里可以看出,twitter的生成是以聊天模型调用的方式生成。

  • GraphState
python 复制代码
class AgentState(TypedDict): 
# The annotation tells the graph that new messages will always 
# be added to the current states 
    messages: Annotated[Sequence[BaseMessage], operator.add] 
# The 'next' field indicates where to route to next 
    next: str

langgraph的第一个核心是graphstate, 这个状态标记当前的应用状态。 typing 是 Python 的一个标准库模块,用于提供Type Hints功能。AgentState是一个基于TypedDict的类,里面包含相应的字段。

  • 创建一个agent
css 复制代码
def create_agent(llm: ChatOpenAI, tools: list, system_prompt: str): 
    prompt = ChatPromptTemplate.from_messages( [ ( "system", system_prompt, ), 
        MessagesPlaceholder(variable_name="messages"),            MessagesPlaceholder(variable_name="agent_scratchpad"), ] ) 
    agent = create_openai_tools_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools) 
    return executor 
def agent_node(state, agent, name): 
    result = agent.invoke(state) return {"messages": [HumanMessage(content=result["output"], name=name)]}

封装了一个create_agent方法,参数为llm、tools、system_prompt。我们这里要做的是一个聊天agent。我们准备了prompt, 调用ChatPromptTemplate的from_messages方法。调用create_openai_tools_agent方法生成聊天agent,接受了参数分别是llm,tools数组, prompt。返回AgentExectutor的实例。

  • supervisor_chain
ini 复制代码
# 两个agent
members = ["Search_Engine", "Twitter_Writer"]
makefile 复制代码
system_prompt = ( 
"You are a supervisor tasked with managing a conversation between the" 
" following workers: {members}. Given the following user request," 
" respond with the worker to act next. Each worker will perform a"
" task and respond with their results and status. When finished," 
" respond with FINISH." )

在这个系统prompt里,我们首先指定它是一个监管上面两个agent的supervisor代理。

ini 复制代码
options = ["FINISH"] + members
ini 复制代码
function_def = { "name": "route", "description": "Select the next role.", "parameters": { "title": "routeSchema", "type": "object", "properties": { "next": { "title": "Next", "anyOf": [ {"enum": options}, ], } }, "required": ["next"], }, }
less 复制代码
prompt = ChatPromptTemplate.from_messages( [ ("system", system_prompt), MessagesPlaceholder(variable_name="messages"), ( "system", "Given the conversation above, who should act next?" " Or should we FINISH? Select one of: {options}", ), ] ).partial(options=str(options), members=", ".join(members))

supervisor_chain = ( prompt | llm.bind_functions(functions=[function_def], function_call="route") | JsonOutputFunctionsParser() )
  • 生成相应的agent和节点
ini 复制代码
# 调用create_agent方法,tool 为web_search, prompt 给的身份是web 搜索引擎
search_engine_agent = create_agent(llm, [web_search], "You are a web search engine.") 
# 搜索引擎节点 
functools.partial会返回一个对象,它预填了agent_node函数的部分参数,agent为 search_engine_agent, 名字为Search_Engine
search_engine_node = functools.partial(agent_node, agent=search_engine_agent, name="Search_Engine") 
# twitter 写作agent
twitter_operator_agent = create_agent(llm, [write_tweet], "You are responsible for writing a tweet based on the content given.") 
# twitter写作节点
twitter_operator_node = functools.partial(agent_node, agent=twitter_operator_agent, name="Twitter_Writer") 
# 实例化StateGraph, 将上面的AgentState 作为状态
workflow = StateGraph(AgentState) 
# 给工作流添加工作节点Search_Engine和Twitter_Writer
workflow.add_node("Search_Engine", search_engine_node) workflow.add_node("Twitter_Writer", twitter_operator_node) 
# 最后再添加监督节点,它会看在每个工作节点的状态
workflow.add_node("supervisor", supervisor_chain)
  • 添加边
css 复制代码
for member in members: 
    workflow.add_edge(member, "supervisor") 
    # 条件边
    conditional_map = {k: k for k in members} 
    # 是否结束
    conditional_map["FINISH"] = END 
    # 给监管节点添加条件边 加上supervisor上,它来决定
    # lambda x: x["next"]  去看在节点的next 值 是多少,下一步做什么
    workflow.add_conditional_edges("supervisor", lambda x: x["next"], conditional_map) 
    # 将supervisor 添加为初始点
    workflow.set_entry_point("supervisor") 
    # compile 编译生成graph
    graph = workflow.compile()

添加完后如图

  • 给任务,并监听状态
bash 复制代码
# graph.stream是一个流式接口,遍历所有的节点,拿到输出信息
for s in graph.stream( 
    { 
        "messages": [ 
        # 使用HumanMesasage 代表用户发表任务
            HumanMessage(content="Write a tweet about LangChain news") ]
    } 
): 
    # 如果没有__end__ 就输出反馈
    if "__end__" not in s: 
        print(s) 
        print("----")

总结

静下心来,分析了一版LangChain关于LangGraph的代码,对Prompt、Agent的理解更深入了。

LangGraph还是不如AutoGen 聊天式多代理好理解,代理间的聊天是用chatOpenAI解决的,代理间的执行顺序是由边和条件边来处理的,而任务状态由grapState来搞定。

参考资料

相关推荐
神秘的土鸡2 分钟前
神经网络图像隐写术:用AI隐藏信息的艺术
人工智能·深度学习·神经网络
数据分析能量站3 分钟前
神经网络-LeNet
人工智能·深度学习·神经网络·机器学习
Jaly_W11 分钟前
用于航空发动机故障诊断的深度分层排序网络
人工智能·深度学习·故障诊断·航空发动机
小嗷犬14 分钟前
【论文笔记】Cross-lingual few-shot sign language recognition
论文阅读·人工智能·多模态·少样本·手语翻译
夜幕龙20 分钟前
iDP3复现代码数据预处理全流程(二)——vis_dataset.py
人工智能·python·机器人
吃个糖糖38 分钟前
36 Opencv SURF 关键点检测
人工智能·opencv·计算机视觉
AI慧聚堂1 小时前
自动化 + 人工智能:投标行业的未来是什么样的?
运维·人工智能·自动化
盛世隐者1 小时前
【pytorch】循环神经网络
人工智能·pytorch
cdut_suye1 小时前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
开发者每周简报1 小时前
微软的AI转型故事
人工智能·microsoft