茶器艺科智造HarmonyOS应用实战-57-Agent ID和queryText在两处各写一遍,改提示词为何容易只改一半:把智能体配置收回单一来源

HSP 的 ChaqiAgentButton 与 entry 的 ChaqiAgentPage 都声明同一个 Agent ID 和默认 queryText。当前值虽然一致,但“一致”只是这次静态检查的结果,不是工程结构能够持续保证的约束。下一次更换智能体或调整首问时,只要漏改一个入口,同一个产品就会出现两套智能体行为。

第57篇封面

本文以 Gitee 仓库 aiding521/chaqi-app-giteemaster 分支、提交 f671fcd8de277973bd7518a3c462f68384575f1e 的静态源码为事实基线。本文没有修改项目源码,也没有执行 HAP 构建、模拟器或真机验证;建议代码属于演进方案,不能描述成已经落地的功能。

一、问题不在值错,而在相同事实被写了两遍

静态源码中,ChaqiAgentButton 与 ChaqiAgentPage 都能看到 Agent ID 和默认 queryText。当前代码可以概括为:

const CHAQI_AGENT_ID = 'agent_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
queryText: '请作为茶器设计智能体,给出茶杯造型、釉色和工艺优化建议。'

为避免公开环境标识,本文示例中的 Agent ID 已脱敏;实际工程应从受控配置读取真实值。这个脱敏值不能复制回生产配置,也不能据此推断真实 Agent ID 的格式或内容。

这里最容易产生误判:两处字符串相同,于是评审者会认为“没有问题”。但源码真正暴露的是维护约束缺失——编译器不知道两个字面量必须相等,代码审查也只能靠肉眼比对。一次需求可能只打开页面文件,另一次需求可能只改按钮组件;任何一边单独提交,都有机会留下无声分叉。

二、把风险沿两个入口展开

Agent ID 决定请求所指向的智能体,queryText 决定首次进入时携带的默认问题。二者都不是某个按钮的视觉属性,也不是某个页面独有的交互文案,而是两处入口共享的业务配置。

第57篇流程图

可以把当前关系看成“两个副本分别喂给两个调用点”:

变化只改按钮入口只改页面入口用户可见后果
更换 Agent ID页面仍走旧值按钮仍走旧值不同入口可能指向不同智能体
调整 queryText页面仍发旧首问按钮仍发旧首问同一能力给出不同上下文
回滚其中一处两处再次分叉两处再次分叉问题随入口出现,难以复现
新增第三入口继续复制字面量继续复制字面量同步成本随入口数量增长

这些是由重复配置结构推导出的风险,不代表本基线已经在设备上出现了错误回复。本文能确认的是“缺少自动保持一致的机制”,不能把它扩写成“线上已经选错智能体”。

三、单一来源应只承载真正共享的字段

最小收口不是做一套庞大配置中心,而是让 Agent ID 与默认 queryText 只有一个定义位置:

export const CHAQI_AGENT_ID: string = 'agent_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
export const CHAQI_AGENT_DEFAULT_QUERY: string =
  '请作为茶器设计智能体,给出茶杯造型、釉色和工艺优化建议。';

这两个导出值表达稳定的茶器智能体业务语义。按钮标题、尺寸、颜色、是否展示以及各自的 FunctionController 仍然属于入口实例,不应因为“集中配置”而被塞进同一个全局对象。判断标准很简单:两个入口是否必须同时遵循这个值?必须一致的才进入共享定义;允许各入口独立变化的继续留在组件内。

还要区分“源码单一来源”和“运行环境配置”。本文只提出消除源码重复的方向,未验证真实 Agent ID 应来自构建配置、受控资源还是其他机制。无论采用哪种来源,都不应把真实标识写进公开文章或日志。

四、两个调用点只消费,不再声明

接回现有 FunctionComponent 时,调用处负责传参,共享模块负责给出值:

FunctionComponent({ agentId: CHAQI_AGENT_ID,
  controller: this.controller,
  options: { queryText: CHAQI_AGENT_DEFAULT_QUERY }
})

迁移时应分别检查 ChaqiAgentButton 与 ChaqiAgentPage,而不是只让其中一个改用常量、另一个继续保留字面量。完成后的理想搜索结果是:共享定义中出现一次完整默认文案,两个入口只出现常量名;旧 Agent ID 字面量也不应散落在其他调用点。

这种改动的边界很窄。它不要求重写 Controller,不要求合并两个组件,也不要求改变页面路由。把入口结构一起“大一统”反而会把可验证的配置修复扩大成组件重构,增加回归面。

五、用配置变更流程代替人工记忆

这篇的核心流程不是通用任务调度,而是“修改一次,消费多处”:

阶段操作应有检查失败时如何定位
定义在唯一模块修改 ID 或首问类型与导出名明确直接定位共享定义
引用两个入口导入同一符号不再声明同义字面量搜索旧字面量和常量名
审查比较变更范围配置变更只有一个源头发现入口内新增副本即退回
构建编译各消费模块导入路径和可见性正确记录具体编译错误
运行分别从两入口触发实际请求配置一致按入口保存日志证据

单一来源的价值不是减少两行代码,而是把“一致性”从团队约定变成依赖关系。只要入口直接导入同一导出值,修改者就不必记住另一份副本在哪里。

六、测试应围绕“不会再次分叉”

原有边界用例框架可以保留,但这里的输入和预期要换成配置语义,而不是泛泛测试任意字符串:

interface BoundaryCase<T> {
  name: string;
  input: T;
  expected: string;
}

const cases: BoundaryCase<string>[] = [
  { name: 'empty', input: '', expected: 'reject' },
  { name: 'normal', input: 'valid', expected: 'accept' },
  { name: 'repeat', input: 'valid', expected: 'idempotent' }
];

这段示意代码本身并未证明项目已有对应测试。真正需要覆盖的第一层是静态约束:空配置是否被拒绝、正常配置是否能被两个入口读取、重复读取是否不会改变值。第二层才是编译与运行:两个组件能否正确导入,分别点击后是否使用同一配置。若真实 ID 来自受控环境,还要为“配置缺失”设计明确失败,而不是悄悄回落到文章中的脱敏占位符。

七、不要把 Controller 生命周期混进配置职责

两个入口各自持有的 Controller 或监听仍由各自组件管理。共享常量解决的是配置漂移,不替代资源清理:

private disposeOwnedResources(): void {
  // timer/listener只由创建它的组件清理
  // 异步结果写状态前核对taskId或pageGeneration
  // 可恢复任务先保存业务事实,再释放进程句柄
}

如果后续发现两个入口还重复实现监听或回调处理,应另开问题评估;不能因为这次抽取常量,就默认它们的生命周期也完全相同。配置可以共享,运行时对象通常不能跨页面随意共享。把这两类职责分开,能让本次改动保持最小,也能避免全局 Controller 带来的页面残留问题。

八、验证清单要逐项对应两个入口

  1. 搜索完整默认 queryText,确认只有共享定义保留字面量。
  2. 搜索旧 Agent ID;公开记录只保留脱敏形式,真实工程按受控配置核对。
  3. 检查 ChaqiAgentButton 与 ChaqiAgentPage 都导入相同符号。
  4. 分别构建包含 HSP 和 entry 消费点的目标,记录结果而不是只写“通过”。
  5. 分别从按钮与页面入口发起一次操作,核对请求所用配置。
  6. 修改共享默认首问后重复两入口用例,确认无需第二处同步修改。
  7. 把构建、模拟器、真机、账号与网络中未执行的部分明确标成“未执行”。

静态搜索能证明字面量是否仍重复,却不能证明真实设备上的 AgentFrameworkKit 调用成功;后者必须有对应运行证据。

九、这类抽取最常见的四种误修

误修为什么仍不可靠更合适的方向
两处继续保留,只加注释提醒同步注释不会在漏改时失败让两处引用同一导出值
只抽取 Agent IDqueryText 仍可能分叉一并识别真正共享的业务配置
把所有按钮样式也做成全局配置混入允许独立变化的展示属性只集中必须一致的字段
用脱敏占位符作为运行回退可能把配置错误伪装成正常值配置缺失时明确失败并记录
看到编译通过就宣称完成未覆盖两个实际入口补充逐入口运行与请求证据

“抽常量”看似简单,评审重点却不应是文件少了几行,而应是旧副本是否全部消失、敏感标识是否仍受控、两个入口是否真的共用同一来源。

十、模块落点由依赖方向决定

第57篇结构图

项目中 entry 负责产品入口,libraryhsp 负责页面和交互,libraryhar 负责模型、算法、路由与通用服务。若 entrylibraryhsp 都能依赖承载稳定配置的共享层,常量应放在双方都可消费、又不反向依赖 UI 的位置。最终落点仍需结合仓库现有依赖验证,本文不凭静态片段虚构新的模块关系。

entry / libraryhsp -> 页面、组件、生命周期
libraryhar        -> 纯类型、纯函数、可持久化模型
tests             -> 边界、幂等、恢复序列
runtime evidence  -> 日志、设备行为、服务读回

关键约束是依赖只能从入口指向共享定义,共享层不能为了拿到页面状态反过来导入 entry 或 HSP。若现有依赖不允许两端共同引用,就应先确认一个合法的共享位置,而不是复制第三份常量来绕过编译问题。

十一、基线证据与结论边界

baseline: f671fcd8de277973bd7518a3c462f68384575f1e
scope: source-level proposal
build: not run
simulator/device: not run
network/account: not run
result: static boundary identified; implementation pending

当前证据只支持三个结论:基线提交中有两个入口、两处声明当前相同、这种结构依赖人工同步。它不支持“改造已经完成”,也不支持“真实账号调用已经验证”。后续若源码、SDK 或产品规则变化,需要重新固定仓库与 commit,再复查定义和消费点。

十二、把回归步骤写成可复现的双入口场景

验证不应停在“全局搜索只有一个结果”。最小场景是先记录共享配置基线,再分别进入 ChaqiAgentButton 与 ChaqiAgentPage,观察两条路径是否消费同一配置;然后只修改共享默认 queryText,重新构建并重复两条路径。整个过程中不应再编辑任一入口中的文案副本。

场景操作序列必须观察的证据不能替代的证据
基线搜索搜 ID 与完整首问定义点、消费点列表只看单个文件
按钮入口从按钮发起智能体能力所用配置与终态日志按钮能被点击
页面入口直接进入智能体页面所用配置与终态日志页面能正常显示
单点修改只改共享 queryText 后重测两入口同时取得新值人工比对两份字符串
配置缺失移除受控值后启动明确错误与恢复入口自动使用脱敏占位符

异步调用若需要追踪,可以继续使用带身份和代次的记录结构,但它属于运行证据,不应反过来承担配置同步:

interface OperationTrace {
  taskId: string;
  generation: number;
  startedAtMs: number;
  finishedAtMs?: number;
  terminalSource?: 'ui' | 'service' | 'restore' | 'cancel';
}

function canApplyResult(trace: OperationTrace, currentGeneration: number): boolean {
  return trace.generation === currentGeneration &&
    trace.finishedAtMs === undefined;
}

日志可以记录入口名、配置版本摘要、任务身份与终态来源,但不应记录真实 Agent ID、令牌或完整用户输入。这样既能比较两个入口,又不会用“可观测性”制造新的敏感信息泄露点。

本篇的收口标准因此很具体:共享事实只定义一次;两个入口都直接消费;脱敏值不进入真实运行回退;验证按入口分别取证。本文尚未执行这些改动和运行步骤,当前结论仍是源码级演进建议。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐