感觉写的不错,置顶一下吧。
我在做的这个项目将mcp集成到tauri大模型工具调用中。这个项目做到现在,我深刻学到的就是项目架构设计要遵循解耦性,代码书写要符合简易性,项目的运行要追求高效性
高效、简易、解耦 三原则
在一个项目开发的过程中,我们怎么知道什么逻辑放哪个位置?什么东西算前端,什么东西算后端?
我们未必一开始清楚,但在我们在上层构建的过程中,始终记得三原则,并按照这个三原则将逻辑和我们所聚焦的位置不断下沉。
拿现在这个项目举例——
项目构建了一个多层通信管道,每一层都有明确的职责边界:
用户界面层 (UI Events)
↓
Tauri命令层 (Rust Invoke Handler)
↓
┌─────────────────────────────────────┐
│ AI代理循环 (Agent Loop) │ ←─ 决策中心
└─────────────────────────────────────┘
↓
MCP服务层 (HTTP POST /execute)
↓
工具执行层 (Tool Registry)
前端是由typescript构建的,我们刚开始的工作就是在前端,在完成后端基础工作后,我们就把构想的一切内容放到前端来,如无必要,不放后端,这是符合三原则中的简易性。
举例,我们有一个组件想要实现时间显示,时间显示怎么获取?
now.toTimeString()
我们直接获取时间而不会逐个获取时分秒再拼接,这是符合简易性。
另一方面,我们不把获取查询时间的操作下沉,而是保留到前端,这也是简易性。
什么时候下沉呢?
我的ai问答需要向大模型api发送请求,后端处理工具调用、多轮对话等通信和处理工作更高效,同时api的一些配置如apikey、system需要进行保密工作,这就体现出下沉的必要性,由此我们放到了后端,由主程序与前端通信来处理请求。
如果前端和后端处理逻辑基本没差别,那就放前端,不做多余的事,也避免通信开销。
可是一个项目逻辑也太多了,总不能都塞main.rs后端里吧,这样翻找查阅逻辑位置多麻烦。为了我们开发工作的高效性,我们将ai逻辑拿出来,由主程序代理接受请求和返回执行结果。
工具调用需要给tool参数添加json格式数据,工具的执行需要有特定函数,可这些和AI逻辑并无关联,这正是我们追求的解耦性,这些再怎么写,出怎样的错误,都和AI逻辑关系不大,它依旧能正确发送请求和返回结果。
既然这部分实现了解耦,我们就应该把它们拿出来,我们由此使用了mcp,它给工具调用提供了标准的协议,工具调用方法从硬编码变成了进程间通信,由此,又完成了一次下沉。
可是如果大模型用到的工具非常多,每次对话依旧会带一大堆tool,既不符合高效性简易性,还浪费tokens,怎么解决?
我们因而给工具动态分类,甚至在工具成百上千时分好几层类,调用工具时逐层访问到工具,为了实现这点,我们把工具的执行从index拿出来,用到的时候才访问,实现热插拔。
但这里还有个小问题,虽然不会分多少层,但是每次工具调用都会带着上下文,实现一次工具调用不是也会浪费tokens以及减慢速度呢。因而同时我们进行简单的语义检索和关键词匹配,进一步揣测用户需要然后提前将可能用到的工具调给AI,如果命中,就少走几步。
这样似乎没问题了,可是每个工具添加还需要在json格式的表里注册自己的用法,这也有点麻烦,改一处就得再改另一处,这不仅仅是因为我就是这么懒,而是这一过程中存在信息溢出——我在脚本里存在的东西/已经获取到的信息/已经完成的工作,还要到别的地方再写/获取/做一遍?这不多余?这不就产生耦合?因而我们让脚本的用法暴露出来,在index中每次执行都生成一份新包含所有工具用法的json表,这样我们不用管了,这才是真正的热插拔,我想来就来,想走就走。
至此我们的通信管道完成,并且没有一层,没有一个工作是多余的。
所以你看,简易性并非是为了追求省事而简化逻辑,而是不做多余的事。 函数执行结果的catch error,变量的类型检查我们都不会省略。
高效性:可能的条件下追求极致的性能
简易性:不做多余的事
解耦性:做框架,而不是做效果
此方悬停