ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

技术线05_端侧小模型不可靠先检查你的Agent架构

技术线05_端侧小模型不可靠先检查你的Agent架构 端侧 4B 模型不可靠先检查你的 Agent 架构面向读者正在做 RAG、Agent、私有化部署或端侧 AI 应用的工程师。示例系统完全离线的质量体系助手生成模型使用 Qwen3.5-4B嵌入模型使用 bge-m3存储使用 SQLite回答要求可溯源。结论先说4B 模型不能像云端大模型那样承担“规划、检索、判断、表达、自检”的全部职责。更稳的做法是把架构拆成记忆预算、意图仲裁、证据计划、引用门禁和受控表达。模型可以参与但不应该拥有最终裁决权。一、先说问题小模型经常不是“笨”而是被架得太高常见 Agent 链路很直接prompt 里写清角色把聊天记录拼进上下文模型推理后决定是否查 RAG、是否调用工具最后再由模型生成答案。这种链路对大模型勉强可用是因为大模型有较强的长上下文稳定性、格式遵循能力和隐式纠错能力。但换到端侧 4B 后模型要同时做五件事理解当前问题继承历史对象规划检索和工具组织自然语言回答自查引用和格式。一旦上下文变长、问法变短、工具参数变复杂错误就会集中暴露。项目里最常见的失败不是“完全答不出”而是四类更危险的问题失败类型表现风险历史继承错对象上一轮问 A这一轮短追问被接到 B回答流畅但主体错了短追问缺主语“还有吗”“必须改的有哪些”解析成宽泛问题检索范围漂移JSON / 工具参数漂移多字段、多枚举、多目标时输出不稳定工具调用失败或调错入口库外编号被误放行相似度高模型就把库外标准当成命中的标准高置信地编造引用第三类尤其值得展开。早期评测里出现过GJB 450A-2004的检索相似度约0.8015但这个标准号并不在库内。这个问题说明语义相似度只能衡量文本接近程度不能证明编号真实存在。如果门禁只有相似度阈值系统就会把“很像”当成“命中”。所以只往 prompt 里加“不要编造”“仔细判断”“严格输出 JSON”是不够的。小模型的可靠性要靠架构让它难错。二、架构改法把决定权从模型手里拿回来这个项目的主链路可以压缩成下面这样用户输入 - Context Core有界记忆装配 - Rule Intent规则优先解析 - LLM Intent Parser受限语义提案 - Intent Arbiter确定性仲裁 - Evidence Planner生成证据计划 - Retrieval / Tools受控召回与工具执行 - Quality Gate Precheck生成前拦截 - 4B 受控表达 - Quality Gate Postcheck引用后校验 - 正常回答 / 澄清 / 拒答 / 摘录降级核心分工是规则管边界域外、系统指令、明确对象、材料上下文优先由确定性逻辑处理。状态管继承短追问继承的是结构化意图和目标 ID不是自由复述聊天记录。计划管检索先确定查什么、查多少、允许用什么工具再执行检索。门禁管引用编号、条款、来源必须在允许集合内。模型管表达4B 只在受控输入上组织语言或者输出必须可校验的结构化提案。这不是不给模型用语义能力而是把语义能力放在护栏中间。三、Context Core历史只补信息不重写当前意图长会话最大的问题不是“记得少”而是“什么都记得”。如果把几十轮原文全部塞给 4B当前问题很容易被旧主题带偏。项目里没有把记忆做成一个聊天数组而是拆成固定事实、滚动摘要、最近完整轮次和检索增强话题。关键预算直接写成常量// gjb-agent/src-tauri/src/context_core.rspubconstREAD_PAIRS:usize12;pubconstRECENT_PAIRS:usize4;pubconstRETRIEVAL_CONTEXT_PAIRS:usize3;pubconstSUMMARY_MAX_CHARS:usize1_500;pubconstPROMPT_MAX_CHARS:usize10_000;装配时也保持明确优先级更早轮次进入摘要最近 4 轮保留原文固定事实优先于摘要最后统一受 prompt 上限约束。letsplitturns.len().saturating_sub(RECENT_PAIRS);let(older,recent_turns)turns.split_at(split);render_prompt_block(summary,recent_turns,pinned_facts,PROMPT_MAX_CHARS,);这里的重点是历史上下文只允许补充缺失对象和任务背景不能反过来改写当前意图。比如用户先问“技术归零是什么”再问“它和管理归零有什么区别”系统可以把“技术归零”作为补充对象但如果当前问题已经显式切换到新对象旧对象不能继续争夺解释权。检索 query 也同样有界pubfnbuild_retrieval_query(question:str,turns:[ConversationTurn],)-String{letmutpartsvec![question.trim().to_string()];letrecent_questions:VecStringturns.iter().rev().take(RETRIEVAL_CONTEXT_PAIRS).map(|turn|excerpt(turn.question,150)).collect();if!recent_questions.is_empty(){parts.push(format!(相关话题{},recent_questions.join()));}ifletSome(last)turns.last(){letconclusionexcerpt(last.answer,PRIOR_CONCLUSION_EXCERPT);if!conclusion.is_empty(){parts.push(format!(上一轮结论{conclusion}));}}dedupe_query_parts(parts)}这样“还有吗”这类短问不会把整个历史都拖进检索只会补充最近有限的话题和上一轮结论。四、意图层模型输出是提案不是最终决定规则意图快、可解释、稳定但语义泛化有限。LLM 意图解析能补语义但 4B 输出不能直接相信。项目里的做法是让两者同时存在再由确定性仲裁器裁决。LLM Intent Parser 的预算写得很紧// gjb-agent/src-tauri/src/intent_parser.rspubconstLLM_INTENT_MAX_TOKENS:u32128;pubconstLLM_INTENT_TIMEOUT_MS:u641_800;pubconstLLM_INTENT_TOTAL_TIMEOUT_MS:u642_000;超时、schema 非法、目标非法都会失败。只有可重试的 schema / object 错误才允许修复而且第一次调用如果已经超过 200ms就不再重试避免短追问被拖成两次完整生成。letstartedInstant::now();letfirstself.invoke(user_prompt,budget,1,started);matchfirst.result{Ok(output)LlmIntentOutcome::succeeded(output,attempts),Err(code)if!code.retryable(){LlmIntentOutcome::failed(code,attempts,false)}Err(_){ifstarted.elapsed().as_millis()asu64200{LlmIntentOutcome::failed(LlmIntentFailureCode::RetryBudgetExceeded,attempts,false,)}else{self.retry_after_invalid(user_prompt,budget,started)}}}模型返回的内容还要过三道校验JSON schema、封闭枚举、ID 白名单。lettrimmedraw.trim();if!trimmed.starts_with({)||!trimmed.ends_with(}){returnErr(LlmIntentFailureCode::InvalidSchema);}letvalue:Valueserde_json::from_str(trimmed).map_err(|_|LlmIntentFailureCode::InvalidSchema)?;letmutoutput:LlmIntentOutputserde_json::from_value(value).map_err(|_|LlmIntentFailureCode::InvalidSchema)?;whitelist.validate(mutoutput)?;Ok(output)白名单校验不是简单看类型而是检查目标 ID、主题 ID、活跃引用 ID 是否都在当前领域上下文允许范围内fnvalidate(self,output:mutLlmIntentOutput)-Result(),LlmIntentFailureCode{validate_closed_set(output.target_ids,self.target_ids)?;validate_closed_set(output.topic_ids,self.topic_ids)?;validate_closed_set(output.referenced_intent_ids,self.active_intent_ids)?;validate_collection_size(output.secondary_intents,2)?;validate_collection_size(output.target_ids,5)?;validate_collection_size(output.topic_ids,3)?;if!output.confidence.is_finite()||!(0.0..1.0).contains(output.confidence){returnErr(LlmIntentFailureCode::InvalidSchema);}ifoutput.needs_clarificationoutput.ambiguity_code.is_none(){returnErr(LlmIntentFailureCode::InvalidSchema);}output.topic_idsself.project_topic_ids(output.target_ids);Ok(())}确定性仲裁器再做最后裁决。域外硬边界不交给模型// gjb-agent/src-tauri/src/intent_arbiter.rsifinput.rule_state.domain_statusDomainStatus::OutOfDomain||input.snapshot.domain_guard_hint()reject_domain||(input.rule_actionIntentAction::RejectDomaininput.proposal.is_some_and(|proposal|{proposal.primary_intentUserIntent::Unknown})){returndomain_guard(input);}有效提案也不会直接采纳而是先比较规则结果和模型结果是否一致再根据显式目标、活跃引用、冲突惩罚、规则兜底加分等确定性策略裁决letagreementproposals_agree(input.rule_state,proposal);letmutconfidenceself.base_confidence(input,proposal,agreement);ifcontext_switch_has_rule_task(input,proposal){returnrule_secondary_task_decision(input,confidence);}ifpure_continuation_rule_beats_system_misread(input,proposal){returnrule_decision(input,ArbitrationReason::RuleFallback,confidence.max(self.policy.execute_threshold),);}这一层的价值在于LLM 单路不准不代表不能接入主链路只要它的输出是可拒绝、可仲裁、可降级的提案就能参与提高语义覆盖。五、Evidence Planner先开检索单再查库很多 Agent 的检索是“模型想查什么就查什么”这在端侧 4B 上很危险。项目里检索前必须先生成EvidencePlan里面明确证据类型、检索 query、直接来源、输出契约和允许工具。// gjb-agent/src-tauri/src/evidence_planner.rspubconstMAX_RETRIEVAL_QUERIES:usize3;pubconstMAX_DIRECT_SOURCES:usize8;pubstructEvidencePlan{pubevidence_ids:VecEvidenceKind,pubretrieval_queries:VecString,pubrequired_direct_sources:VecString,puboutput_contract:String,puballowed_tools:VecString,}域外问题不开证据直接返回no_evidence需要明确对象但对象缺失时返回clarification不让模型猜检索词。ifstate.domain_status!DomainStatus::InDomain{returnOk(EvidencePlan{evidence_ids:Vec::new(),retrieval_queries:Vec::new(),required_direct_sources:Vec::new(),output_contract:no_evidence.into(),allowed_tools:Vec::new(),});}ifrule.requires_targetsstate.target_ids.is_empty()!current_material_scope{returnOk(EvidencePlan{evidence_ids:Vec::new(),retrieval_queries:Vec::new(),required_direct_sources:Vec::new(),output_contract:clarification.into(),allowed_tools:Vec::new(),});}所有计划也会做数量收敛direct_sources.truncate(MAX_DIRECT_SOURCES);语义召回当然有用但它必须被夹在证据计划中间召回前知道范围和上限召回后还要能校验来源。六、Quality Gate生成前后都要有门禁生成前门禁不是走过场而是检查任务路由、工具白名单、超时预算和证据状态。证据缺失时不进入 LLM。// gjb-agent/src-tauri/src/quality_gate.rspubfnprecheck(input:PrecheckInput_)-PrecheckOutcome{ifinput.elapsed_msinput.timeout_ms{returnrejected(run_timeout);}ifinput.route_id.is_none(){returnrejected(route_unmatched);}ifletSome(tool)input.required_tools.iter().find(|tool|{!input.allowed_tools.iter().any(|allowed|allowed**tool)}){returnrejected_tool(tool);}if!input.has_public_evidence!input.has_material!input.has_template{returnrejected(evidence_missing);}PrecheckOutcome{passed:true,code:None,}}生成后门禁继续校验标准号和条款号。允许集合不是模型自己声明的而是由本轮命中文档、片段和结构化上下文构造出来的。letmutallowed_docsallowed_from_docs(hits.iter().map(|hit|hit.doc.clone()));allowed_docs.extend(hits.iter().flat_map(|hit|profile.extract_standard_codes(hit.snippet)).map(|token|normalized_code(token)),);letbad_standardsunknown_tokens(profile.extract_standard_codes(answer),allowed_docs,);let(cleaned,removed_standards,_)strip_unknown_token_sentences(answer,bad_standards);ifremoved_standards0{violations.push(repaired(citation_standard_removed,答案含非本轮引用的标准号,false,));answercleaned;}也就是说模型可以写出一段流畅回答但如果其中引用了本轮证据之外的标准号包含这个引用的句子会被剥离。这个动作可能让回答变得保守但比“高置信编造”更可接受。七、验证结果单路不稳主链路可控架构不能只讲故事。项目用封闭评测集、真实链路压测和功能回归来验证。1. 语义意图评测一组 60 例语义评测的对比指标Rule 单路LLM 单路Arbiter 主链路意图准确率60.00%76.67%100.00%上下文继承准确率54.17%83.33%100.00%对象准确率70.27%89.19%100.00%域外误放行-00误澄清率-1.67%0LLM P95 延迟-1600ms1600ms这组数据容易被误读成“4B 达到 100%”。更准确的解释是Rule 和 LLM 单路都没有达到稳定接主链路的水平但经过确定性仲裁、域外守卫和白名单校验后主链路在这组封闭题集里表现可控。2. 引用压力集100 题连续压力集首轮为98/100暴露的是指代链和证据兜底长度问题。修复指代词表、当前任务继承和硬上限后完整重跑达到100/100。这里的重点不是“永远满分”而是失败能被定位到具体机制并且能进入回归。3. 多功能真实链路回归另一组覆盖标准问答、模板中心、研制过程评审、评审模拟和资格审查的评测项结果真实链路执行750 次唯一题目450 道三轮机器判定均通过引导题86/86 命中目标功能落库展示一致750/750这组测试里只有 138 题进入生成回答大量问题由结构化引导、模板目录、规则知识或审查结果直接回答。这也说明一个端侧 Agent 的稳定性不只来自“模型答得好”还来自“很多问题根本不需要把解释权交给模型”。当然要保留边界这些是封闭域、固定题集、带校验和仲裁的机器判定结果不能直接推广为开放域能力也不能替代业务盲评。八、可复用的工程经验1. 不要让 4B 同时做规划、检索、判定和表达大模型 Agent 可以把多角色压在一个推理过程里4B 不适合。更稳的结构是每个模块只负责一件可验证的事。2. 约束要写成常量和契约不要写成提示词态度“最近 4 轮保留原文”“query 最多 3 条”“来源最多 8 个”“单次解析 1800ms”这类规则应该出现在代码、配置和 schema 里而不是只出现在系统提示词里。3. 模型输出必须可拒绝JSON schema、封闭枚举、目标白名单、置信度范围、重试次数、总延迟都要校验。模型解析失败时系统应该回退规则路径而不是把非法输出继续往下传。4. 检索不是自由行动而是受控任务先由意图和状态生成 Evidence Plan再执行检索。query 数量、来源数量、允许工具、输出契约都应该提前声明。5. 相似度阈值不是引用门禁相似度只能帮助排序不能证明标准号、条款号真实存在。引用校验必须基于本轮证据、库内编号和结构化上下文构造允许集合。6. 拒答和澄清是系统能力no_evidence、clarification、evidence_missing、citation_standard_removed不是失败文案而是把不确定挡在系统边界外的机制。端侧垂直 Agent 更需要这种保守性。7. 判断一个端侧 Agent先问四个问题意图谁仲裁证据谁选择引用谁校验超时怎么降级这四个问题答不清楚换更大的模型也只是把错误往后推。落地检查清单列出模型当前能决定的所有事项把域外、对象继承、工具选择和最终引用裁决移到确定性模块。为上下文、检索、LLM 解析和总链路定义显式预算用常量或配置锁住不允许调用方临时放宽。给每个模型输出定义 schema、封闭枚举、ID 白名单、超时和失败回退路径。在检索前生成 Evidence Plan明确 query 数量、来源数量、证据类型、允许工具和输出契约。建立四类回归Rule 单路、LLM 单路、Arbiter 主链路、真实链路压测指标至少覆盖意图、继承、域外误放行、引用违规和延迟。结语端侧 4B 的正确用法不是把它当成缩小版专家而是给它一张受控工单输入有界、工具白名单化、证据可计划、输出可校验、失败可降级。这个项目的实践可以压缩成一句话规则管边界状态管继承计划管检索门禁管引用模型管表达。当架构先把错误路径拦住4B 模型反而能在一个很窄但稳定的位置上发挥价值。说明文中数据来自示例系统内部评测记录评测集版本、硬件、并发和阈值变化后结果不能直接横向比较。
返回列表