LOADING

加载过慢请开启缓存 浏览器默认开启

Astrbot-plugin-model-mcp-bridge 为不支持工具调用和MCP的模型提供工具调用途径的插件

0. 想法来源

所谓有得必有失。一个便宜的GPT API必定有其便宜的理由。
我现在使用的某个API,便宜是真的便宜,但……显然功能上有所缺失。例如,很多本该支持工具的模型不支持工具调用。
当然,也有部分模型生来就不支持这个。
然而……Astrbot调用函数工具和MCP的方式刚好是把Function Tool直接塞给模型。
我又希望我的Bot可以通过函数工具调用一些奇奇怪怪的东西……
既然先天不支持,那后天摇个轮椅总行了吧。

1. 背景

Astrbot正常的大致运作流程:

  1. 触发OnLLMRequestEvent,判断模型是否启用了工具和图像支持。没启用则清空相应内容。

  2. 初始化Agent。

  3. 执行Agent的step方法。

  4. 判断Agent的状态是否为DONE或者ERROR,运行次数是否超过限制。
    是则停止循环。
    不是则进行返回值处理后返回上一步。

其中Agent的step流程为:

  1. 转换运行状态为RUNNING。

  2. 发送LLM请求。

  3. 判断请求是否成功。不成功则转换状态为ERROR。

  4. 判断工具调用是否为空。为空转换状态至DONE,触发on_agent_done,触发OnLLMResponseEvent

  5. 如果信息链不为空,或LLM响应文本不为空,返回LLM响应。

  6. 如果工具调用不为空,调用工具,将工具结果添加回请求。

2. 问题与解决方案

Astrbot提供了一套很不错的插件接口。至少注册和调试还算方便。
也有一些接口刚好命中我们的需求。

先考虑一下我们最理想的流程应该是什么样子:

  1. 向API发送LLM请求前,截获请求并把工具列表和说明塞到API支持的地方。
    显然,稳妥保险的方式是塞到Prompt里。

  2. LLM响应时,判断是否有调用工具。如果有的话,调用工具获取结果。

  3. 把结果塞回给LLM,重复上述流程

显然我们可以在OnLLMRequestEvent时将工具信息塞入Prompt中,在OnLLMResponseEvent时判断有无工具调用,把信息链和响应文本清空防止Agent提前返回,使其调用工具。

但是问题出现了:OnLLMResponseEvent触发时,Agent状态已经转为DONE。哪怕此时我们把工具调用塞入了LLM Response,工具被调用后,结果也塞回了Request。但是Astrbot认为Agent状态已经是DONE了,不会再去调用step,请求永远发不出去。

我们需要劫持Agent的状态。但是显然没有这样的接口。
另外,运行过程中大概率不是只有一个Agent。我们得找到正确的Agent。
OnLLMResponseEvent中我们也无法获取到Agent。我们得想办法访问它。

于是,我劫持了Agent的_transition_state方法。这个方法负责状态转化。在这个方法里我可以通过self来获取Agent对象。
OnLLMResponseEvent中可以获得LLM Response。可以用它来生成ID。每个Agent在一次step中只会创建一个Response。我们可以用Response关联到Agent。

我们创建一个dict来储存ID和Agent的对应关系。这样我们就能在自己的函数中获得任意一个Agent。我们在_transition_state中将Agent储存到dict里。

现在,OnLLMResponseEvent触发时,我们可以通过Response找到这个Agent,修改它的状态和响应,然后使它继续执行,完成它该干的事情。

3. 后记

说实话,我还是希望api能原生实现我想要的功能。但……没人能在开始时想得那么周到。
每个目标都有许多种实现的途径。加钱,加力,加智慧,加机遇。