|
随着大模型技术的快速发展,越来越多的公司在实际业务中落地了大模型应用。但是人们逐渐发现了大模型能力的不足。例如:由于大模型的训练数据是有限的,因此一些垂直领域的知识,如金融,医疗等等,大模型无法回答,或者容易出现幻觉。并且随着业务的复杂度提高,如何能让大模型像人一样思考,深度的分析解决问题,也对大模型的理解力提出了挑战。在这样的背景下,业界提出了 AI Agent 的概念。AI Agent 中文翻译成智能体,旨在让大模型像人脑一样思考问题,通过在思考过程中的不断反馈以及工具的调用,最终实现逐步完成给定目标的过程。例如,用户使用外卖助手 Agent,只需告诉 Agent,我想点一份肯德基的上校鸡块,Agent 便可以实现搜索肯德基商家,选择食物,下单,支付等一系列的思考过程以及工具调用,无需人工参与。 AI Gateway 的定义是 AI Native 的 API Gateway,是基于 API Gateway 的能力来满足 AI Native 的需求。例如:- 将传统的负载均衡/重试/fallback 能力延伸,支持对接多个大模型厂商 API,提高整体稳定性。
- 扩展可观测能力,支持不同模型之间效果对比的 A/B Test,以及对话上下文链路 Tracing 等。
HigressingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">1]是阿里云开源的一款 AI Gateway,基于 API Gateway 的能力,再加上基于 Wasm 插件扩展的大量 AI 插件,就可以满足上述所有 AI Native 的需求。 我也是基于 Higress 的 Wasm 插件扩展能力,开发了一个 AI Agent 插件,通过发挥 API Gateway 对于 API 管理的优势,用 API 赋能 AI Agent,基于 Agent ReAct 能力,可以实现零代码快速构建一个 AI Agent 应用。本文将以高德地图和心知天气两个服务为例,介绍一下如何零代码使用 AI Agent 插件构建一个同时支持地图服务和天气服务的 Agent,同时会探讨 AI Agent 插件的实现原理。高德地图提供了地图相关业务的 API 服务,例如地点搜索,导航等等;心知天气提供了天气情况查询的相关 API 服务。两个服务都提供了每日免费的 API 调用次数,方便用户测试。使用这两个服务需要去其官方网址,注册账号,申请 apiKey,有了 apiKey,便可以根据官方 API 文档去调用 API。官方文档的链接我放在下方,这里就不再赘述申请 apiKey 的过程。高德地图:入门指南-Web 服务 API丨高德地图 API(amap.com)ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">2] 心知天气:查看/修改你的 API 密钥(yuque.com)ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">3] 为了让插件能够访问通义千问大模型以及高德地图和心知天气服务,需要在 Higress 的路由管理-服务中,配置服务。服务类型为 DNS 域名:
dashscope:#通义千问大模型客户端配置apiKey:sk-xxxxxxxxxxxxxxxxxxxxxxxdomain:dashscope.aliyuncs.comserviceName:dashscopeservicePort:443promptTemplate:language:CHapis:-apiProvider:domain:restapi.amap.comserviceName:geoservicePort:80apiKey:in:queryname:keyvalue:fcxxxxxxxxxxxxxxxxxxapi:|openapi:3.1.0info:title:高德地图description:获取POI的相关信息version:v1.0.0servers:-url:https://restapi.amap.compaths:/v5/place/text:get:description:根据POI名称,获得POI的经纬度坐标operationId:get_location_coordinateparameters:-name:keywordsin:querydescription OI名称,必须是中文required:trueschema:type:string-name:regionin:querydescription OI所在的区域名,必须是中文required:trueschema:type:stringdeprecated:false/v5/place/around:get:description:搜索给定坐标附近的POIoperationId:search_nearby_poisparameters:-name:keywordsin:querydescription:目标POI的关键字required:trueschema:type:string-name:locationin:querydescription:中心点的经度和纬度,用逗号隔开required:trueschema:type:stringdeprecated:falsecomponents:schemas:{}-apiProvider:domain:api.seniverse.comserviceName:seniverseservicePort:80apiKey:in:queryname:keyvalue:SMxxxxxxxxxxxxxxapi:|openapi:3.1.0info:title:心知天气description:获取天气预办相关信息version:v1.0.0servers:-url:https://api.seniverse.compaths:/v3/weather/now.json:get:description:获取指定城市的天气实况operationId:get_weather_nowparameters:-name:locationin:querydescription:所查询的城市required:trueschema:type:string-name:languagein:querydescription:返回天气查询结果所使用的语言required:trueschema:type:stringdefault:zh-Hansenum:-zh-Hans-en-ja-name:unitin:querydescription:表示温度的的单位,有摄氏度和华氏度两种required:trueschema:type:stringdefault:cenum:-c-fdeprecated:falsecomponents:schemas:{} 插件配置分三部分,第一部分 dashscope,是通义千问大模型服务的相关信息,第二部分 promptTemplate,是自定义 agent react 模板的配置项,示例中的 language 是指使用中文模板,第三部分 apis 是外部 API 服务的相关配置,包含服务相关信息 apiProvider 以及 api(tools) 的 OpenAPI 文档,这一部分是实现 agent 调用外部工具的关键,agent 会通过理解 OpenAPI 文档来理解参数应该赋什么值。 curl'http://<这里换成网关公网IP>/api/openai/v1/chat/completions'\-H'Accept:application/json,text/event-stream'\-H'Content-Type:application/json'\--data-raw'{"messages":[{"role":"user","content":"我想在济南市鑫盛大厦附近喝咖啡,给我推荐几个"}],"model":"qwen","stream":false}'示例响应一: {...,"content":"在济南市鑫盛大厦附近,您可以选择以下咖啡店:\n1.luckincoffee瑞幸咖啡(鑫盛大厦店),位于新泺大街1299号鑫盛大厦2号楼大堂;\n2.三庆齐盛广场挪瓦咖啡(三庆·齐盛广场店),位于新泺大街与颖秀路交叉口西南60米;\n3.luckincoffee瑞幸咖啡(三庆·齐盛广场店),位于颖秀路1267号;\n4.库迪咖啡(齐鲁软件园店),位于新泺大街三庆齐盛广场4号楼底商;\n5.库迪咖啡(美莲广场店),位于高新区新泺大街1166号美莲广场L117号;以及其他一些选项。希望这些建议对您有所帮助!"...}示例请求二: curl'http://<这里换成网关公网IP>/api/openai/v1/chat/completions'\-H'Accept:application/json,text/event-stream'\-H'Content-Type:application/json'\--data-raw'{"messages":[{"role":"user","content":"济南市现在的天气情况如何?"}],"model":"qwen","stream":false}'示例响应二: {..."content":"济南市现在的天气状况为阴天,温度为31℃。此信息最后更新于2024年8月9日15时12分(北京时间)。"...}示例请求三: curl'http://<这里换成网关公网IP>/api/openai/v1/chat/completions'\-H'Accept:application/json,text/event-stream'\-H'Content-Type:application/json'\--data-raw'{"messages":[{"role":"user","content":"济南市现在的天气情况如何?用华氏度表示,用日语回答"}],"model":"qwen","stream":false}'{..."content":"济南市の現在の天気は雨曇りで、気温は88°Fです。この情報は2024年8月9日15時12分(東京時間)に更新されました。"...}AI Agent 插件的实现是使用了 ReAct(Reasoning and Action),ReAct 一词来自于论文《ReAct: Synergizing Reasoning and Acting in Language Models》,其核心思想是通过思维链的方式,引导模型将复杂问题进行拆分,一步一步地推理(Reasoning)和行动(Action),同事还引入了观察(Observation)环节,在每次执行(Action)之后,都会先观察(Observation)当前现状,然后再进行下一步的推理(Reasoning)。ReAct,就是要让开发者引导大模型进行推理,然后根据推理结果,判断需要采取哪个行动(调用工具),与外界环境互动。 AI Proxy 插件配置在默认阶段,而 AI Agent 可以配置在确保比 AI Agent 优先级高的阶段,比如认证阶段。这样可以保证用户的 http request 可以先被 AI Agent 拦截到。1. 参数配置使用 AI Agent 需要先按上一章节的插件参数配置的格式配置好服务以及 api 相关参数,也就是图中第 0 步要做的工作。2. prompt 模板首先,因为 Agent 是一个一步一步思考,多次调用工具的过程,因此是一个多轮对话场景,因此 AI Agent 维护了一个 messageStore,用来存储历史对话。整个 Agent ReAct 的控制核心就在于 prompt 模板,中文版本的模板如下:尽你所能回答以下问题。你可以使用以下工具:
{tools}
请使用以下格式,其中Action字段后必须跟着Action Input字段,并且不要将Action Input替换成Input或者tool等字段,不能出现格式以外的字段名,每个字段在每个轮次只出现一次:Question: 你需要回答的输入问题Thought: 你应该总是思考该做什么Action: 要采取的动作,动作只能是{tools_name}中的一个 ,一定不要加入其它内容Action Input: 行动的输入,必须出现在Action后。Observation: 行动的结果...(这个Thought/Action/Action Input/Observation可以重复N次)Thought: 我现在知道最终答案Final Answer: 对原始输入问题的最终答案再次重申,不要修改以上模板的字段名称,开始吧!
Question: {input}
该模板指导了大模型的推理过程。
在 AI Agent 的 onHttpRequestBody 阶段,接收到用户的 query 后,例如:我要在北京五道口附近喝咖啡,帮我推荐一下,会将 query 填入 {input} 部分,同时将插件参数配置中的 api 名称,功能以及 OpenAPI 文档放在 {tools} 部分,将 api 名称放在 {tools_name} 部分。将该 prompt 模板存入到 messageStore 中,格式为:role:usermsg:{prompt模板}之后通过 proxywasm.ReplaceHttpRequestBody 函数用 prompt 模板替换掉用户的原始 query,通过 ai-proxy 发送给大模型。
3. 推理过程(工具调用)大模型的返回会在 AI Agent 的 onHttpResponseBody 阶段拦截到。此时首先将回复内容存储到 messageStore 中,格式为:role:assistantmsg:{大模型的回复}之后需要通过正则表达式来判断大模型的返回内容。 Thought: 为了提供咖啡店的推荐,我首先需要获取五道口这一地点的经纬度坐标。
Action: get_location_coordinate
ActionInput:{"keywords":"五道口","region":"北京市"}
通过正则表达式取出 Action 与 Action Input 的值,就得到了需要调用的工具名称以及参数的值。 由于通常外部 API 都会提供一个认证 apiKey,只有配置了 apiKey,才能使用 api 接口。以本例子为例,需要在 url 中包含 key={apiKey} 的参数,所以我在 apiProvider 中对 apiKey 做了设计。包含 name 和 value 两个字段,name 表示实际服务商要求的 apiKey 的名称,例如本例中的 key,value 是具体的 apiKey 值。程序还会根据 OpenAPI 文档拼接处 url 以及查看 method 是什么,从而发送对应的 http 请求,例如本例子是要发送:GEThttps://restapi.amap.com/v5/place/text?key=xxxxxx&keywords=五道口®ion=北京市 该 API 的回复为: {"status":0,"message":"成功","result":{"location":{"lng":116.352978,"lat":39.982849},"precise":1,"confidence":100,"comprehension":100}}将该回复拼接到 Observation 后面,作为新的 query,在存储到 messageStore 后,将整个历史对话发送给大模型。此时由于处在插件的 onHttpResponseBody 阶段,无法再通过 ai-proxy 访问大模型,因此需要自己去调用 dashscope client 访问大模型。 Thought: 现在我得到了五道口的经纬度坐标,接下来我可以使用这些坐标来搜索附近的咖啡店。
Action: search_nearby_pois
ActionInput:{"keywords":"咖啡","longitude":"116.352978","latitude":"39.982849"}
程序通过正则得到 Action 与 Action Input 后,会重复刚才的过程,组装新的 url,向高德地图发请求,然后把结果存 messageStore 后给到大模型。整个过程是一个不断递归调用的过程。 Thought: 我现在知道最终答案
Final Answer: 在北京市五道口附近有几家咖啡店可以选择,包括:- 星巴克五道口店- Costa Coffee五道口店- 漫咖啡五道口店
您可以根据个人喜好选择前往。
这一次,程序通过可以检测到回复中包含了 Final Answer,这说明大模型已经得到了最终答案,无需再次调用工具了。因此检测到 Final Answer 就是结束递归调用的条件,此时就可以将 Final Answer 的答案通过 proxywasm.ReplaceHttpResponseBody 函数替换掉 response body 返回给用户了。 该部分对应图中的 5,6,7,8 步骤。 |