LangChain+通义千问+AnalyticDB向量引擎保姆级教程 - 阿里技术

阿里妹导读

本文以构建AIGC落地应用ChatBot和构建AI Agent为例,从代码级别详细分享AI框架LangChain、阿里云通义大模型和AnalyticDB向量引擎的开发经验和最佳实践,给大家快速落地AIGC应用提供参考。

前言
9月13日,通义千问大模型已首批通过备案,并正式向公众开放。通义模型具备的能力包括:1.创作文字,如写故事、写公文、写邮件、写剧本、写诗歌等;2.编写代码;3.提供各类语言的翻译服务,如英语、日语、法语、西班牙语等;4.进行文本润色和文本摘要等工作;5.扮演角色进行对话;6.制作图表等。我们在可以登录通义千问官网体验的同时,也可以充分发挥想象力通过调用通义千问API的方式来构建属于自己的AI应用了。

如果直接使用通义千问API从0到1来构建应用,技术成本还是相对比较高的。幸运的是,当前已经有非常优秀的框架LangChain来串联AIGC相关的各类组件,让我们轻松构建自己的应用。由于业务上对客户支持的需要,我在几个月前已经在LangChain模块中添加了调用通义千问API的模块代码。在这个时间点,刚好可以直接拿来使用。

在过去的一段时间,已经有很多同学分享了LangChain的框架和原理,本文则从实际开发角度出发,以构建应用过程中遇到的问题,和我们实际遇到的客户案例出发,来详细讲解LangChain的代码,希望给大家在基于通义API构建应用入门时提供一些启发和思路。本文主要包括几个部分:
1)LangChain的简单介绍 。
2)LangChain的源码解读,以通义千问API调用为例 。
3.)学习和构建一些基于不同Chain的小应用Demo,比如基于通义和向量数据库的ChatBot;构建每日金融资讯收集和分析的AI Agent。
4)如何提高大模型的问答准确率,比如如何更好地处理现有数据,如何使用思维链能力提升Agent的实际思考能力等。

LangChain是什么

LangChain是一个基于语言模型开发应用程序的框架。其通过串联开发应用需要的各个模块和组件,简化和加速程序的构建和开发。

LangChain模块

LLM模块
提供统一的大语言模型调用接口,屏蔽各种大语言模型因调用方式和实现细节的不同带来的开发复杂度。比如OpenAI和Tongyi模块。实现一个LLM模块需要实现LLM基类的call和generate接口。

class LLM(BaseLLM): 
    def _call( 
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None, 
        run_manager: Optional[CallbackManagerForLLMRun] = None, 
        **kwargs: Any, 
    ) -> str: 
        """Run the LLM on the given prompt and input.""" 
  def _generate( 
        self, 
        prompts: List[str], 
        stop: Optional[List[str]] = None, 
        run_manager: Optional[CallbackManagerForLLMRun] = None, 
        **kwargs: Any, 
    ) -> LLMResult: 
        """Run the LLM on the given prompt and input."""

Embedding模块
提供统一的embedding能力接口,与LLM一样,也提供不同的厂商实现,比如OpenAIEmbeddings,DashScopeEmbeddings。同样需要集成和实现Embeddings基类的两个方法embed_documents和embed_query。

class Embeddings(ABC): 
    """Interface for embedding models.""" 
    @abstractmethod 
    def embed_documents(self, texts: List[str]) -> List[List[float]]: 
        """Embed search docs.""" 
    @abstractmethod 
    def embed_query(self, text: str) -> List[float]:

VectorStore模块
向量存储模块,用于存储由Embedding模块生成的向量和生产向量的数据,主要作为记忆和检索模块向LLM提供服务。比如AnalytiDB VectorStore模块。实现VectorStore模块主要需要实现几个写入和查询接口。

class VectorStore(ABC): 
    """Interface for vector store.""" 
    @abstractmethod 
    def add_texts( 
        self, 
        texts: Iterable[str], 
        metadatas: Optional[List[dict]] = None, 
        **kwargs: Any, 
    ) -> List[str]: 
    def search(self, query: str, search_type: str, **kwargs: Any) -> List[Document]:

Chain模块 用于串联上面的这些模块,使得调用更加简单,让用户不需要关心繁琐的调用链路,在LangChain中已经集成了很多chain,最主要的就是LLMChain,在其内部根据不同的场景定义和使用了不同的PromptTemplate来达到目标。

Agents模块 和chain类似,提供了丰富的agent模版,用于实现不同的agent,后面会详细介绍。

还有一些模块比如indexes,retrievers等都是上面这些模块的变种,以及提供一些可调用的工具类,比如tools等。这里就不再详细展开。我们会在后面的案例中讲解如何使用这些模块来构建自己的应用。

应用案例

构建ChatBot

ChatBot是LLM应用的一个比较典型的场景,这个场景又可以细分为问答助手(知识库),智能客服,Copilot等。比较典型的案例是LangChain-chatchat.构建ChatBot主要需要以下模块:

TextSplitter一篇文档的内容往往篇幅较长,由于LLM和Embedding token限制,无法将其全部传给LLM,因此将需要存储的文档按照一定的规则切分成内聚的小块chunk进行存储。

LLM模块 用于总结问题和回答问题。

Embedding模块 用于生产知识和问题的向量表示。

VectorStore模块
用于存储和检索匹配的本地知识内容。

一个比较清晰的调用链路图如下(比较经典清晰,老图借用):

Example

基于通义API和ADB-PG向量数据库的ChatBot

首先我们从Google拉取一些问答数据,然后调用Dashscope上的Embedding模型进行向量化,并写入AnalyticDB PostgreSQL。

import os 
import json 
import wget 
from langchain.vectorstores.analyticdb import AnalyticDB 
CONNECTION_STRING = AnalyticDB.connection_string_from_db_params( 
    driver=os.environ.get("PG_DRIVER", "psycopg2cffi"), 
    host=os.environ.get("PG_HOST", "localhost"), 
    port=int(os.environ.get("PG_PORT", "5432")), 
    database=os.environ.get("PG_DATABASE", "postgres"), 
    user=os.environ.get("PG_USER", "postgres"), 
    password=os.environ.get("PG_PASSWORD", "postgres"), 
) 
# All the examples come from https://ai.google.com/research/NaturalQuestions 
# This is a sample of the training set that we download and extract for some 
# further processing. 
wget.download("https://storage.googleapis.com/dataset-natural-questions/questions.json") 
wget.download("https://storage.googleapis.com/dataset-natural-questions/answers.json") 
# 导入数据 
with open("questions.json", "r") as fp: 
    questions = json.load(fp) 
with open("answers.json", "r") as fp: 
    answers = json.load(fp) 
from langchain.vectorstores import AnalyticDB 
from langchain.embeddings import DashScopeEmbeddings 
from langchain import VectorDBQA, OpenAI 
embeddings = DashScopeEmbeddings( 
    model="text-embedding-v1", dashscope_api_key="your-dashscope-api-key" 
) 
doc_store = AnalyticDB.from_texts( 
    texts=answers, embedding=embeddings, connection_string=CONNECTION_STRING, 
    pre_delete_collection=True, 
)

然后创建LangChain内集成的tongyi模块。

from langchain.chains import RetrievalQA 
from langchain.llms import Tongyi 
os.environ["DASHSCOPE_API_KEY"] = "your-dashscope-api-key" 
llm = Tongyi()

查询和检索数据,然后回答问题。

from langchain.prompts import PromptTemplate 
custom_prompt = """ 
Use the following pieces of context to answer the question at the end. Please provide 
a short single-sentence summary answer only. If you don't know the answer or if it's 
not present in given context, don't try to make up an answer, but suggest me a random 
unrelated song title I could listen to. 
Context: {context} 
Question: {question} 
Helpful Answer: 
""" 
custom_prompt_template = PromptTemplate( 
    template=custom_prompt, input_variables=["context", "question"] 
custom_qa = VectorDBQA.from_chain_type( 
    llm=llm, 
    chain_type="stuff", 
    vectorstore=doc_store, 
    return_source_documents=False, 
    chain_type_kwargs={"prompt": custom_prompt_template}, 
) 
random.seed(41) 
for question in random.choices(questions, k=5): 
    print(">", question) 
    print(custom_qa.run(question), end="nn")
> what was uncle jesse's original last name on full house 
Uncle Jesse's original last name on Full House was Cochran. 
> when did the volcano erupt in indonesia 2018 
No information about a volcano erupting in Indonesia in 2018 is present in the given context. Suggested song title: "Volcano" by U2. 
> what does a dualist way of thinking mean 
A dualist way of thinking means believing that humans possess a non-physical mind or soul which is distinct from their physical body. 
> the first civil service commission in india was set up on the basis of recommendation of 
The first Civil Service Commission in India was not set up on the basis of a recommendation. 
> how old do you have to be to get a tattoo in utah 
In Utah, you must be at least 18 years old to get a tattoo.

问题和挑战

在我们实际给用户提供构建一站式ChatBot的过程中,我们依然遇到了很多问题,比如文本切分过碎,导致语义丢失,文本包含图表,切分后导致段落无法被理解等。

  1. 文本切分器 向量的匹配度直接影响召回率,而向量的召回率又和内容本身以及问题紧密联系在一起,哪怕有一个很强大的embedding模型,如果文本切分本身做的不好,也无法达到用户的预期效果。比如LangChain本身提供的CharacterTextSplitter,其会根据标点符号和换行符等来切分段落,在一些多级标题的场景下,小标题会被切分成单独的chunk,与正文分割开,导致被切分的标题和正文都无法很内聚地表达需要表达的内容。
  2. 优化切分长度,过长的chunk会导致在召回后达到token限制,过小的chunk又可能丢失想要找到的关键信息。我们尝试过很多切分策略,发现如果不做深度的优化,将文本直接按照200-500个token长度来切分反而效果比较好。
  3. 召回优化1. 回溯上下文,在某些场景,我们能够准确地召回内容,但是这部分内容并不全,因此我们可以在写入时为chunk按照文章级别构建id,在召回时额外召回最相关chunk的相邻chunk,随后做拼接。
  4. 召回优化2. 构建标题树,在富文本场景,用户非常喜欢使用多级标题,有些文本内容在脱离标题之后就无法了解其究竟在说什么,这时我们可以通过构建内容标题树的方式来优化chunk.比如把chunk按照下面的方式构建。
    #大标题1-中标题1-小标题1#:内容1 
    #大标题1-中标题1-小标题1#:内容2 
    #大标题1-中标题1-小标题2#:内容1 
    #大标题2-中标题1-小标题1#:内容1
  5. 双路召回,纯向量召回有时候会因为对专有名词的不理解导致无法召回相关内容,这时可以考虑使用向量和全文检索进行双路召回,在召回后再做精排去重。在全文检索时,我们可以通过额外增加自定义专有名词库和虚词屏蔽的方式来进一步优化召回效果。
  6. 问题优化,有时候用户的问题本身并不适合做向量匹配,这时我们可以根据聊天历史让模型来总结独立问题,来提升召回率,提高回答准确度。

虽然我们做了很多优化,但是由于用户的文档本身五花八门,现在依然无法找到一个完全通用的方案来应对所有的数据源.比如某一切分器在markdown场景表现很好,但是对于pdf就效果下降得厉害。比如有的用户还要求能够在召回文本的同时召回图片,视频甚至ppt的slice.目前我们也只是通过metadata link的方式召回相关内容,而不是把相关内容直接做向量化。如果有同学有很好的办法,欢迎在评论区交流。

构建AI Agent

以LLM构建AI Agent是大语言模型的另一个典型的应用场景。一些开源的非常火热的项目,如AutoGPT、BabyAGI都是非常典型的示例。让我们明白LLM的潜力不仅限于生成写作精彩的文本、故事、文章等;它可以被视为一个强大的自我决策的系统。用AI做决策存在一定的风险,但在一些简单,只是处理繁琐工作的场景,让AI代替人工决策是可取的。

Agent System组成

在以LLM为核心的自主代理系统中,LLM是Agent的大脑,我们还需要一些其他的组件来补全它的四肢。AI Agent主要借助思维链和思维树的思想,提高Agent的思考和决策能力。

Planning

planning的作用有两个:

  1. 进行子任务的设定和拆解
    : 实际生活中的任务往往是复杂的,需要将大任务分解为更小、可管理的子目标,从而能够有效处理复杂任务。
  2. 进行自我反思和迭代: 通过对过去的行动进行自我批评和反思,从错误中学习并为将来的步骤进行完善,从而提高最终结果的质量。

Memory

短期记忆:将所有上下文学习(参见提示工程)视为利用模型的短期记忆来学习。

长期记忆:这为代理提供了在长时间内保留和检索(无限)信息的能力,通常通过利用外部向量存储和快速检索来实现。

Tools

Tools模块可以让Agent调用外部API以获取模型权重中缺失的额外信息(通常在预训练后难以更改),包括实时信息、代码执行能力、访问专有信息源等。通常是通过设计API的方式让LLM调用执行。

Planning模块

一个复杂的任务通常包括许多步骤。代理需要知道这些步骤并提前规划。

任务拆解

思维链(Chain of thought) (CoT; Wei et al. 2022)已经成为提高模型在复杂任务上性能的标准提示技术。模型被指示“逐步思考”,以利用更多的测试时间计算来将困难任务分解成更小更简单的步骤。CoT将大任务转化为多个可管理的任务,并揭示了模型思考过程的解释。

思维树(Tree of Thoughts)
(Yao et al. 2023) 通过在每一步探索多种推理可能性来扩展了CoT。它首先将问题分解为多个思维步骤,并在每一步生成多种思考,创建一个树状结构。搜索过程可以是广度优先搜索(BFS)或深度优先搜索(DFS),每个状态都由分类器(通过提示)或多数投票进行评估。

任务拆解可以通过以下方式完成:(1)LLM使用简单的提示,如“完成任务X需要a、b、c的步骤。n1。”,“实现任务X的子目标是什么?”,(2)使用任务特定的指令;例如,“撰写文案大纲。”,或者(3)通过交互式输入指定需要操作的步骤。

自我反思(Self-Reflection)是一个非常重要的思想,它允许Agent通过改进过去的行动决策和纠正以前错误的方式来不断提高。在可以允许犯错和试错的现实任务中,它发挥着关键作用。比如写一段某个用途的脚本代码。

ReAct
(Yao et al. 2023)通过将行动空间扩展为任务特定的离散行动和语言空间的组合,将推理和行动整合到LLM中。前者使LLM能够与环境互动(例如使用搜索引擎API),而后者促使LLM生成自然语言中的推理轨迹。

ReAct的prompt template包含了明确的步骤,供LLM思考,大致格式如下:

Thought: ... 
Action: ... 
Observation: ... 
... (Repeated many times)

在对知识密集型任务和决策任务的两个实验中,ReAct都表现比仅包含行动(省略了“思考:…”步骤)更好的回答效果。

Memory模块

记忆可以定义为用于获取、存储、保留和以后检索信息的过程。对于人类大脑,有几种类型的记忆。

感觉记忆:这是记忆的最早阶段,它使我们能够在原始刺激结束后保留感觉信息(视觉、听觉等)的能力。感觉记忆通常只持续几秒钟。子类别包括图像记忆(视觉)、声音记忆(听觉)和触觉记忆(触觉)。

短期记忆(Short-Term Memory):它存储我们当前意识到的信息,需要执行复杂的认知任务,如学习和推理。短期记忆的容量被认为约为7个项目(Miller 1956),持续时间为20-30秒。

长期记忆(Long-Term Memory)
:长期记忆可以存储信息很长时间,范围从几天到数十年,具有本质上无限的存储容量。长期记忆有两个子类型:

显式/陈述性记忆:这是关于事实和事件的记忆,指的是那些可以有意识地回忆起来的记忆,包括情节记忆(事件和经历)和语义记忆(事实和概念)。

隐式/程序性记忆:这种记忆是无意识的,涉及自动执行的技能和例行程序,如骑自行车,在键盘上打字等。

我们可以粗略地考虑以下映射关系:

感觉记忆是为原始输入内容(包括文本、图像或其他模态),其可以在embedding之后作为输入。

短期记忆就像上下文内容,也就是聊天历史,它是短暂而有限的,因为受到Token长度的限制。

长期记忆就像Agent可以在查询时参考的外部向量存储,可以通过快速检索访问。

外部存储可以缓解有限注意力跨度的限制。一个标准的做法是将信息的嵌入表示保存到一个向量存储数据库中,该数据库可以支持快速的最大内积搜索(Maximum Inner Product Search)。为了优化检索速度,常见的选择是使用近似最近邻(ANN)算法,以返回近似的前k个最近邻,可以在略微损失一些准确性的情况下获得巨大的速度提升。对于相似性算法有兴趣的同学可以阅读这篇文章《ChatGPT都推荐的向量数据库,不仅仅是向量索引》。

Tool模块

使用工具可以使LLM完成一些其本身不能直接完成的事情。

Modular Reasoning, Knowledge and Language
(Karpas et al. 2022)提出了一个MRKL系统,包含一组专家模块,通用的LLM作为路由器,将查询路由到最合适的专家模块。这些模块可以是其他模型(文生图,领域模型等)或功能模块(例如数学计算器、货币转换器、天气API)。现在最典型的方式就是使用ChatGPT的function call功能。通过对ChatGPT注册和描述接口的含义,就可以让ChatGPT帮我们调用对应的接口,返回正确的答案。

典型案例-AUTOGPT

autogpt通过类似下面的prompt可以成功完成一些复杂的任务,比如review开源项目的代码,给开源项目代码写注释。最近看到了Aone Copilot,其主要focus在代码补全和代码问答两个场景。那么如果我们可以调用Aone Copilot的API,是否也可以在我们推送mr之后,让agent帮我们完成一些代码风格,语法校验的代码review工作,和单元测试用例编写工作。

You are {{ai-name}}, {{user-provided AI bot description}}. 
Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications. 
GOALS: 
1. {{user-provided goal 1}} 
2. {{user-provided goal 2}} 
3. ... 
4. ... 
5. ... 
Constraints: 
1. ~4000 word limit for short term memory. Your short term memory is short, so immediately save important information to files. 
2. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember. 
3. No user assistance 
4. Exclusively use the commands listed in double quotes e.g. "command name" 
5. Use subprocesses for commands that will not terminate within a few minutes 
Commands: 
1. Google Search: "google", args: "input": "<search>" 
2. Browse Website: "browse_website", args: "url": "<url>", "question": "<what_you_want_to_find_on_website>" 
3. Start GPT Agent: "start_agent", args: "name": "<name>", "task": "<short_task_desc>", "prompt": "<prompt>" 
4. Message GPT Agent: "message_agent", args: "key": "<key>", "message": "<message>" 
5. List GPT Agents: "list_agents", args: 
6. Delete GPT Agent: "delete_agent", args: "key": "<key>" 
7. Clone Repository: "clone_repository", args: "repository_url": "<url>", "clone_path": "<directory>" 
8. Write to file: "write_to_file", args: "file": "<file>", "text": "<text>" 
9. Read file: "read_file", args: "file": "<file>" 
10. Append to file: "append_to_file", args: "file": "<file>", "text": "<text>" 
11. Delete file: "delete_file", args: "file": "<file>" 
12. Search Files: "search_files", args: "directory": "<directory>" 
13. Analyze Code: "analyze_code", args: "code": "<full_code_string>" 
14. Get Improved Code: "improve_code", args: "suggestions": "<list_of_suggestions>", "code": "<full_code_string>" 
15. Write Tests: "write_tests", args: "code": "<full_code_string>", "focus": "<list_of_focus_areas>" 
16. Execute Python File: "execute_python_file", args: "file": "<file>" 
17. Generate Image: "generate_image", args: "prompt": "<prompt>" 
18. Send Tweet: "send_tweet", args: "text": "<text>" 
19. Do Nothing: "do_nothing", args: 
20. Task Complete (Shutdown): "task_complete", args: "reason": "<reason>" 
Resources: 
1. Internet access for searches and information gathering. 
2. Long Term memory management. 
3. GPT-3.5 powered Agents for delegation of simple tasks. 
4. File output. 
Performance Evaluation: 
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities. 
2. Constructively self-criticize your big-picture behavior constantly. 
3. Reflect on past decisions and strategies to refine your approach. 
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps. 
You should only respond in JSON format as described below 
Response Format: 
{ 
    "thoughts": { 
        "text": "thought", 
        "reasoning": "reasoning", 
        "plan": "- short bulletedn- list that conveysn- long-term plan", 
        "criticism": "constructive self-criticism", 
        "speak": "thoughts summary to say to user" 
    }, 
    "command": { 
        "name": "command name", 
        "args": { 
            "arg name": "value" 
        } 
    } 
} 
Ensure the response can be parsed by Python json.loads

LangChain Agent模块

LangChain已经内置了很多agent实现的框架模块,主要包含:

agent_toolkits

这个模块目前是实验性的,其目的是为了模拟代替甚至超越ChatGPT plugin的能力,通过提供一系列的工具集提供链式调用,来让用户组装自己的workflow.比较典型的包括发送邮件功能, 执行python代码,执行用户提供的sql,调用zapier api等。

toolkits主要通过注册机制向agent返回一系列可以调用的tool。其基类代码为BaseToolkit。

class BaseToolkit(BaseModel, ABC): 
    """Base Toolkit representing a collection of related tools.""" 
    @abstractmethod 
    def get_tools(self) -> List[BaseTool]: 
        """Get the tools in the toolkit."""

我们可以通过继承BaseToolkit的方式来实现不同的toolkit,每一个toolkit都会实现一系列的tools,一个Tool则包含几个部分,必须要包含的内容有name,description。通过这几个字段来告知LLM这个工具的作用和调用方法,这里就要求注册的tool最好能够通过name明确表达其用途,同时也可以在description中增加few-shot来做调用example,使得LLM能够更好地理解tool。同时在LangChain内部已经集成了很多工具,我们可以直接调用这些工具来组成Tools。

class BaseTool(BaseModel, Runnable[Union[str, Dict], Any]): 
    name: str 
    """The unique name of the tool that clearly communicates its purpose.""" 

    description: str 
    """Used to tell the model how/when/why to use the tool. 
    You can provide few-shot examples as a part of the description. 
    """ 
    ... 
class Tool(BaseTool): 
    """Tool that takes in function or coroutine directly.""" 
    description: str = "" 
    func: Optional[Callable[..., str]] 
    """The function to run when the tool is called."""

Example1 Calculate Agent

接下来我们做一个简单的agent demo,这个agent主要做两件事情。1. 从网上检索收集问题需要的数据 2.利用收集到的数据进行科学计算,回答用户的问题。在这个流程中,我们主要用到Search和Calculator两个工具。

from langchain.agents import initialize_agent, AgentType, Tool 
from langchain.chains import LLMMathChain 
from langchain.chat_models import ChatOpenAI 
from langchain.llms import OpenAI 
from langchain.utilities import SerpAPIWrapper 
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613") 
search = SerpAPIWrapper() 
llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=True) 
tools = [ 
    Tool( 
        name = "Search", 
        func=search.run, 
        description="useful for when you need to answer questions about current events. You should ask targeted questions" 
    ), 
    Tool( 
        name="Calculator", 
        func=llm_math_chain.run, 
        description="useful for when you need to answer questions about math" 
    ) 
] 
agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS, verbose=True) 
agent.run("Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?")
> Entering new  chain... 

    Invoking: `Search` with `{'query': 'Leo DiCaprio girlfriend'}` 

    Amidst his casual romance with Gigi, Leo allegedly entered a relationship with 19-year old model, Eden Polani, in February 2023. 
    Invoking: `Calculator` with `{'expression': '19^0.43'}` 

    > Entering new  chain... 
19^0.43
```text 
19**0.43 </code></pre>
<pre><code>...numexpr.evaluate("19**0.43")... 

Answer: 3.547023357958959 
> Finished chain. 
Answer: 3.547023357958959Leo DiCaprio&#039;s girlfriend is reportedly Eden Polani. Her current age raised to the power of 0.43 is approximately 3.55. 

> Finished chain. 
"Leo DiCaprio&#039;s girlfriend is reportedly Eden Polani. Her current age raised to the power of 0.43 is approximately 3.55."</code></pre>
<pre><code>
可以看到,这个agent可以成功地完成意图检索寻求知识和科学计算得到结果。

#### **Example2 SQL Agent**

这个case是结合大模型和数据库,通过查询表里的数据来回答用户问题,用的关键prompt为
```sql
_postgres_prompt = """You are a PostgreSQL expert. Given an input question, first create a syntactically correct PostgreSQL query to run, then look at the results of the query and return the answer to the input question. 
Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the LIMIT clause as per PostgreSQL. You can order the results to return the most informative data in the database. 
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (") to denote them as delimited identifiers. 
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table. 
Pay attention to use CURRENT_DATE function to get the current date, if the question involves "today". 
Use the following format: 
Question: Question here 
SQLQuery: SQL Query to run 
SQLResult: Result of the SQLQuery 
Answer: Final answer here 
"""

下面是实际的工作代码,目前在这个场景,openai的推理能力最强,能够正确完成这个复杂的Agent工作。

## export your openai key first export OPENAI_API_KEY=sk-xxxxx 
from langchain.agents import create_sql_agent 
from langchain.agents.agent_toolkits import SQLDatabaseToolkit 
from langchain.agents import AgentExecutor 
from langchain.llms.tongyi import Tongyi 
from langchain.sql_database import SQLDatabase 
import psycopg2cffi as psycopg2 # pip install psycopg-binary if on linux, just use psycopg2 
from langchain.chat_models import ChatOpenAI 
db = SQLDatabase.from_uri('postgresql+psycopg2cffi://admin:password123@localhost/admin') 
llm = ChatOpenAI(model_name="gpt-3.5-turbo") 
toolkit = SQLDatabaseToolkit(db=db,llm=llm) 
agent_executor = create_sql_agent( 
    llm=llm, 
    toolkit=toolkit, 
    verbose=True 
) 
agent_executor.run("using the teachers table, find the first_name and last name of teachers who earn less the mean salary?")

可以看到大模型经过多轮思考,正确回答了我们的问题。

> Entering new AgentExecutor chain... 
Action: sql_db_list_tables 
Action Input: "" 
Observation: teachers 
Thought:I can query the "teachers" table to find the first_name and last_name columns. 
Action: sql_db_schema 
Action Input: "teachers" 
Observation:  
CREATE TABLE teachers ( 
        id INTEGER,  
        first_name VARCHAR(25),  
        last_name VARCHAR(50),  
        school VARCHAR(50),  
        hire_data DATE,  
        salary NUMERIC 
) 
/* 
3 rows from teachers table: 
id      first_name      last_name       school  hire_data       salary 
None    Janet   Smith   F.D. Roosevelt HS       2011-10-30      36200 
None    Lee     Reynolds        F.D. Roosevelt HS       1993-05-22      65000 
None    Samuel  Cole    Myers Middle School     2005-08-01      43500 
*/ 
Thought:I can now construct a query to find the first_name and last_name of teachers who earn less than the mean salary. 
Action: sql_db_query 
Action Input: "SELECT first_name, last_name FROM teachers WHERE salary < (SELECT AVG(salary) FROM teachers) LIMIT 10" 
Observation: [('Janet', 'Smith'), ('Samuel', 'Cole'), ('Samantha', 'Bush'), ('Betty', 'Diaz'), ('Kathleen', 'Roush')] 
Thought:Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised RateLimitError: Rate limit reached for default-gpt-3.5-turbo in organization org-FDYSniIsv0FIQBi9p4P9Dinn on requests per min. Limit: 3 / min. Please try again in 20s. Contact us through our help center at help.openai.com if you continue to have issues. Please add a payment method to your account to increase your rate limit. Visit https://platform.openai.com/account/billing to add a payment method.. 
Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised RateLimitError: Rate limit reached for default-gpt-3.5-turbo in organization org-FDYSniIsv0FIQBi9p4P9Dinn on requests per min. Limit: 3 / min. Please try again in 20s. Contact us through our help center at help.openai.com if you continue to have issues. Please add a payment method to your account to increase your rate limit. Visit https://platform.openai.com/account/billing to add a payment method.. 
Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised RateLimitError: Rate limit reached for default-gpt-3.5-turbo in organization org-FDYSniIsv0FIQBi9p4P9Dinn on requests per min. Limit: 3 / min. Please try again in 20s. Contact us through our help center at help.openai.com if you continue to have issues. Please add a payment method to your account to increase your rate limit. Visit https://platform.openai.com/account/billing to add a payment method.. 
Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 8.0 seconds as it raised RateLimitError: Rate limit reached for default-gpt-3.5-turbo in organization org-FDYSniIsv0FIQBi9p4P9Dinn on requests per min. Limit: 3 / min. Please try again in 20s. Contact us through our help center at help.openai.com if you continue to have issues. Please add a payment method to your account to increase your rate limit. Visit https://platform.openai.com/account/billing to add a payment method.. 
The first_name and last_name of teachers who earn less than the mean salary are Janet Smith, Samuel Cole, Samantha Bush, Betty Diaz, and Kathleen Roush. 
Final Answer: Janet Smith, Samuel Cole, Samantha Bush, Betty Diaz, Kathleen Roush 
> Finished chain. 
'Janet Smith, Samuel Cole, Samantha Bush, Betty Diaz, Kathleen Roush'

问题和挑战

和ChatBot不同,agent的构建是对LLM的推理能力提出了更高的要求。ChatBot的回答可能是不正确的,但这依然可以通过人类的判别回馈来确定问答结果是否有益,对于无效的回答可以容忍地直接忽略或者重新回答。 但是agent对模型的错误判断的容忍度则更低。虽然我们可以通过自我反思机制减少agent的出错率,但是其当前可以应用的场景依然较小。需要我们不断去探索和开拓新的场景,同时不断提高大模型的推理能力,从而能够搭建更加复杂的agent。

同时,agent目前能够在比较小的场景胜任工作,比如我们的意图是明确的,同时也只给agent提供了比较少量的toolkit来执行任务(10个以内),且每个tool的用差异明显,在这种情况下,LLM能够正确选择tool进行任务,并得到期望的结果。但是当一个agent里注册了上百个甚至更多工具时,LLM就可能无法正确地选择tool执行操作了。这里的一个解法是通过多层agent树的方式来解决,父agent负责路由分发任务给不同的子agent。每一个子agent则仅仅包含和使用有限的toolkit来执行任务,从而提高agent复杂场景的任务完成率。

快来关注

云原生数据仓库 AnalyticDB 是一种大规模并行处理数据仓库服务,可提供海量数据在线分析服务。在云原生数据仓库能力上全自研企业级向量引擎,支持流式向量数据写入、百亿级向量数据检索;支持结构化数据分析、向量检索和全文检索多路召回,支持对接通义千问等国内外主流大模型。

了解更多 AnalyticDB 介绍和相关解决方案请参考:

AnalyticDB向量引擎介绍:https://www.aliyun.com/activity/database/adbpg_vector

一键部署PAI+通义千问+AnalyticDB向量引擎搭建ChatBot:
https://computenest.console.aliyun.com/user/cn-hangzhou/serviceInstanceCreate?ServiceId=service-ddfecdd9b626465f85b6

References

3