手把手教你从0到1搭建一个AI Agent(智能体)

LLM为什么需要agent化

虽然大语言模型的能力很强大,但是Llm仅限于用于训练的知识,这些知识很快会过时,所以llm有以下缺点

  • 幻觉
  • 结果并不总是真实的
  • 对时事的了解有限或一无所知
  • 难以应对复杂推理和计算

例如:买高铁票
在这里插入图片描述

(虽然LLM完全理解了买票的行为,但是它本身并不知道“我”所处的城市,列车的时刻表,价格等等信息)
而基于大模型的Agent (LLM based Agent) 可以利用外部工具来克服以上缺点。

ReAct Agent

ReAct Agent 论文

LLM Agent 的升级之路:

Standard IO(直接回答) -> COT(chain-of-thought)(思维链) -> Action-Only (Function calling) -> Reason + Action
ReAct = Reasoning(推理) + Action(行动)
在这里插入图片描述

ReAct Agent 的组成部分 (通过LangChain实现)

  • Models:LLM
  • Prompts:对Agent的指令、约束
  • Memory : 记录Action执行状态 & 缓存已知信息
  • Indexes : 用于结构化文档,以便和模型交互
  • Chains :Langchain的核心(链)
  • Agent

ReAct Agent 的prompt 模板

from langchain_core.prompts import PromptTemplate

template = '''Answer the following questions as best you can. You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {input}
Thought:{agent_scratchpad}'''

prompt = PromptTemplate.from_template(template)

代码

手写一个能帮忙买火车票的智能Agent
注:火车票相关API均为mock

安装 & import依赖

pip install langchain
pip install uuid
pip install pydantic


import json
import sys
from typing import List, Optional, Dict, Any, Tuple, Union
from uuid import UUID

from langchain.memory import ConversationTokenBufferMemory
from langchain.tools.render import render_text_description
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.language_models import BaseChatModel
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_core.outputs import GenerationChunk, ChatGenerationChunk, LLMResult
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import StructuredTool
from langchain_openai import ChatOpenAI

from pydantic import BaseModel, Field, ValidationError

定义工具(Tools)

细节可参考LangChain定义Tool

from typing import List

from langchain_core.tools import StructuredTool

def search_train_ticket(
        origin: str,
        destination: str,
        date: str,
        departure_time_start: str,
        departure_time_end: str
) -> List[dict[str, str]]:
    """按指定条件查询火车票"""

    # mock train list
    return [
        {
            "train_number": "G1234",
            "origin": "北京",
            "destination": "上海",
            "departure_time": "2024-06-01 8:00",
            "arrival_time": "2024-06-01 12:00",
            "price": "100.00",
            "seat_type": "商务座",
        },
        {
            "train_number": "G5678",
            "origin": "北京",
            "destination": "上海",
            "departure_time": "2024-06-01 18:30",
            "arrival_time": "2024-06-01 22:30",
            "price": "100.00",
            "seat_type": "商务座",
        },
        {
            "train_number": "G9012",
            "origin": "北京",
            "destination": "上海",
            "departure_time": "2024-06-01 19:00",
            "arrival_time": "2024-06-01 23:00",
            "price": "100.00",
            "seat_type": "商务座",
        }
    ]


def purchase_train_ticket(
        train_number: str,
) -> dict:
    """购买火车票"""
    return {
        "result": "success",
        "message": "购买成功",
        "data": {
            "train_number": "G1234",
            "seat_type": "商务座",
            "seat_number": "7-17A"
        }
    }


search_train_ticket_tool = StructuredTool.from_function(
    func=search_train_ticket,
    name="查询火车票",
    description="查询指定日期可用的火车票。",
)

purchase_train_ticket_tool = StructuredTool.from_function(
    func=purchase_train_ticket,
    name="购买火车票",
    description="购买火车票。会返回购买结果(result), 和座位号(seat_number)",
)

finish_placeholder = StructuredTool.from_function(
    func=lambda: None,
    name="FINISH",
    description="用于表示任务完成的占位符工具"
)

tools = [search_train_ticket_tool, purchase_train_ticket_tool, finish_placeholder]

Prompt

主要任务Prompt

prompt_text = """
你是强大的AI火车票助手,可以使用工具与指令查询并购买火车票

你的任务是:
{task_description}

你可以使用以下工具或指令,它们又称为动作或actions:
{tools}

当前的任务执行记录:
{memory}

按照以下格式输出:

任务:你收到的需要执行的任务
思考: 观察你的任务和执行记录,并思考你下一步应该采取的行动
然后,根据以下格式说明,输出你选择执行的动作/工具:
{format_instructions}
"""

最终回复Prompt

final_prompt = """
你的任务是:
{task_description}

以下是你的思考过程和使用工具与外部资源交互的结果。
{memory}

你已经完成任务。
现在请根据上述结果简要总结出你的最终答案。
直接给出答案。不用再解释或分析你的思考过程。
"""

一些方便编程的工具类

class Action(BaseModel):
    """结构化定义工具的属性"""
    name: str = Field(description="工具或指令名称")
    args: Optional[Dict[str, Any]] = Field(description="工具或指令参数,由参数名称和参数值组成")


class MyPrintHandler(BaseCallbackHandler):
    """自定义LLM CallbackHandler,用于打印大模型返回的思考过程"""
    def __init__(self):
        BaseCallbackHandler.__init__(self)

    def on_llm_new_token(
            self,
            token: str,
            *,
            chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
            run_id: UUID,
            parent_run_id: Optional[UUID] = None,
            **kwargs: Any,
    ) -> Any:
        end = ""
        content = token + end
        sys.stdout.write(content)
        sys.stdout.flush()
        return token

    def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
        end = ""
        content = "\n" + end
        sys.stdout.write(content)
        sys.stdout.flush()
        return response

定义Agent

class MyAgent:
    def __init__(
            self,
            llm: BaseChatModel = ChatOpenAI(
                model="gpt-4-turbo", # agent用GPT4效果好一些,推理能力较强
                temperature=0,
                model_kwargs={
                    "seed": 42
                },
            ),
            tools=None,
            prompt: str = "",
            final_prompt: str = "",
            max_thought_steps: Optional[int] = 10,
    ):
        if tools is None:
            tools = []
        self.llm = llm
        self.tools = tools
        self.final_prompt = PromptTemplate.from_template(final_prompt)
        self.max_thought_steps = max_thought_steps # 最多思考步数,避免死循环
        self.output_parser = PydanticOutputParser(pydantic_object=Action)
        self.prompt = self.__init_prompt(prompt)
        self.llm_chain = self.prompt | self.llm | StrOutputParser() # 主流程的LCEL
        self.verbose_printer = MyPrintHandler()

	    def __init_prompt(self, prompt):
        return PromptTemplate.from_template(prompt).partial(
            tools=render_text_description(self.tools),
            format_instructions=self.__chinese_friendly(
                self.output_parser.get_format_instructions(),
            )
        )

    def run(self, task_description):
		"""Agent主流程"""
		
        # 思考步数
        thought_step_count = 0

        # 初始化记忆
        agent_memory = ConversationTokenBufferMemory(
            llm=self.llm,
            max_token_limit=4000,
        )
        agent_memory.save_context(
            {"input": "\ninit"},
            {"output": "\n开始"}
        )

        # 开始逐步思考
        while thought_step_count < self.max_thought_steps:
            print(f">>>>Round: {thought_step_count}<<<<")
            action, response = self.__step(
                task_description=task_description,
                memory=agent_memory
            )

            # 如果是结束指令,执行最后一步
            if action.name == "FINISH":
                break

            # 执行动作
            observation = self.__exec_action(action)
            print(f"----\nObservation:\n{observation}")

            # 更新记忆
            self.__update_memory(agent_memory, response, observation)

            thought_step_count += 1

        if thought_step_count >= self.max_thought_steps:
            # 如果思考步数达到上限,返回错误信息
            reply = "抱歉,我没能完成您的任务。"
        else:
            # 否则,执行最后一步
            final_chain = self.final_prompt | self.llm | StrOutputParser()
            reply = final_chain.invoke({
                "task_description": task_description,
                "memory": agent_memory
            })

        return reply

    def __step(self, task_description, memory) -> Tuple[Action, str]:

        """执行一步思考"""
        response = ""
        for s in self.llm_chain.stream({
            "task_description": task_description,
            "memory": memory
        }, config={
            "callbacks": [
                self.verbose_printer
            ]
        }):
            response += s

        action = self.output_parser.parse(response)
        return action, response

    def __exec_action(self, action: Action) -> str:
        observation = "没有找到工具"
        for tool in self.tools:
            if tool.name == action.name:
                try:
                    # 执行工具
                    observation = tool.run(action.args)
                except ValidationError as e:
                    # 工具的入参异常
                    observation = (
                        f"Validation Error in args: {str(e)}, args: {action.args}"
                    )
                except Exception as e:
                    # 工具执行异常
                    observation = f"Error: {str(e)}, {type(e).__name__}, args: {action.args}"

        return observation

    @staticmethod
    def __update_memory(agent_memory, response, observation):
        agent_memory.save_context(
            {"input": response},
            {"output": "\n返回结果:\n" + str(observation)}
        )

    @staticmethod
    def __chinese_friendly(string) -> str:
        lines = string.split('\n')
        for i, line in enumerate(lines):
            if line.startswith('{') and line.endswith('}'):
                try:
                    lines[i] = json.dumps(json.loads(line), ensure_ascii=False)
                except:
                    pass
        return '\n'.join(lines)

测试

if __name__ == "__main__":
    my_agent = MyAgent(
        tools=tools,
        prompt=prompt_text,
        final_prompt=final_prompt,
    )

    task = "帮我买24年6月1日早上去上海的火车票"
    reply = my_agent.run(task)
    print(reply)

结果

第一轮思考

Agent根据要求,选择了需要使用的Tool,组装了请求参数并完成了调用。
(还可以多定义一些Tools,比如获取当前位置的,获取今天日期的工具等等,这样这里的查询火车票的参数可以更智能)

>>>>Round: 0<<<<
任务:帮我买24年6月1日早上去上海的火车票

思考: 根据任务需求,首先需要查询2024年6月1日早上从当前位置到上海的火车票。这需要使用“查询火车票”工具,指定出发地、目的地、日期以及早上的时间范围。

动作/工具:

{
  "name": "查询火车票",
  "args": {
    "origin": "当前位置",
    "destination": "上海",
    "date": "2024-06-01",
    "departure_time_start": "00:00",
    "departure_time_end": "12:00"
  }
}

----
Observation:
[{'train_number': 'G1234', 'origin': '北京', 'destination': '上海', 'departure_time': '2024-06-01 8:00', 'arrival_time': '2024-06-01 12:00', 'price': '100.00', 'seat_type': '商务座'}, {'train_number': 'G5678', 'origin': '北京', 'destination': '上海', 'departure_time': '2024-06-01 18:30', 'arrival_time': '2024-06-01 22:30', 'price': '100.00', 'seat_type': '商务座'}, {'train_number': 'G9012', 'origin': '北京', 'destination': '上海', 'departure_time': '2024-06-01 19:00', 'arrival_time': '2024-06-01 23:00', 'price': '100.00', 'seat_type': '商务座'}]

第二轮思考

根据查询出的车票信息去调用购票的Tool

>>>>Round: 1<<<<
任务:帮我买24年6月1日早上去上海的火车票

思考: 根据查询结果,有三个车次可供选择,但只有车次G1234符合早上出发的要求,因此应选择购买这个车次的票。

动作/工具:
{
  "name": "购买火车票",
  "args": {
    "train_number": "G1234"
  }
}
----
Observation:
{'result': 'success', 'message': '购买成功', 'data': {'train_number': 'G1234', 'seat_type': '商务座', 'seat_number': '7-17A'}}

第三轮思考

LLM识别到任务已完成,输出了结果

>>>>Round: 2<<<<
任务:帮我买24年6月1日早上去上海的火车票

思考: 根据执行记录,已经成功购买了2024年6月1日早上从北京到上海的火车票(车次G1234)。因此,接下来的任务是完成这个购票任务。

动作/工具:
{
  "name": "FINISH"
}

购买成功。您已成功购买2024年6月1日早上从北京出发前往上海的火车票,车次为G1234,座位类型为商务座,座位号为7-17A。
  • 40
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
1. 为了搭建一个React项目,首先要确保你的开发环境已经配置好了。你需要安装Node.js和npm(Node包管理器)。 2. 使用命令行工具进入你想要保存项目的文件夹,并执行以下命令来创建一个新的React应用: ```shell npx create-react-app my-app ``` 这将使用create-react-app脚手架工具创建一个新的React应用,并在my-app文件夹中初始化项目结构。 3. 进入my-app文件夹,并启动开发服务器: ```shell cd my-app npm start ``` 这将启动一个本地开发服务器,并在浏览器中打开http://localhost:3000以查看你的React应用。 4. 现在你可以开始编写你的React组件了。你可以创建函数组件或类组件来定义你的UI。函数组件是一个纯函数,接收props对象并返回一个React元素;而类组件需要继承React.Component,并实现一个render函数来返回React元素。 5. 如果你需要在组件之间进行通信,可以使用Redux这样的状态管理库。Redux可以帮助你管理全局的状态,并使不同组件之间共享数据变得更加容易。你可以使用npm来安装Redux和相关的库: ```shell npm install redux react-redux ``` 然后,你可以在你的应用中创建Redux的store,并通过Provider组件将它提供给整个应用。你可以使用connect函数来连接你的组件,使其能够访问Redux中的状态。 6. 最后,你可以使用第三方UI库来加快你的开发速度。Ant Design是一个流行的React UI框架,它提供了丰富的组件和样式,可以帮助你快速构建漂亮的用户界面。你可以使用npm来安装Ant Design: ```shell npm install antd ``` 然后,你可以在你的组件中引入Ant Design的组件,并使用它们来构建你的UI。Ant Design的文档提供了详细的使用说明和示例代码,可以帮助你更好地了解和使用该框架。 以上是从0到1搭建一个React项目的一般步骤。你可以根据实际需求和项目要求进行相应的调整和扩展。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [REACT实战项目从0到1搭建(仅供参考)](https://blog.csdn.net/qq_31851435/article/details/121740400)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [react-从0到1新建react项目](https://blog.csdn.net/weixin_40220492/article/details/127145343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值