1. 从一个“套壳”说起:这个项目到底在做什么
第一次看到“一个套壳MCP,为什么长成了研究决策系统”这个标题,我脑子里蹦出来的第一个词是“意外”。因为“套壳”这个词在技术圈里通常带着一点贬义,暗示这东西没什么技术含量,无非是把别人的能力包装一下换个皮。但后半句“长成了研究决策系统”又明显在说,这个东西后来变得比预期复杂得多,甚至有了自己的骨架和判断逻辑。
我花了大概两周时间,把一个类似思路的原型从零跑通,又花了一周做压力测试和边界情况验证。这篇文章就是把这整个过程拆开,讲清楚一个看起来像“套壳”的东西,是怎么一步步演化成一个能辅助研究决策的系统的。如果你正在做AI应用层的东西,或者对“工具调用编排”这个方向感兴趣,又或者你手里有一个看起来很简单但总觉得能做得更深的小项目,那这篇内容应该对你有用。
先把这个项目的核心说清楚。所谓MCP,在这个语境下指的是一种“模型上下文协议”的实现思路,简单理解就是:让大语言模型能够通过一套标准接口去调用外部工具、查询外部数据、执行外部动作。而“套壳MCP”指的是,一开始并没有自己去实现底层协议,而是基于某个已有的协议框架或者开源实现,快速搭了一个能跑通的版本。目标很简单:让模型能查资料、能算数、能调API,就够了。
但问题在于,当你真的把这样一个东西放到真实的研究场景里,你会发现“能查资料”和“能辅助做研究决策”之间,隔了不止一个数量级的复杂度。研究决策不是问答,它需要多轮信息聚合、需要权衡不同来源的可信度、需要在信息不完整的时候做出判断、还需要把判断过程记录下来以便回溯。这些东西,一个简单的套壳MCP是给不了的。
所以这个项目的演化路径,本质上是一个“需求倒逼架构”的过程。一开始你只想让模型能调工具,后来你发现工具调用的结果需要被验证,验证之后需要被加权,加权之后需要被呈现,呈现之后还需要被追问。每一步都在往系统里加东西,加到最后,它就不再是一个套壳了,而是一个有层次、有状态、有决策逻辑的研究辅助系统。
我写这篇文章的目的,不是要吹嘘这个系统有多厉害,而是想把“为什么会变成这样”讲清楚。因为我相信很多做AI应用的人都会遇到类似的岔路口:你本来只想做个简单的东西,但用户的需求和真实场景的复杂度会推着你往深处走。关键是,你得知道往深处走的时候,哪些东西该加,哪些东西该砍,哪些东西看起来重要其实是个坑。
2. 核心架构拆解:从单层调用到多层决策的演化逻辑
2.1 最初的设计:为什么选择“套壳”而不是从零实现
一开始选择套壳,理由很实际。MCP协议本身并不复杂,核心就是定义了一套工具描述格式和调用返回格式,让模型能理解“有哪些工具可用”以及“调用结果长什么样”。如果从零实现,你需要处理协议解析、工具注册、调用路由、错误处理、超时重试这一整套东西。而基于已有的开源实现,你只需要关注“我要注册哪些工具”和“这些工具返回的数据怎么用”。
这个选择在当时是对的。因为项目的初始目标只是验证“模型能不能通过标准接口调用外部能力”,而不是验证“这套协议本身好不好”。用套壳的方式,两天就能跑通一个demo,一周就能接入三五个工具。如果从零写,光协议兼容性测试就得花掉一周多。
但套壳的代价很快就显现出来了。第一个问题是工具描述的粒度。开源实现通常只提供最基础的工具描述格式,比如“工具名称、功能描述、参数列表”。但在研究场景里,一个工具的输出可能需要标注来源可信度、时间戳、数据质量评分。这些元信息在基础协议里没有位置放,你只能塞在返回内容的文本里,模型读起来就很费劲。
第二个问题是调用链的可见性。套壳实现通常只记录“调用了什么工具、返回了什么”,但不记录“为什么调用这个工具、调用之前模型是怎么想的、调用之后模型怎么评估这个结果”。在研究决策场景里,这些中间过程恰恰是最重要的。因为你需要回溯“这个结论是怎么得出来的”,而不是只看最终答案。
所以大概在项目第二周的时候,我意识到一个问题:套壳可以让你快速起步,但如果你真的想让它辅助决策,你必须在套壳之上加一层“决策记录层”。这一层不改变底层协议,但会在每次工具调用前后插入结构化的日志和评估逻辑。
2.2 决策层的引入:从“能调用”到“会判断”的关键一步
决策层的核心思路很简单:在模型和工具之间加一个中间层,这个中间层负责三件事。第一,在模型决定调用某个工具之前,记录它当前的推理状态和调用意图。第二,在工具返回结果之后,对结果做一次结构化评估,包括来源可信度、时效性、与当前问题的相关度。第三,在模型基于结果做出下一步判断时,把之前的评估信息一并传回去,让模型在更丰富的上下文里做决策。
这个中间层听起来不复杂,但实现起来有几个关键点。第一个关键点是评估逻辑不能太重。如果你在中间层里跑一个完整的NLP模型去评估每条结果,延迟会爆炸。我的做法是用轻量规则加少量启发式判断。比如来源可信度可以基于域名白名单和历史准确率来打分,时效性可以基于时间戳和当前日期的差值来分级,相关度可以用简单的关键词重叠率来估算。这些规则不需要很精确,但必须很快。
第二个关键点是评估结果要能被模型理解。你不能只给模型一个数字分数,那样它不知道怎么用。我的做法是把评估结果转成自然语言描述,比如“该来源在过去三个月内的准确率为85%,数据发布时间为两周前,与当前问题的关键词重叠度为中等”。这样模型在后续推理时,就能自然地引用这些信息。
第三个关键点是决策层本身要可配置。不同研究场景对可信度、时效性、相关度的权重需求不一样。比如做历史研究,时效性权重可以很低;做市场分析,时效性权重就要很高。所以我把权重做成配置文件,不同项目用不同配置,不需要改代码。
这一步做完之后,系统的行为发生了明显变化。以前模型调用工具就是“查了就用”,现在它会说“我查到了三个来源,其中两个可信度较高但时效性一般,一个时效性很好但来源可信度中等,我倾向于综合前两个来源的结论,但会标注时效性风险”。这种变化,就是从“能调用”到“会判断”的跨越。
2.3 状态管理的必要性:为什么研究决策不能是无状态的
研究决策和普通问答最大的区别在于,它是有状态的。你不可能每次提问都从零开始,因为研究本身就是一个逐步深入的过程。今天查到的资料,明天还要用;昨天做出的判断,今天可能要修正。如果系统是无状态的,每次对话都重新开始,那它永远只能做浅层问答,做不了真正的辅助决策。
所以我在决策层之上又加了一个状态管理层。这个层负责维护几个东西:当前研究问题的结构化描述、已经收集到的信息来源列表、已经做出的中间判断、以及这些判断的依据和置信度。每次新的工具调用返回结果,状态管理层会更新这些信息。每次模型要做出新的判断,状态管理层会把相关的历史状态注入到上下文里。
状态管理最难的地方不是存储,而是“什么时候该遗忘”。研究过程中会产生大量中间信息,如果全部保留,上下文会爆炸,模型也会被无关信息干扰。我的做法是给每条状态信息打一个“相关性衰减因子”,随着研究问题的推进,旧信息的权重会逐渐降低。当权重低于阈值时,信息会被归档而不是删除,这样既控制了上下文长度,又保留了回溯能力。
这个设计参考了人类做研究时的习惯。你不会把所有笔记都摊在桌面上,但你会把重要的放在手边,不太重要的放进文件夹,需要的时候再翻出来。状态管理层做的就是这件事。
3. 核心细节解析:工具注册、结果评估与上下文注入的实操要点
3.1 工具注册的粒度控制:太粗和太细都是坑
工具注册看起来很简单,就是把能用的工具列出来,写好描述和参数。但实际操作中,粒度控制是个大坑。我试过两种极端做法,都出了问题。
第一种是粗粒度注册。比如把“搜索资料”做成一个工具,参数就是查询词。这样做的问题是,模型不知道这个工具背后到底查了哪些来源、用了什么策略。它只能看到一个结果列表,无法判断结果的可靠性。而且当搜索结果不理想时,模型也不知道该怎么调整查询策略,因为它不知道搜索是怎么执行的。
第二种是细粒度注册。比如把“搜索某学术数据库”“搜索某新闻源”“搜索某论坛”分别做成独立工具。这样做的问题是,模型需要自己决定该用哪个工具,而它往往缺乏足够的领域知识来做这个判断。结果就是它可能同时调用五个工具,然后被大量重复信息淹没。
我最后采用的方案是“中等粒度加策略参数”。比如注册一个“综合搜索”工具,但参数里包含“来源类型偏好”“时间范围”“结果数量上限”这些策略选项。模型不需要知道具体查了哪些数据库,但它可以通过参数来表达自己的信息需求偏好。同时,在工具返回结果时,每条结果都附带来源类型标签,这样模型在后续判断时可以参考。
这个方案的关键在于,策略参数的默认值要合理。我花了大概三天时间,用几十个典型研究问题做测试,调整默认参数,让模型在不指定策略的情况下也能得到还不错的结果。然后在此基础上,允许模型在需要时覆盖默认值。
注意:工具注册的粒度没有绝对标准,但有一个判断原则——如果模型需要知道“工具内部是怎么工作的”才能用好它,那粒度就太细了;如果模型无法通过参数表达“我想要什么样的结果”,那粒度就太粗了。
3.2 结果评估的轻量实现:规则、启发式与少量统计
结果评估是决策层的核心。我一开始想用模型来评估模型,就是让另一个大模型去判断每条结果的可信度和相关度。实测下来,效果确实好,但延迟太高,成本也扛不住。后来改成规则加启发式,效果打了八折,但速度快了十倍以上。
具体来说,评估分三个维度。可信度评估主要看来源。我维护了一个来源评分表,初始分数基于来源类型(学术期刊、官方统计、知名媒体、一般网站、个人博客等)给一个基础分,然后根据历史使用中的准确率做动态调整。这个动态调整很简单,就是每次人工确认某条信息是准确或错误时,对应来源的分数加一点或减一点。
时效性评估看时间戳。我把时效性分成五级:实时(一天内)、近期(一周内)、较新(一个月内)、一般(半年内)、陈旧(半年以上)。不同研究问题对时效性的要求不同,所以时效性分数不是绝对的,而是相对于当前问题的时效性需求来计算的。
相关度评估用关键词重叠加语义相似度的混合方案。关键词重叠就是看结果文本和当前研究问题的关键词重合比例,这个很快但不够准。语义相似度用一个小型的句子嵌入模型,只对关键词重叠度中等的结果做二次计算,这样既控制了计算量,又提高了准确度。
这三个维度的分数最后会合成一个综合评分,但合成方式不是简单加权平均,而是用了一个简单的决策树逻辑。比如如果可信度低于阈值,不管其他维度多高,综合评分都会被压到很低。如果可信度很高但时效性很差,综合评分会中等,但会标注“时效性风险”。这种非线性合成方式,比线性加权更符合实际决策逻辑。
3.3 上下文注入的策略:什么时候给模型看什么
上下文注入是状态管理层和决策层的接口。做得好的话,模型会感觉“信息刚刚好”;做得差的话,模型要么信息不足,要么被淹没。我踩过的坑包括:一次性注入太多历史信息导致模型注意力分散;注入太少导致模型重复调用工具;注入格式不统一导致模型理解困难。
现在的做法是分层注入。第一层是“当前问题摘要”,用三五句话描述当前研究问题的核心和进展。第二层是“关键发现列表”,只列出置信度最高的三到五条发现,每条附来源和评分。第三层是“待验证假设”,列出当前还没有足够证据支持的判断。第四层是“工具调用历史摘要”,只记录最近几次调用的工具和结果概况,不展开细节。
这四层信息的总长度控制在上下文窗口的百分之二十以内,留出足够空间给当前对话和工具返回结果。如果某层信息为空,就跳过不注入,避免浪费上下文。
注入格式上,我用的是结构化文本而不是JSON。因为实测发现,模型对结构化文本的理解和引用准确率更高。比如“关键发现:1. 某来源指出X现象在Y条件下显著(可信度85%,时效性较新)”,这种格式模型很容易在后续推理中直接引用。
提示:上下文注入不是越多越好。我的一般原则是,如果一条信息在当前轮对话中不太可能被用到,就不要注入。宁可让模型多调用一次工具,也不要让它被无关信息干扰。
4. 实操过程全记录:从零搭建一个可用的研究决策原型
4.1 环境准备与基础框架选型
先说环境。我用的是一台普通开发机,16G内存,没有独立显卡。这意味着所有模型调用都走API,本地只跑轻量逻辑。如果你有本地GPU,可以把句子嵌入模型放到本地跑,延迟会更低。
基础框架选型上,我对比了三个方向。第一个方向是基于某个开源MCP实现直接改,优点是起步快,缺点是受限于原实现的架构,加东西多了之后会很别扭。第二个方向是完全自研,优点是架构自由,缺点是前期投入大,协议兼容性要自己处理。第三个方向是混合方案,底层用开源实现做协议解析和工具调用,上层自研决策层和状态管理层。
我最终选了第三个方向。具体来说,底层用了一个轻量级的开源工具调用框架,只保留它的协议解析和调用路由功能,把它的日志和状态管理全部替换掉。上层自己写了决策层和状态管理层,用Python实现,大概一千多行代码。
这个混合方案的好处是,协议兼容性有保障,同时上层架构完全可控。坏处是两层之间的接口需要自己定义,如果开源实现升级,可能需要适配。但考虑到这个项目的生命周期,这个代价可以接受。
4.2 工具接入的具体步骤与参数配置
工具接入我分了四步。第一步是工具清单梳理,把研究场景中常用的工具列出来,包括综合搜索、学术搜索、数据查询、计算工具、文本分析工具等。第二步是逐个接入,每个工具写一个适配器,把工具的原始接口转换成统一的内部格式。第三步是参数默认值调优,用典型问题测试,调整每个工具的默认参数。第四步是工具描述优化,确保模型能准确理解每个工具的能力边界。
以综合搜索工具为例,适配器需要做几件事。接收内部格式的查询请求,转换成搜索API的参数,调用API,然后把返回结果转换成内部格式,包括标题、摘要、来源、时间戳、URL等字段。同时,适配器还要处理API的错误和超时,返回结构化的错误信息而不是直接抛异常。
参数默认值方面,综合搜索的默认结果数量设为10条,来源类型偏好设为“学术和官方优先”,时间范围设为“不限”。这些默认值是通过测试确定的。我试过默认返回20条,结果模型处理起来很吃力,很多结果根本用不上。也试过默认只返回5条,结果模型经常需要二次搜索。10条是个平衡点。
工具描述方面,我写了一段大概两百字的说明,包括工具能做什么、不能做什么、参数含义、返回结果格式、以及一些使用建议。这段描述会作为系统提示的一部分注入给模型。实测发现,描述越具体,模型用错工具的概率越低。
4.3 决策层与状态管理层的代码实现要点
决策层的核心是一个评估函数和一个注入函数。评估函数接收工具返回的原始结果,输出带评分和标签的结构化结果。注入函数接收当前状态和评估后的结果,输出注入到模型上下文的文本。
评估函数的实现要点是快。我用了缓存机制,相同来源的评分结果缓存一小时,避免重复计算。关键词重叠用简单的集合运算,语义相似度只在必要时调用嵌入模型。整个评估过程控制在100毫秒以内。
注入函数的实现要点是分层和截断。分层就是前面说的四层结构,截断就是每层都有最大长度限制,超出部分按评分排序截断。截断阈值是通过测试确定的,目标是让注入文本的总长度不超过上下文窗口的百分之二十。
状态管理层的核心是一个状态存储和一个衰减函数。状态存储用简单的字典结构,按研究问题ID分组。衰减函数在每次新结果注入时更新所有历史状态的权重,权重低于阈值的状态被移到归档区。
代码实现上,我用了Python的dataclass来定义状态结构,用简单的JSON文件做持久化。没有用数据库,因为数据量不大,JSON足够。如果你要做多用户版本,可能需要换成数据库。
4.4 完整测试流程与效果评估
测试分三个阶段。第一阶段是单元测试,每个工具适配器单独测试,确保输入输出格式正确。第二阶段是集成测试,用十个典型研究问题跑完整流程,检查决策层和状态管理层是否正常工作。第三阶段是压力测试,用一百个问题连续跑,检查延迟和内存占用。
效果评估我用了三个指标。第一个是信息覆盖率,就是最终答案中引用的信息占所有收集信息的比例。这个指标太低说明收集了太多无用信息,太高说明可能遗漏了重要信息。第二个是决策可回溯率,就是最终答案中的每个判断都能追溯到具体来源和评估记录的比例。这个指标应该接近百分之百。第三个是人工满意度,我找了几个朋友做盲测,让他们评价答案的质量和可信度。
实测下来,信息覆盖率在百分之四十到六十之间比较健康,决策可回溯率在百分之九十五以上,人工满意度在四星左右(满分五星)。这个结果不算惊艳,但考虑到这是一个套壳起步的项目,已经超出了我的预期。
5. 常见问题与排查技巧实录
5.1 模型不调用工具或调用错误工具怎么办
这是最常见的问题。表现是模型要么直接回答不调用工具,要么调用了不相关的工具。原因通常有三个。第一个是工具描述不够清晰,模型不知道这个工具能解决什么问题。第二个是系统提示没有强调“需要外部信息时应该调用工具”。第三个是工具名称或参数名称有歧义。
排查步骤是这样的。先看模型是否理解了工具的存在,可以在系统提示里加一句“你有以下工具可用”,然后列出工具名称和一句话描述。如果模型还是不用,就在用户问题后面加一句“如果需要外部信息,请调用相应工具”。如果模型调用了错误工具,就检查工具描述是否准确,特别是“这个工具不适用于什么场景”这部分。
我踩过的一个坑是工具名称太抽象。比如把搜索工具命名为“query”,模型经常把它当成通用查询工具,用来查什么都行。后来改成“search_external_sources”,模型就清楚多了。
5.2 工具返回结果过多导致上下文溢出
这个问题在接入综合搜索时特别明显。一次返回十几条结果,每条几百字,上下文很快就满了。解决方案有三个。第一个是限制返回数量,在工具适配器里做截断。第二个是摘要化,对长结果做自动摘要,只保留关键信息。第三个是分层注入,只把评分最高的几条注入上下文,其余放在状态管理层备用。
我最后三个方案都用上了。工具适配器默认只返回十条,每条结果自动摘要到两百字以内,注入时只取评分最高的五条。这样上下文占用控制在可接受范围内。
5.3 评估分数与实际质量不符的修正方法
评估分数不准是必然的,因为规则和启发式不可能覆盖所有情况。关键是要有修正机制。我的做法是加了一个人工反馈接口,用户可以对每条结果标注“准确”“不准确”“不确定”。这些标注会用来调整来源评分和评估规则。
具体来说,如果某个来源被多次标注为“不准确”,它的可信度基础分会被调低。如果某个关键词组合被多次标注为“不相关”,相关度评估里的关键词权重会被调整。这个修正机制不需要很频繁,每周跑一次批量更新就行。
注意:修正机制要谨慎,不要因为一两次标注就大幅调整分数。我一般设置一个最小样本量,比如某个来源至少被标注十次以上,才调整它的基础分。
5.4 多轮对话中状态丢失或混乱的排查
状态丢失通常是因为状态存储的键值设计有问题。我一开始用对话ID做键,但对话ID在长对话中会变化,导致状态找不到。后来改成用研究问题ID做键,研究问题ID在问题创建时生成,整个研究过程中不变。
状态混乱通常是因为衰减函数参数设置不当。衰减太快,旧信息很快被归档,模型看不到;衰减太慢,旧信息一直占着上下文。我的经验是,衰减半衰期设为五轮对话左右比较合适。也就是说,五轮对话之后,旧信息的权重降到一半。
排查时可以用日志。每次状态更新时记录哪些信息被注入、哪些被归档、权重是多少。这样出问题时可以回溯。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 模型不调用工具 | 工具描述不清或系统提示未强调 | 检查工具描述和系统提示 | 优化描述,加调用提示 |
| 调用错误工具 | 工具名称或描述有歧义 | 检查工具命名和描述 | 重命名,明确适用场景 |
| 上下文溢出 | 返回结果过多或注入过多 | 检查返回数量和注入长度 | 截断、摘要、分层注入 |
| 评估分数不准 | 规则覆盖不足或来源评分偏差 | 对比人工标注和自动评分 | 加反馈接口,调整规则 |
| 状态丢失 | 状态键值设计问题 | 检查状态存储键 | 改用稳定ID做键 |
| 状态混乱 | 衰减参数不当 | 检查衰减半衰期 | 调整半衰期到五轮左右 |
| 延迟过高 | 评估逻辑太重或API调用慢 | 分段计时 | 缓存、轻量化、异步调用 |
6. 从套壳到系统的关键转折点复盘
6.1 什么时候该加层,什么时候该砍功能
这个项目最大的经验就是:加层容易砍层难。每加一个层,系统复杂度就上一个台阶,调试难度也上一个台阶。所以加层之前一定要问自己:这个层解决的是不是核心问题?有没有更轻量的替代方案?
我的判断标准是:如果一个问题在不加层的情况下,会导致系统在百分之二十以上的场景中表现明显下降,那就值得加层。如果只是百分之五的场景有问题,那就先忍着,或者用临时方案绕过。
砍功能同样重要。我在项目中期加了一个“自动生成研究假设”的功能,想法是让系统根据已有信息自动提出新的研究方向。实测下来,这个功能产生的假设质量参差不齐,而且经常偏离用户的实际需求。后来我把它砍了,改成“基于当前信息列出待验证问题”,让用户自己决定要不要深入。这个改动让系统的实用性反而提高了。
6.2 套壳项目的演化边界在哪里
套壳项目的演化边界,取决于底层协议的能力上限。如果底层协议只支持简单的请求-响应模式,那你再怎么加层,也做不了复杂的多轮协商。如果底层协议不支持流式返回,那你做不了实时交互。
所以判断一个套壳项目能走多远,关键看底层协议是否支持你需要的核心交互模式。如果支持,那加层可以走很远;如果不支持,那要么换底层,要么接受能力上限。
这个项目的底层协议支持工具调用和结果返回,但不支持工具之间的直接通信。这意味着所有工具协调都必须通过模型来做,模型成了瓶颈。如果要做更复杂的工具编排,可能需要换一个支持工具间通信的底层框架。
6.3 给后来者的三条实操建议
第一条建议:先跑通最小闭环,再考虑加层。最小闭环就是“模型调用工具、工具返回结果、模型基于结果回答”。这个闭环跑通了,你才有资格谈优化。我见过太多人一上来就设计复杂架构,结果连基本调用都跑不通。
第二条建议:日志要详细,但不要影响性能。详细日志是排查问题的唯一依靠,但日志写入不能拖慢主流程。我的做法是异步写日志,主流程只负责把日志事件推到队列里,后台线程负责写文件。
第三条建议:评估逻辑要可解释。不要用黑盒模型做评估,因为出问题时你没法调试。用规则和启发式虽然效果差一点,但每一步都能解释,都能调整。等规则稳定了,再考虑用模型做补充。
6.4 这个系统后续还能怎么扩展
扩展方向有几个。第一个是增加工具类型,比如接入数据库查询、接入代码执行环境、接入图表生成工具。每增加一类工具,系统的能力边界就扩大一圈。第二个是优化评估逻辑,引入更细粒度的来源可信度模型,比如按领域区分可信度。第三个是增加协作功能,让多个用户可以共享研究状态和发现。
但扩展之前,我建议先把现有功能的稳定性做好。我见过太多项目,功能加了一堆,但每个都不稳定,最后用户流失。稳定性的优先级永远高于功能数量。
我个人在实际操作中的体会是,一个研究决策系统的价值不在于它用了多先进的模型或多复杂的架构,而在于它能不能让研究者更快地找到可靠信息、更清晰地看到判断依据、更方便地回溯决策过程。这个项目从套壳起步,一路加层,最后长成现在这个样子,本质上是被这三个需求推着走的。如果你也在做类似的东西,不妨先问问自己:我的系统到底在帮用户解决哪个具体问题?这个问题的答案,会决定你该加什么层、砍什么功能。