1、本文目的
不借助任何框架,使用c#写一个agent,实现调用阿里千问大模型完成预定任务。同时完成一个可扩展的agent框架雏形。
2、预期读者
本文假设读者已经了解了一些基本概念,例如AI,functioncall,c#编程等。
3、主要技术
在编写agent的过程中主要用到以下几点:
- 使用c#的WebClient来和千问接口进行通讯。
- 使用functioncall功能实现外部功能调用
4、解决的难题
在实现上述目的的过程中遇到了一些困难,这里记录一下,给自己备注,也为后人提醒。
- tool数量太多。我们都知道,大模型能够调用外部函数,是基于大模型本身的一个功能functioncall,后来这个功能发展成目前非常火热的MCP。不管是那种形式,底层的本质都是一样的,在向大模型输入信息的时候,同时提供给大模型一些工具(tools),以供大模型在需要的时候选择调用(其实这里说大模型调用不适合,应该是大模型指出应该让agent调用哪个function)。对于专注于特定领域的agent来说,也许不需要太多的tools就能完成工作。但是对于一些通用型的agent来说,要辅助人类完成任何可能得任务,需要的function数量非常庞大。想象一下有这样一个agent,可以当做你的助理,不但能在工作中给你提供帮助,还能在生活中为你处理各种事情:查天气,订机票,提醒,订外卖,做旅游规划等等。这样一个功能周全的agent要实现起来需要的tools无法想象。我们每次请求大模型的时候,如果都把这些tools传递给大模型,不但数据量大,速度慢,你的大模型账单也会快速增加,因为大模型是按照tokens收费的。为此设计了一些小技巧,以减少每次提交请求时的tools数量。
- 关键词法:这种方法就是监控用户的请求内容,如果包含指定的关键词,就把相对应的tools一起附带上。比如:当用户提到"天气"、"温度"时,就把查询天气的tools一起提交。
- 分组控制器法:这种方法就是把所有的tool分成一些大类,每个大类包含一定数量的tools,同时为每个大类编写一个tools,这样大模型在选择工具时,先选择大类的tool,然后大类tool的实现中在动态加载这个类别下面的其他tools。比如:我们可以给"预定东西"作为一个大类,这个大类下面包含"订机票","订酒店","订外卖"等等。大模型分析到用户的意图是"预定"类的,就先调用这个大类的tool,再有大类tool的实现中,动态加载各个子类的tool,然后根据用户需求调用实际的tool,例如"订机票"。分组控制器法的好处是减少了非必要的tool的提交,弊端是需要二次提交才能定位到具体的小类tool上面。后面会进行优化,尽量减少二次提交的时机。
- 大模型一次只能处理一个任务:在使用大模型function功能进行函数调用时候,发现大模型每次请求只能返回一个函数调用(观点如果有误,请指教)。比如用户请求:"帮我查一下北京明天的天气,然后订一张明天深圳到北京的机票"。在上面的例子中,用户实际给大模型2个任务,第一查天气,第二订机票。这两个任务都可以通过大模型的functioncall功能完成,但是一次请求中,大模型无法返回2个调用,只能返回一个,比如:getWeather(天气查询)。为此我们需要为这样的场景设计一个任务分解的tool,告诉大模型,如果存在多个任务,可以使用方法把任务分解为可以单独执行的最小单元。这样任务分解函数中的实现就可以对每个单独的任务进行重新提交,然后等待所有任务都返回后,整理这些内容在发给用户。
- 逻辑处理:大模型本身具有推理能力,如何把这种推理能力转化为解决实际问题能力成为我们任务中的关键。在上面的任务分级方法来处理复杂任务时,每个任务之间可能存在依赖关系。例如用户请求:"如果明天北京天气好的话,就给我订一张从深圳到北京的机票"。很明显,"订机票"这个任务的执行取决于"查询北京天气"的结果,通过任务分解,并制定任务依赖,使用大模型的推理能力可以解决这类问题。
5、目前完成的功能
- 实现自然语言对内部数据进行统计分析,并给出图形报表。例如:查一下加班最多的3个人是谁。大模型会根据用户的描述生成对应sql查询语句,执行这个查询语句得到结果,再把结果提交个大模型来回答用户的问题。当然这里有个前提,就是需要把内部系统数据表结果发给大模型(使用system的prompt)。
- 个人工作助理:例如可以提醒、通知、会议纪要等。
6、代码说明:打算把相关代码打包下载,但是写完文章,发现无法插入附件。另外代码本身没有难度,代码里面包含了一些我们自己的类库调用,所以无法直接运行,但是这些类库调用不影响阅读、理解,要是把这些类库替换一下(没几个,很好改)也能运行。
既然无法下载,那么接下来我会抽空把一些主要的代码进行单独说明,最后会放到github上下载。