如何用 LlamaIndex 实现 agent

202 阅读11分钟

agent 是什么?

打开市面上流行的AIGC应用,agent 几乎是必备的功能。 agent到底是什么呢?

我们先从字面意思理解,看看各家公司做的产品是如何定义 agent 的。

智谱清言chatglm.cn) 将 agent 定义为 “智能体”

通义千问tongyi.aliyun.com/)将 agent 定义为 “智能体”

Kimikimi.moonshot.cn/)将 agent 定义为 “私人助理”

扣子www.coze.cn/) 将 agent 定义为 “智能体”

文心一言yiyan.baidu.com/) 将 agent 定义为 “智能体” (无图,拿来凑数)

agent 从英文直译过来是 “代理”,但从国内各AIGIC的产品定义来看,绝大多数公司将 agent 定义为了 “智能体”

好,我们再进一步,那什么是智能体呢?

从 “智能体” 使用者的角度看,“智能体” 就像专门完成某一类任务的机器人。智能体的设计目的其实就是为了实现特定的目标或完成特定的任务。

它像是一个专家一样,专门解决某一类的问题,用医生做比喻,agent就像一个专科大夫,比如骨科大夫,只能看骨科相关的病,而大模型LLM 像是一个全科大夫,什么病都能看。

从设计上不一样,使用上自然就要有所区别,比如你要处理和计算复杂的数学公式,那么向专门为解决这类问题而设计的 “智能体” 提问就比直接向LLM 提问得到的答案要准确。

agent 的创建和使用

我们以 智谱清言 举例,下图是在创建 “智能体” 时的设置页面内容,可以看到有不少的配置。这里面,我要说两个重要的配置。

第一个重要的配置是:“配置信息

看上图你是否觉得很熟悉,是的,如果你看过我之前的文章,就会注意到,这不就是 "prompt" 吗? 准确地来说是 system promptrole prompt

所以,在我最初的认识里,agent 或者叫 “智能体” 简直就是一个 "prompt engineering" 的产物,只需要把 “提示词” 写好,就能创建好一个 agent 了。后来,我了解到,提示词工程确实很重要,但 agent 并不是只包括设置提示词 。

除了上图所示的 “联网能力”、“代码能力” 这些显而易见的功能外。另一个重要的配置就是 “知识库” 。 加上知识库后,实际上这就是一个 RAG 应用了。这会让 agent 本身外挂一个我们自己上传的知识库,让它学习了我们上传的“知识” 后,再接受提问,就可以更准备地做出回答。

LlamaIndex

demo

用 LlamaIndex 实现一个简单的 agent demo 比较容易,看一下具体的代码实现:

import sys
import os

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))

from llama_index.core.agent import ReActAgent
from llama_index.core.tools import FunctionTool
from llamaindex_demo.custom.custom_llm_glm import GLM4LLM


def multiply(a: float, b: float) -> float:
    """Multiply two numbers and returns the product"""
    return a * b


def add(a: float, b: float) -> float:
    """Add two numbers and returns the sum"""
    return a + b


def main():

    multiply_tool = FunctionTool.from_defaults(fn=multiply)
    add_tool = FunctionTool.from_defaults(fn=add)

    # 使用GLM-4 Plus模型
    llm = GLM4LLM()
    # 创建ReActAgent实例
    agent = ReActAgent.from_tools([multiply_tool, add_tool], llm=llm, verbose=True)

    response = agent.chat("20+(2*4)等于多少?使用工具计算每一步")

    print(response)


if __name__ == "__main__":
    main()

LLM 上,我们继续使用之前文章中使用过的 GLM。

我们看一下它的输出

可以看出,它将提问中的计算步骤分别利用了我们自定义的函数 addmultiply ,而不是走大模型。挺有意思的吧,我们可以自定义 agent 中的某些处理流程。除了使用 prompt 外,我们的控制权更大了。

Rag demo

这个 demo 我们来看一下如何把rag 集成到 agent中。也很简单,我们直接上代码:

import os
import sys
import re

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))

from llama_index.core.agent import ReActAgent
from llama_index.core.tools import FunctionTool
from llama_index.core.tools import QueryEngineTool
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llamaindex_demo import logger
from llamaindex_demo.custom.custom_llm_glm import GLM4LLM
from llamaindex_demo.custom.custom_embedding_zhipu import ZhipuEmbeddings
from llama_parse import LlamaParse

# 设置环境变量,禁用tokenizers的并行处理
os.environ["TOKENIZERS_PARALLELISM"] = "false"


def toHtml(text: str) -> str:
    """遇到英文单词就用html标签包裹"""
    return re.sub(r"(\b[a-zA-Z]+\b)", r'<span style="color:red;">\1</span>', text)


def run_glm4_query_with_embeddings(query: str):
    # 从指定目录加载文档数据
    documents = SimpleDirectoryReader(input_files=["./data/sample.txt"]).load_data()

    # 设置LLM和嵌入模型
    Settings.llm = GLM4LLM()
    Settings.embed_model = ZhipuEmbeddings()

    # 创建索引和查询引擎 show_progress=True 显示 embedding 进度
    index = VectorStoreIndex.from_documents(documents, show_progress=True)

    query_engine = index.as_query_engine(streaming=True)

    yc_tool = QueryEngineTool.from_defaults(
        query_engine,
        #name="YC 创始人的个人经理",
        #description="关于YC创始人Paul Graham的RAG引擎",
    )

    to_html_tool = FunctionTool.from_defaults(fn=toHtml)

    agent = ReActAgent.from_tools(
        [to_html_tool,yc_tool],
        verbose=True,
    )

    # 执行查询
    logger.info("agent 查询结果:")
    response = agent.chat(query)

    print(response)

    logger.info("\n查询完成")


def main():
    run_glm4_query_with_embeddings("请描述一下作者的求学经历,并将英文用html高亮显示")


if __name__ == "__main__":
    main()

可以看到,yc_tool 是我们通过加载本地文件自定义的一个RAG 应用,然后我们把它加入到 agent的 from tools中。

以下是加入 RAG 以后的 agent 的输出

INFO:llamaindex_demo.config:agent 查询结果:
> Running step 28bfc6bb-5e8a-4d5b-8273-36791b222a90. Step input: 请描述一下作者的求学经历,并将英文用html高亮显示
INFO:httpx:HTTP Request: POST https://open.bigmodel.cn/api/paas/v4/chat/completions "HTTP/1.1 200 OK"
Thought: The current language of the user is: Chinese. I need to use a tool to help me answer the question.
Action: query_engine_tool
Action Input: {'input': '请描述一下作者的求学经历'}
INFO:httpx:HTTP Request: POST https://open.bigmodel.cn/api/paas/v4/embeddings "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST https://open.bigmodel.cn/api/paas/v4/chat/completions "HTTP/1.1 200 OK"
Observation: 作者的求学经历可以概括如下:

1. **研究生阶段**:
   - 作者最初在研究生院学习,但似乎对所选主题(连续性应用程序)并不满意,认为应该选择更有探索空间的宏和嵌入式语言。
   - 作者的主要目标是尽快完成学业,摆脱研究生院。

2. **申请艺术学校**:
   - 在研究生期间,作者同时申请了美国的罗德岛设计学院(RISD)和意大利佛罗伦萨的Accademia di Belli Arti。
   - RISD录取了作者,而Accademia di Belli Arti的回复因邮寄错误延迟。

3. **在RISD的学习**:
   - 作者被RISD视为转学二年级生,需要在夏季完成基础课程,包括绘画、色彩和设计等。
   - 尽管如此,作者在基础课程中表现不错。

......

总体来说,作者的求学经历充满了探索和转变,从研究生院的学术研究到艺术学校的实践学习,再到通过个人项目和工作的经济独立,展现了其对知识和职业发展的不断追求和反思。
> Running step c8f8eee2-ab9b-45c9-a05e-6136f688cae3. Step input: None
INFO:httpx:HTTP Request: POST https://open.bigmodel.cn/api/paas/v4/chat/completions "HTTP/1.1 200 OK"
Thought: The current language of the user is: Chinese. I need to use a tool to help me highlight the English words in the provided text.
Action: toHtml
Action Input: {'text': '作者的求学经历可以概括如下:\n\n1. **研究生阶段**:\n   - 作者最初在研究生院学习,但似乎对所选主题(连续性应用程序)并不满意,认为应该选择更有探索空间的宏和嵌入式语言。\n   - 作者的主要目标是尽快完成学业,摆脱研究生院。\n\n2. **申请艺术学校**:\n   - 在研究生期间,作者同时申请了美国的罗德岛设计学院(RISD)和意大利佛罗伦萨的Accademia di Belli Arti。\n   - RISD录取了作者,而Accademia di Belli Arti的回复因邮寄错误延迟。\n\n3. **在RISD的学习**:\n   - 作者被RISD视为转学二年级生,需要在夏季完成基础课程,包括绘画、色彩和设计等。\n   - 尽管如此,作者在基础课程中表现不错。\n\n4. **意外的Accademia邀请**:\n   - 夏末时,作者意外收到Accademia的入学考试邀请,决定前往佛罗伦萨。\n   - 作者通过节俭生活和之前的咨询工作积蓄,勉强支付了生活和学习的费用。\n\n5. **在Accademia的学习**:\n   - 作者发现Accademia的绘画系存在一种默契,即学生和教职员工互不干涉,维持着19世纪工作室的传统。\n   - 这种教育模式让作者感到失望。\n\n6. **秘密项目与经济独立**:\n   - 在Accademia期间,作者秘密从事《论Lisp》的工作,并获得了出版合同和一笔可观的收入。\n   - 这笔收入帮助作者还清了大学贷款,并积攒了回RISD的费用。\n\n7. **在Interleaf的工作经历**:\n   - 作者在Interleaf公司学到了许多关于科技公司的管理和技术开发的见解。\n   - 最重要的是,作者领悟到“低端吞噬高端”的市场策略。\n\n8. **回到RISD**:\n   - 作者在秋季回到RISD继续学习,发现真正的艺术学校与Accademia没有太大不同。\n   - 作者观察到绘画系的教育相对松散,而其他系如纺织、插画和建筑则更为严格。\n\n总体来说,作者的求学经历充满了探索和转变,从研究生院的学术研究到艺术学校的实践学习,再到通过个人项目和工作的经济独立,展现了其对知识和职业发展的不断追求和反思。'}
Observation: 作者的求学经历可以概括如下:

1. **研究生阶段**:
   - 作者最初在研究生院学习,但似乎对所选主题(连续性应用程序)并不满意,认为应该选择更有探索空间的宏和嵌入式语言。
   - 作者的主要目标是尽快完成学业,摆脱研究生院。

2. **申请艺术学校**:
   - 在研究生期间,作者同时申请了美国的罗德岛设计学院(<span style="color:red;">RISD</span>)和意大利佛罗伦萨的Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> <span style="color:red;">Arti</span>。
   - RISD录取了作者,而Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> Arti的回复因邮寄错误延迟。

3. **在RISD的学习**:
   - 作者被RISD视为转学二年级生,需要在夏季完成基础课程,包括绘画、色彩和设计等。
   - 尽管如此,作者在基础课程中表现不错。

......

总体来说,作者的求学经历充满了探索和转变,从研究生院的学术研究到艺术学校的实践学习,再到通过个人项目和工作的经济独立,展现了其对知识和职业发展的不断追求和反思。
> Running step 649456af-f363-47c9-bd06-3c36d1ff48c6. Step input: None
INFO:httpx:HTTP Request: POST https://open.bigmodel.cn/api/paas/v4/chat/completions "HTTP/1.1 200 OK"
Thought: I can answer without using any more tools. I'll use the user's language to answer.
Answer: 作者的求学经历可以概括如下:

1. **研究生阶段**:
   - 作者最初在研究生院学习,但似乎对所选主题(连续性应用程序)并不满意,认为应该选择更有探索空间的宏和嵌入式语言。
   - 作者的主要目标是尽快完成学业,摆脱研究生院。

2. **申请艺术学校**:
   - 在研究生期间,作者同时申请了美国的罗德岛设计学院(<span style="color:red;">RISD</span>)和意大利佛罗伦萨的Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> <span style="color:red;">Arti</span>。
   - RISD录取了作者,而Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> Arti的回复因邮寄错误延迟。

3. **在RISD的学习**:
   - 作者被RISD视为转学二年级生,需要在夏季完成基础课程,包括绘画、色彩和设计等。
   - 尽管如此,作者在基础课程中表现不错。

......

总体来说,作者的求学经历充满了探索和转变,从研究生院的学术研究到艺术学校的实践学习,再到通过个人项目和工作的经济独立,展现了其对知识和职业发展的不断追求和反思。
作者的求学经历可以概括如下:

1. **研究生阶段**:
   - 作者最初在研究生院学习,但似乎对所选主题(连续性应用程序)并不满意,认为应该选择更有探索空间的宏和嵌入式语言。
   - 作者的主要目标是尽快完成学业,摆脱研究生院。

2. **申请艺术学校**:
   - 在研究生期间,作者同时申请了美国的罗德岛设计学院(<span style="color:red;">RISD</span>)和意大利佛罗伦萨的Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> <span style="color:red;">Arti</span>。
   - RISD录取了作者,而Accademia <span style="color:red;">di</span> <span style="color:red;">Belli</span> Arti的回复因邮寄错误延迟。

3. **在RISD的学习**:
   - 作者被RISD视为转学二年级生,需要在夏季完成基础课程,包括绘画、色彩和设计等。
   - 尽管如此,作者在基础课程中表现不错。

......

总体来说,作者的求学经历充满了探索和转变,从研究生院的学术研究到艺术学校的实践学习,再到通过个人项目和工作的经济独立,展现了其对知识和职业发展的不断追求和反思。
INFO:llamaindex_demo.config:
查询完成

可以看出,在它的思考过程中,应用了我们的 toHtml 方法,将英文单词就用html标签包裹。

agent 记忆

agent 还有记忆的功能,也就是说之前问过的问题,它会记得,比如我们在一个流程中发起了多次提问

agent 不会把每次的提问当做一个独立提问,它是知道上下文的,这就是它的记忆功能。