Kotaemon支持自定义评分函数,优化排序结果
在智能问答、知识检索系统日益普及的今天,用户早已不再满足于“搜到就行”的粗放体验。他们期待的是精准、及时、符合上下文情境的答案——比如一位项目经理深夜查询“上周进度”,希望看到的是最新会议纪要而非三个月前的旧文档;一位财务高管搜索“风险报告”,自然期望优先呈现经过审计验证的内容。
然而现实是,大多数检索系统仍停留在“语义匹配即终点”的阶段。向量相似度高就排前面,关键词命中就能上榜。这种静态逻辑面对复杂业务场景时显得力不从心:过时信息混杂、权限边界模糊、关键内容被淹没……最终导致LLM生成响应时引用错误资料,甚至泄露敏感信息。
Kotaemon最近推出的自定义评分函数功能,正是为了解决这一系列痛点。它没有选择训练更复杂的重排序模型,也没有引入繁重的特征工程流程,而是走了一条更直接的路:把排序规则的控制权交还给开发者。
这套机制的核心思想其实很朴素——在完成初步召回后,允许你用几行代码决定“哪个结果更重要”。听起来简单,但它打破了传统检索系统中“排序不可见、不可调”的黑箱状态,让业务逻辑真正渗透进信息获取链条。
整个流程依然是熟悉的两段式架构:先由向量数据库(如FAISS)或混合索引快速捞出Top-K候选,再进入精排环节。不同的是,在这一步,系统不再依赖预设模型打分,而是加载用户注册的脚本函数,对每个结果动态计算新分数。
这个函数能访问什么?不只是文档内容和原始相似度,还包括元数据(创建时间、来源、标签)、查询文本本身,以及运行时上下文——比如当前用户的职级、所在部门、设备类型,甚至是会话历史中的意图线索。换句话说,你可以基于“谁在什么时候、出于什么目的提问”来调整返回顺序。
举个实际例子。假设你在构建一个企业应急响应平台,当有人输入“应急预案”时,系统不仅要找到相关文档,还得确保这些预案是最新的。老旧版本哪怕语义匹配度再高,也不该出现在首位。这时一段简单的JavaScript就能解决问题:
function score(query, document, context) { const docTimestamp = new Date(document.metadata.created_at).getTime(); const now = Date.now(); const hoursAgo = (now - docTimestamp) / (1000 * 60 * 60); // 越早的内容得分越低,但不低于基础值的10% const timeDecay = Math.max(0.1, 1 / (1 + hoursAgo / 72)); const baseScore = document.score || 1.0; const keywordBoost = query.includes("紧急") && document.content.includes("应急预案") ? 1.5 : 1.0; return baseScore * timeDecay * keywordBoost; }这里的时间衰减因子每72小时削减一次权重,相当于三天后影响力减半。同时,如果查询包含“紧急”且文档明确提到“应急预案”,则额外提升优先级。不需要重新训练任何模型,改完即生效,还能通过控制台日志实时观察每篇文档的打分轨迹。
再进一步,如果你的系统涉及权限分级,完全可以实现基于角色的排序策略。例如普通员工只能看到公开文档,而管理层则可接触内部报告,并且后者还会因“可信源加成”获得更高排名。Python风格的伪代码如下:
def score(query, document, context): base_score = document.get("score", 1.0) user_role = context["user"].get("role") required_level = document["metadata"].get("access_level", "public") role_rank = {"admin": 3, "manager": 2, "employee": 1, "guest": 0} if role_rank[user_role] < role_rank[required_level]: return 0.0 # 无权限访问,强制置底 trust_bonus = 1.2 if required_level == "internal" else 1.0 if "财务报告" in query and document["metadata"].get("audited") is True: return base_score * trust_bonus * 1.3 return base_score * trust_bonus注意这里的处理方式:不是等到前端才做权限过滤,而是在排序层就将未授权内容压至末尾。这意味着即使后续模块出现漏洞,也不会意外暴露高密级信息,安全防线前移了一步。
更有意思的是,这个函数甚至可以异步调用外部服务。比如你想让“成功案例”类查询只返回正面情绪的内容,就可以接入内部的情感分析微服务:
async function score(query, document, context) { const baseScore = document.score; const response = await fetch('https://nlp.internal.company/sentiment', { method: 'POST', body: JSON.stringify({ text: document.content }), headers: { 'Content-Type': 'application/json' } }); const { sentiment_score } = await response.json(); // -1 到 +1 if (query.includes("成功案例") && sentiment_score < 0.3) { return 0; // 过滤负面内容 } const bonus = sentiment_score > 0.6 ? 1.2 : 1.0; return baseScore * bonus; }当然,这类操作需要开启async_scoring模式并设置严格超时(建议不超过200ms),避免拖慢整体响应。也正因此,平台内置了沙箱环境,限制危险操作如文件删除、进程退出等,确保单个脚本异常不会影响全局稳定性。
整个系统的数据流清晰地体现了这种设计哲学:
[用户查询] ↓ [NLU模块:意图识别 + 查询理解] ↓ [双路检索] → [向量数据库召回] + [全文索引召回] ↓ [结果合并与去重] ↓ [自定义评分函数引擎] ← 加载用户脚本 ↓ [重排序后的Top-N结果] ↓ [LLM生成响应 | 直接返回列表]评分引擎作为插件化组件嵌入检索与生成之间,轻量、隔离、可热更新。修改函数无需重启服务,配合版本管理与AB测试能力,团队可以快速试错不同策略。比如同时部署两个版本:一个侧重时效性,一个强调权威性,按10%流量对比点击率和用户停留时间,数据说话。
实践中我们发现,很多问题其实根本不需要上深度学习模型。像“PDF技术手册不该和短视频教程混在一起展示”这样的需求,完全可以通过类型权重轻松解决:
// 给结构化文档更高优先级 const typeWeights = { "pdf": 1.3, "ppt": 1.2, "docx": 1.25, "video": 0.8, "blog": 0.9 }; return baseScore * (typeWeights[document.metadata.type] || 1.0);这种规则透明、调整迅速的方式,特别适合中小团队快速搭建垂直领域系统。相比微调rerank模型动辄数周的数据准备与训练周期,写个脚本几分钟就能上线验证,运维成本不可同日而语。
当然,自由也意味着责任。我们在使用时需遵循一些基本原则:保持函数轻量,避免复杂循环;保证幂等性,不修改外部状态;设置默认返回值以防崩溃;监控平均延迟与分差波动。尤其要警惕对外部API的高频调用,防止雪崩效应。
但从长远看,这种“可编程排序”的思路代表了一种趋势:AI系统不应只是被动执行模型输出,而应具备可解释、可干预、可进化的能力。当业务人员能用直观逻辑参与结果调控时,智能才真正落地为价值。
未来,Kotaemon计划推出图形化编辑器,让用户通过拖拽节点构建评分逻辑;内建常用模板库,如时间衰减、多样性打散、权威性加权等;甚至探索结合强化学习自动优化参数配置。但无论形式如何演进,核心理念不变——把控制权交给最懂业务的人。
这种高度集成的设计思路,正引领着智能知识引擎向更可靠、更高效的方向演进。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考