零基础快速构建你的LLM Agent框架,掌握AI的未来!

前言

构建代理时,开发者不仅需要决定使用的模型、用例和架构,还必须选择合适的框架。

是选择经验丰富的 LangGraph,还是尝试新兴的 LlamaIndex 工作流?又或者,走传统路线,完全手动编写代码?为了简化这一决策过程,我在过去几周里使用各大主流框架构建了相同的代理程序,并深入分析了它们的优缺点。

第一个选项是完全跳过框架,完全自己构建代理。在开始这个项目时,这就是我开始采用的方法。

下面基于代码的代理由一个 OpenAI 驱动的路由器组成,该路由器使用函数调用来选择要使用的正确技能。该技能完成后,它会返回路由器以调用其他技能或响应用户。

def router(messages):
    if not any(
        isinstance(message, dict) and message.get("role") == "system" for message in messages
    ):
        system_prompt = {"role": "system", "content": SYSTEM_PROMPT}
        messages.append(system_prompt)

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=skill_map.get_combined_function_description_for_openai(),
    )

    messages.append(response.choices[0].message)
    tool_calls = response.choices[0].message.tool_calls
    if tool_calls:
        handle_tool_calls(tool_calls, messages)
        return router(messages)
    else:
        return response.choices[0].message.content

技能本身在它们自己的类(例如 GenerateSQLQuery)中定义,这些类共同保存在 SkillMap 中。路由器本身只与 SkillMap 交互,它用于加载技能名称、描述和可调用函数。这种方法意味着,向代理添加新技能就像将该技能编写为自己的类一样简单,然后将其添加到 SkillMap 中的技能列表中即可。这里的想法是让添加新技能变得容易,而不会干扰 router 代码。

class SkillMap:
    def __init__(self):
        skills = [AnalyzeData(), GenerateSQLQuery()]

        self.skill_map = {}
        for skill in skills:
            self.skill_map[skill.get_function_name()] = (
                skill.get_function_dict(),
                skill.get_function_callable(),
            )

    def get_function_callable_by_name(self, skill_name) -> Callable:
        return self.skill_map[skill_name][1]

    def get_combined_function_description_for_openai(self):
        combined_dict = []
        for _, (function_dict, _) in self.skill_map.items():
            combined_dict.append(function_dict)
        return combined_dict

    def get_function_list(self):
        return list(self.skill_map.keys())

    def get_list_of_function_callables(self):
        return [skill[1] for skill in self.skill_map.values()]

    def get_function_description_by_name(self, skill_name):
        return str(self.skill_map[skill_name][0]["function"])

总的来说,这种方法的实现相当简单,但也带来了一些挑战。基于代码的方法提供了一个很好的基线和起点,提供了一种很好的方法来了解代理的工作原理,而无需依赖来自主流框架的固定代理教程。尽管说服 LLM 运行可能具有挑战性,但代码结构本身足够简单,并且可能对某些用例有意义。

LangGraph 是历史最悠久的代理框架之一,于 2024 年 1 月首次发布。该框架旨在通过采用 Pregel 图形结构来解决现有管道和链的非循环性质。LangGraph 通过添加节点、边和条件边的概念来遍历图形,可以更轻松地在代理中定义循环。LangGraph 构建在 LangChain 之上,并使用该框架中的对象和类型。

LangGraph 代理看起来与纸上基于代码的代理相似,但其背后的代码却截然不同。LangGraph 在技术上仍然使用“路由器”,因为它使用函数调用 OpenAI,并使用响应继续新的步骤。但是,程序在技能之间移动的方式完全不同。

tools = [generate_and_run_sql_query, data_analyzer]
model = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools)

def create_agent_graph():
    workflow = StateGraph(MessagesState)

    tool_node = ToolNode(tools)
    workflow.add_node("agent", call_model)
    workflow.add_node("tools", tool_node)

    workflow.add_edge(START, "agent")
    workflow.add_conditional_edges(
        "agent",
        should_continue,
    )
    workflow.add_edge("tools", "agent")

    checkpointer = MemorySaver()
    app = workflow.compile(checkpointer=checkpointer)
    return app

此处定义的图形有一个用于初始 OpenAI 调用的节点,上面称为“agent”,还有一个用于工具处理步骤的节点,称为“tools”。LangGraph 有一个名为 ToolNode 的内置对象,它获取可调用工具的列表,并根据 ChatMessage 响应触发它们,然后再次返回到 “agent” 节点。

def should_continue(state: MessagesState):
    messages = state["messages"]
    last_message = messages[-1]
    if last_message.tool_calls:
        return "tools"
    return END

def call_model(state: MessagesState):
    messages = state["messages"]
    response = model.invoke(messages)
    return {"messages": [response]}

LangGraph 的主要好处之一是它易于使用。图形结构代码干净且易于访问。特别是如果您具有复杂的节点逻辑,则拥有图形的单一视图可以更轻松地了解代理是如何连接在一起的。LangGraph 还使转换在 LangChain 中构建的现有应用程序变得简单明了。

Workflows 是代理框架领域的新进入者,于今年夏天早些时候首次亮相。与 LangGraph 一样,它旨在使循环代理更易于构建。工作流还特别关注异步运行。

Workflows 的一些元素似乎是对 LangGraph 的直接响应,特别是它使用事件而不是边缘和条件边缘。工作流使用步骤(类似于 LangGraph 中的节点)来容纳逻辑,并使用发出和接收的事件在步骤之间移动。

下面的代码定义了 Workflow 结构。与 LangGraph 类似,这是我准备状态并将技能附加到 LLM。

class AgentFlow(Workflow):
    def __init__(self, llm, timeout=300):
        super().__init__(timeout=timeout)
        self.llm = llm
        self.memory = ChatMemoryBuffer(token_limit=1000).from_defaults(llm=llm)
        self.tools = []
        for func in skill_map.get_function_list():
            self.tools.append(
                FunctionTool(
                    skill_map.get_function_callable_by_name(func),
                    metadata=ToolMetadata(
                        name=func, description=skill_map.get_function_description_by_name(func)
                    ),
                )
            )

    @step
    async def prepare_agent(self, ev: StartEvent) -> RouterInputEvent:
        user_input = ev.input
        user_msg = ChatMessage(role="user", content=user_input)
        self.memory.put(user_msg)

        chat_history = self.memory.get()
        return RouterInputEvent(input=chat_history)

设置 Workflow 后,我定义了路由代码:

@step
async def router(self, ev: RouterInputEvent) -> ToolCallEvent | StopEvent:
    messages = ev.input

    if not any(
        isinstance(message, dict) and message.get("role") == "system" for message in messages
    ):
        system_prompt = ChatMessage(role="system", content=SYSTEM_PROMPT)
        messages.insert(0, system_prompt)

    with using_prompt_template(template=SYSTEM_PROMPT, version="v0.1"):
        response = await self.llm.achat_with_tools(
            model="gpt-4o",
            messages=messages,
            tools=self.tools,
        )

    self.memory.put(response.message)

    tool_calls = self.llm.get_tool_calls_from_response(response, error_on_no_tool_call=False)
    if tool_calls:
        return ToolCallEvent(tool_calls=tool_calls)
    else:
        return StopEvent(result=response.message.content)
@step
async def tool_call_handler(self, ev: ToolCallEvent) -> RouterInputEvent:
    tool_calls = ev.tool_calls

    for tool_call in tool_calls:
        function_name = tool_call.tool_name
        arguments = tool_call.tool_kwargs
        if "input" in arguments:
            arguments["prompt"] = arguments.pop("input")

        try:
            function_callable = skill_map.get_function_callable_by_name(function_name)
        except KeyError:
            function_result = "Error: Unknown function call"

        function_result = function_callable(arguments)
        message = ChatMessage(
            role="tool",
            content=function_result,
            additional_kwargs={"tool_call_id": tool_call.tool_id},
)

        self.memory.put(message)

    return RouterInputEvent(input=self.memory.get())

选择代理框架只是影响生成式 AI 系统生产结果的众多选择之一。与往常一样,拥有强大的护栏和 LLM 跟踪是值得的,并且在新的代理框架、研究和模型颠覆现有技术时保持敏捷。

最后的最后

感谢你们的阅读和喜欢,我收藏了很多技术干货,可以共享给喜欢我文章的朋友们,如果你肯花时间沉下心去学习,它们一定能帮到你。

因为这个行业不同于其他行业,知识体系实在是过于庞大,知识更新也非常快。作为一个普通人,无法全部学完,所以我们在提升技术的时候,首先需要明确一个目标,然后制定好完整的计划,同时找到好的学习方法,这样才能更快的提升自己。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

大模型知识脑图

为了成为更好的 AI大模型 开发者,这里为大家提供了总的路线图。它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

经典书籍阅读

阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。

在这里插入图片描述

实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

在这里插入图片描述

面试资料

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下

在这里插入图片描述

640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值