SwiftUI Agent Skill:构建真正会执行操作的iOS智能体应用
SwiftUI-Agent-Skill 这个项目名看起来不起眼但拆开看其实是三个关键词的叠加SwiftUI 负责界面Agent 负责调度决策Skill 负责具体能力。如果你正想在 iOS/macOS 上做一个带“智能体感”的应用而不是简单地在聊天框里接一个 API 再吐字那这套思路值得认真研究一遍。它解决的问题很明确让 AI 不只停留在“你说一句我回一句”而是能根据你的意图去调用应用内的真实能力比如查日程、改设置、拉取数据、执行动作。这个项目构建了一个小而完整的框架把 Agent 的调度逻辑和 Skill 的能力扩展解耦开SwiftUI 只做展示和交互底层可以接任意大模型服务也可以接本地规则引擎。适合谁来学一是有 SwiftUI 基础、想给 App 引入 AI Agent 能力的客户端开发者二是正在搭 Agent 应用但苦于 UI 层和技能层纠缠不清的工程师三是想把“技能扩展”做成插件体系、让团队可以各自开发能力模块的架构师。接下来我直接拆开给你看。1. 整体架构与设计思路1.1 为什么是 Agent Skill而不是“聊天框 大模型”很多人在给 App 加 AI 能力时第一反应是“我接一个流式接口把对话展示在 SwiftUI 列表里”。这没有错但它解决的是“对话”问题不是“做事”问题。用户说“帮我把明天的会议推迟一小时”如果只是聊天大模型只能回复一段文字说“好的您可以手动改日程”。但如果你希望它能真正调用系统的日历 API 去改就需要在 App 里有一个可被调度的能力层。Agent-Skill 的核心思想就是把 AI 系统拆成两个部分Agent 是“大脑”负责理解意图、规划步骤、决定什么时候调用什么能力Skill 是“手脚”每个 Skill 是一个独立的能力单元负责执行一个具体动作。SwiftUI 在中间只做两件事展示 Agent 的状态和内容接收用户的输入。这个分层的好处非常明显你可以先写一套 Agent 调度与 UI不需要管具体有哪些技能同一套代码加入一个新能力只需要新增一个符合协议的 SkillAgent 会自动发现并封装响应格式完全不用改界面。我实际测试这套方案时最大的感受是调试成本直线下降。聊天式方案里一旦应用逻辑出错你需要在冗长的对话上下文里找问题是模型理解错了还是代码调错了Agent Skill 方案里Agent 每走一步都有明确的“意图 → 技能 → 参数 → 结果”链路哪一步出问题一目了然。1.2 项目整体分层UI 层、协调层、能力层从具体代码结构来看整个项目可以清晰地分成三层UI 层SwiftUI 的 View 组件包含对话列表、输入栏、状态指示器、技能执行结果卡片。它不持有任何业务逻辑只是通过观察一个 Agent 状态对象来渲染。协调层Agent。负责接收用户的自然语言输入调用意图理解模块解析出“用户想做什么”匹配合适的 Skill执行调度并把结果回流到 UI 层。这个层同时管理会话上下文和状态机。能力层一组 Skill。每个 Skill 实现同一个协议声明自己的标识、描述、参数类型、执行方法。比如一个ScheduleSkill负责操作日历一个NoteSkill负责创建备忘录一个DeviceSkill负责调整硬件开关。这样的结构天然适合功能扩展。新来的同事不需要理解整个 Agent 调度流程只需要照着协议写完一个 Skill然后在注册中心声明一下这个能力就自动进入了系统。在团队协作场景里好处尤其明显各个技能之间没有耦合不会出现改一个功能把另一个搞坏的情况。1.3 Agent 的状态机设计从输入到执行的完整生命周期Agent 不能是“黑盒”它的每一步状态变化都需要能被 UI 观察到。我用状态机来管理整个生命周期定义如下状态idle等待用户输入analyzing正在理解意图和抽取参数matching正在匹配可用技能executing技能正在执行中outputting准备返回结果或渲染结果卡片completed一次完整交互结束failed出现错误每个状态都对应一个可被 SwiftUI 观察的枚举值界面上的“正在思考”、“正在处理”、“执行失败”等 UI 提示都来源于状态机的变化。这里有个小技巧SwiftUI 的动画可以跟状态变化挂钩。比如executing状态时转圈completed时切换成结果卡片用withAnimation包裹状态改变就能得到很自然的过渡效果不需要额外写过渡代码。2. 核心协议与数据结构设计2.1 Skill 协议定义能力的最小单元一个 Skill 必须有三个属性唯一标识、用途描述、参数 Schema。标识用于 Agent 内部查表描述用于让意图理解模块知道“这个技能能做什么”参数 Schema 用于校验从用户语句中抽取的参数。用 Swift 协议描述大概长这样protocol AppSkill { var identifier: String { get } var displayName: String { get } var description: String { get } var parameterSchema: ParameterSchema { get } func execute( parameters: [String: Any], context: AgentContext ) async throws - SkillResult }参数类型我用了一个可 Codable 的ParameterSchema里面定义属性名、类型、是否必填以及枚举值约束。struct ParameterSchema: Codable { let properties: [String: ParameterProperty] let required: [String] } struct ParameterProperty: Codable { let type: ParameterType let description: String let enumValues: [String]? let defaultValue: String? }意图理解模块拿到用户说的话之后从中抽出结构化参数先根据技能描述匹配到哪个 Skill再用 Schema 校验参数的合法性比如必填项是否缺失、枚举值是否越界。这个设计让 Agent 有能力做“澄清提问”当参数缺失时Agent 不会直接误调而是反问你“需要推迟到几点”。在对话体验上这一步是拉开差距的关键。2.2 技能注册中心让 Agent 知道自己“会什么”既然 Agent 是动态发现技能的就得有个注册中心来登记所有可用能力。我用一个全局的SkillRegistry来管理MainActor final class SkillRegistry { static let shared SkillRegistry() private var skills: [String: any AppSkill] [:] func register(_ skill: any AppSkill) { skills[skill.identifier] skill } func skill(for identifier: String) - (any AppSkill)? { skills[identifier] } func allSkills() - [any AppSkill] { Array(skills.values) } }注册的动作放在 App 启动阶段完成比如main struct AgentSkillApp: App { init() { let registry SkillRegistry.shared registry.register(ScheduleSkill()) registry.register(NoteSkill()) registry.register(WeatherSkill()) registry.register(SystemControlSkill()) } var body: some Scene { WindowGroup { ContentView() } } }这里必须用MainActor。技能注册通常涉及 UI 状态更新如果没有确保 MainActor 隔离可能会在并发场景里踩到 Swift 6 严格并发检查的坑后面我再细说。2.3 意图理解模块把自然语言变成可执行的意图意图理解不一定要依赖大模型。对于大多数功能型 App 来说用一套“关键词 正则 小模型”的轻量方案反而更稳、更快、成本更低。只有当关键词匹配置信度低时才把请求升级给大模型做兜底。我把意图理解拆成了三个层级精确匹配层用正则或者固定模板匹配。比如用户说“创建备忘录”直接命中NoteSkill。语义匹配层用轻量关键词向量判断意图能处理一些同义表达比如“记一下”“帮我写个备忘”都能匹配到NoteSkill。大模型兜底层前两层置信度低于阈值时调用大模型做意图分类和参数抽取并保证 Schema 校验时不直接报错而是进入澄清流程。这个三级递进设计在成本控制上非常关键。实测中大约七成的用户指令在精确匹配层就能被正确识别不需要每次请求都走大模型响应延迟也从三秒降到了毫秒级。2.4 AgentContext 上下文传递会话记忆与技能共享Skill 在执行时不一定只用当前这条消息的参数很多时候还需要访问会话上下文。比如用户先说“帮我定个两点的闹钟”下一秒又说“改成三点”第二条指令的理解就需要依赖上一条的上下文。我在AgentContext里管理三部分数据struct AgentContext { var conversationHistory: [Message] var currentIntent: Intent? var temporaryStorage: [String: Any] }conversationHistory是历史消息currentIntent是当前解析出的意图temporaryStorage是临时数据槽技能之间可以通过它共享状态。需要特别提醒的是temporaryStorage的数据要设置有效期或主动清理不然累积多了上下文会越来越大既影响大模型 prompt 的 token 消耗也可能导致状态错乱。实测中我习惯在每轮交互结束后把超过 10 分钟未更新的临时槽自动清空。3. 实操过程与核心环节实现3.1 用 SwiftUI 搭建 Agent 交互界面我们直接从核心界面讲起。主界面是一个典型的对话式交互消息列表 输入栏。消息列表里有两类消息用户消息文本Agent 回复。Agent 回复又分三种展示形态纯文本回复、技能执行中状态、技能执行结果卡片。先定义一个消息模型struct ChatMessage: Identifiable { let id: UUID let role: Role let content: String? enum Role { case user case agent } }再定义技能执行结果的状态模型struct SkillExecutionViewState: Identifiable { let id: UUID let skillIdentifier: String let status: SkillExecutionStatus let result: SkillResult? enum SkillExecutionStatus { case pending case running case success case failure } }列表视图我用List来做配合ScrollViewReader做自动滚动。关键点在于对话消息是动态追加的必须保证新消息出现时列表自动滚到底部。SwiftUI 里有个常见的坑就是List在消息追加时不总是自动滚动需要在 onAppear 或状态变化后主动调用 scrollTo。struct ChatView: View { ObservedObject var viewModel: ChatViewModel State private var inputText: String var body: some View { VStack(spacing: 0) { ScrollViewReader { proxy in List { ForEach(viewModel.messages) { message in MessageRow(message: message) .id(message.id) } } .listStyle(.plain) .onChange(of: viewModel.messages.count) { _, count in if count 0 { withAnimation { proxy.scrollTo(viewModel.messages[count - 1].id, anchor: .bottom) } } } } Divider() InputBar(text: $inputText) { await viewModel.send(inputText) inputText } } } }3.2 技能调用管线用户输入到技能执行的完整衔接用户输入一行文本后整个流程是串起来的。我把完整调用管线拆成四步这也是 Agent-Skill 项目的核心逻辑输入预处理把用户文本清洗、截断。意图解析交给意图理解模块产出Intent包含目标技能标识和参数。技能调度从注册中心取出对应技能Schema 校验参数执行。结果回流把执行结果包装成 UI 消息或结果卡片更新 SwiftUI 视图。这个流程在ChatViewModel里实现MainActor final class ChatViewModel: ObservableObject { Published var messages: [ChatMessage] [] Published var executionStates: [SkillExecutionViewState] [] Published var agentStatus: AgentStatus .idle private let agent: Agent func send(_ text: String) async { messages.append(ChatMessage(role: .user, content: text)) agentStatus .analyzing do { let intent try await agent.analyzeIntent(text) agentStatus .matching guard let skill SkillRegistry.shared.skill(for: intent.skillIdentifier) else { throw AgentError.skillNotFound(intent.skillIdentifier) } try agent.validateParameters(skill: skill, parameters: intent.parameters) let stateID UUID() executionStates.append( SkillExecutionViewState( id: stateID, skillIdentifier: skill.identifier, status: .running, result: nil ) ) agentStatus .executing let result try await skill.execute( parameters: intent.parameters, context: agent.context ) if let index executionStates.firstIndex(where: { $0.id stateID }) { executionStates[index].status .success executionStates[index].result result } if let reply result.replyText { messages.append(ChatMessage(role: .agent, content: reply)) } agentStatus .completed } catch { executionStates.append( SkillExecutionViewState( id: UUID(), skillIdentifier: error, status: .failure, result: nil ) ) messages.append(ChatMessage(role: .agent, content: 操作失败了\(error.localizedDescription))) agentStatus .failed } } }有几个细节值得注意。第一整个send方法要跑在MainActor上因为要更新Published属性。但skill.execute是异步方法不能在主线程执行重活所以每个 Skill 内部必须自己使用后台任务处理耗时操作等结果返回后再回到 MainActor 更新 UI。第二在执行状态插入 UI 时我用了一个局部stateID来标记这条状态记录确保后续更新状态时找到的是正确的那个不会在并发场景下错位。3.3 流式输出与长时任务技能执行时的界面反馈如果技能执行时间比较长比如网络请求要等十五秒用户看到的不能只是“转圈”。那体验太差了。我在项目中给技能执行做了两级反馈机制即时反馈Skill 执行一开始就往executionStates里插入一条running状态UI 立刻显示“正在执行”。阶段反馈Skill 内部可以通过一个回调闭包上报执行进度。比如下载技能可以上报“已下载 30%”这样 UI 就能显示进度条。Skill 协议里的execute支持传入一个ProgressHandlertypealias ProgressHandler (Double, String?) async - Void protocol AppSkill { func execute( parameters: [String: Any], context: AgentContext, progressHandler: (ProgressHandler)? ) async throws - SkillResult }在 UI 层每个执行状态卡片会观察执行状态当running且有进度值时渲染ProgressView(value:)。这个设计看起来不复杂但在项目里却是用户体验提升最明显的一个点。实测下来用户在等待最长的一次技能执行里能看到“正在联网查询”、“正在解析结果”、“正在生成回复”三个阶段的反馈等待焦虑感大幅降低。3.4 技能执行结果卡片比纯文本更直观的渲染同一个技能的结果不一定都要转成文本。如果是查天气直接给一个带温度、湿度的卡片比一句“温度22度湿度60%”直观多了。所以SkillResult结构里我不仅放文本回复还放了一个contentType和可选的cardPayloadstruct SkillResult { let replyText: String? let contentType: ResultContentType let cardPayload: Codable? }SwiftUI 层用一个ResultCardView根据contentType做分发渲染。比如weather类型就渲染一个 WeatherCardcalendarEvent类型就渲染一个日程条目卡片。这样的好处是UI 渲染逻辑和技能逻辑完全解耦新增一种卡片类型时只需要在ResultCardView里加一个 case技能本身不用改动。这个设计对“Agent 应用”来说是个很强的加分项。用户看到的不再是一堆文字而是有结构的操作结果App 的功能价值被视觉化体现出来了这比单纯的对话界面更有说服力。4. 技能封装与扩展实战4.1 从零封装一个可用的 Skill以系统控制为例理论讲得再多不如跟着写一个真实技能。我选一个比较简单的系统控制技能SystemControlSkill负责调节屏幕亮度、开关 Wi-Fi 蓝牙这类操作。iOS 上部分系统能力受权限限制但调节亮度是可以直接做的。struct SystemControlSkill: AppSkill { let identifier system_control let displayName 系统控制 let description 控制设备的系统级设置例如调节屏幕亮度、开关深色模式、调整系统音量 let parameterSchema ParameterSchema( properties: [ ParameterProperty( type: .string, description: 要执行的控制动作枚举值brightness、darkMode、volume, enumValues: [brightness, darkMode, volume], defaultValue: nil ), ParameterProperty( type: .number, description: 亮度值或音量值取值范围 0 到 1, enumValues: nil, defaultValue: nil ) ], required: [action] ) func execute( parameters: [String: Any], context: AgentContext, progressHandler: ProgressHandler? ) async throws - SkillResult { guard let action parameters[action] as? String else { throw SkillError.missingRequiredParameter(action) } switch action { case brightness: guard let value parameters[value] as? Double else { throw SkillError.invalidParameter(value) } let clampedValue min(max(value, 0), 1) await progressHandler?(0.5, 正在调整亮度) try await Task.sleep(nanoseconds: 500_000_000) await MainActor.run { UIScreen.main.brightness CGFloat(clampedValue) } return SkillResult( replyText: 屏幕亮度已调整为 \(Int(clampedValue * 100))%, contentType: .plainText, cardPayload: nil ) case darkMode: // 真实系统里需要访问 UIUserInterfaceStyle模拟场景省略 await progressHandler?(0.3, 正在切换深色模式) try await Task.sleep(nanoseconds: 500_000_000) return SkillResult( replyText: 深色模式已切换, contentType: .success, cardPayload: nil ) default: throw SkillError.unsupportedAction(action) } } }这个 Skill 里我特意加了一个 0.5 秒的延迟和进度回调目的是演示长时任务反馈机制。在实际项目中网络请求或复杂计算可能耗时更长进度回调的骨架是一样的。需要注意权限边界。在 iOS 上控制系统亮度是合法的UIScreenAPI但操作 Wi-Fi 开关、蓝牙开关在普通沙盒应用里是受限的这种做法更像是企业级 App 的私有方案。开发时务必校验自己目标系统版本的权限策略避免上架被拒。4.2 参数校验与失败处理不要信任大模型抽出来的参你可以把大模型或规则引擎解析出的参数理解为“不可信输入”。它可能把“下午三点”解析成15:00也可能解析成3 PM可能把“音量百分之五十”解析成 0.5也可能解析成 50。所以每个 Skill 执行前必须有独立的参数校验逻辑不能只依赖注册中心的 Schema 校验。我的习惯是在每个 Skill 的execute开头先把所有参数转换成本地强类型struct BrightnessAction { let value: Double } guard let action parameters[action] as? String else { throw SkillError.missingRequiredParameter(action) } guard let rawValue parameters[value] as? Double else { throw SkillError.invalidParameter(value) } let normalizedValue: Double if rawValue 1 { normalizedValue rawValue / 100 } else { normalizedValue rawValue }这里的关键是兼容两种表达方式用户说“亮度调到 70”解析结果可能是 70也可能是 0.7Skill 自己要能区分并归一化。类似这种边界情况在只依赖 Schema 校验时很容易漏掉必须在执行端再做一次兜底。项目里我总结了三条铁律参数缺失要有默认值或抛错不要模棱两可数值类型要做边界检查和归一化枚举字符串必须严格匹配匹配不到就说明意图理解有误应该走澄清流程而不是贸然执行4.3 组合技能一个技能内部调用其他技能单个技能只能完成单一动作但真实需求往往是多步骤的。比如用户说“帮我记一下明天下午三点的会议并设为提醒”这涉及创建日程和创建提醒两个动作。我把这个场景设计成两个 SkillScheduleSkill负责建日程ReminderSkill负责建提醒。然后在ScheduleAndRemindSkill里按顺序调用前面两个子技能。struct ScheduleAndRemindSkill: AppSkill { let identifier schedule_and_remind let displayName 日程与提醒 let description 创建一个日程事件同时为该事件创建提醒 let scheduleSkill ScheduleSkill() let reminderSkill ReminderSkill() func execute( parameters: [String: Any], context: AgentContext, progressHandler: ProgressHandler? ) async throws - SkillResult { let scheduleResult try await scheduleSkill.execute( parameters: parameters, context: context, progressHandler: { progress, message in await progressHandler?(progress * 0.5, 正在创建日程) } ) let remindResult try await reminderSkill.execute( parameters: parameters, context: context, progressHandler: { progress, message in await progressHandler?(0.5 progress * 0.5, 正在创建提醒) } ) return SkillResult( replyText: 日程和提醒都已创建开始时间是 \(scheduleResult.replyText ?? ), contentType: .composite, cardPayload: CompositePayload( schedule: scheduleResult.cardPayload, reminder: remindResult.cardPayload ) ) } }这种组合粒度在真实业务里很有用。注意progressHandler的进度折算我用progress * 0.5把子技能的进度映射到总进度的前半段后半段留给第二个子技能用户看到的就是一条平滑推进的进度条。5. 常见问题与排查技巧实录5.1 SwiftUI 视图状态与 Task 生命周期错位这是踩得最频的坑。SwiftUI 的List或者ScrollView在滚动时某些行视图会被复用或销毁如果你在技能执行过程中持有某个视图的引用结束后再往里写数据就会出现坏内存访问或者视图不刷新的问题。我的解决方案是不要在视图里持有任何异步任务的中间状态所有状态都放进 ViewModel视图只读取Published的属性。技能执行期间新建的任务如果需要访问视图一律通过闭包传回 ViewModel。这样即使视图销毁了ViewModel 还是安全的UI 重新出现时依然能读到最新状态。另外Task.sleep或网络请求中如果被取消SwiftUI 的onDisappear不会自动取消你创建的任务。我建议在 ViewModel 的deinit或某个清理时机显式调用task.cancel()否则会出现“界面已经退出技能还在后台执行然后还试图更新已释放对象”的诡异问题。这个坑很隐蔽但触发频率不低一定要提前处理。5.2 Swift 6 严格并发检查下的编译错误Swift 6 对严格并发检查收紧之后protocol AppSkill里的异步方法会要求在Sendable边界上正确标注。如果你看到Task is not sendable或者non-sendable type...的编译报错大概率是参数或者上下文类型没有标记Sendable。我踩过具体一例SkillResult里我放了Codable的cardPayload协议要求它是Sendable但 Swift 编译器并不能确认任意的Codable类型是否可跨并发域传递。解决方案是把SkillResult相关的类型统一用struct定义并且显式声明Sendable或使用unchecked Sendable。老实说这个项目如果目标部署版本支持 iOS 17 以上用 Swift 6 模式开发会更顺但如果你还在维护老项目建议在模块级别先关掉严格并发逐步适配不然第一轮迁移会花掉不少时间。5.3 技能执行结果未刷新 UI 的排查清单反馈“我执行了技能但界面没有变化”是最常见的问题。表面上是 UI 不刷新实际可能有一长串原因。我整理了一份排查顺序确认 ViewModel 是ObservableObject且属性标记了Published确认每次状态改变发生在 MainActor 上确认没有在子线程修改 UI 状态使用await MainActor.run包裹确认视图订阅正确ObservedObject还是StateObject二选一不要混用确认技能内部抛出的错误被 catch 住并转换成了可见的消息确认列表id稳定不要直接用索引值做 id不然增删消息时 SwiftUI 会误判其中“技能内部抛错但没被 UI 体现”是最隐蔽的。我在execute里抛了SkillError.invalidParameter但 catch 块里没把错误内容转成 UI 消息调试了半小时才发现错误信息丢在error.localizedDescription里没展示出来。建议在开发阶段给每个 catch 加可视化日志不要只看控制台的print。5.4 内存与性能优化上下文膨胀与技能卸载Skill 数量增多后内存问题和性能问题会逐渐暴露。最典型的是上下文膨胀。每个技能执行都会把新的上下文写进AgentContext.temporaryStorage如果不清除运行时间一长这个字典会越来越大大模型请求的 token 数也越来越大耗时长、成本高。我在项目里给临时上下文设了生命周期管理每个上下文条目带一个createdAt时间戳每次新交互开始前检查过期条目。超过 10 分钟的临时数据一律移除。对于重要的会话记忆则写进持久化层不用临时存储来承载。还有一个容易被忽略的点是缓存。如果用户问同一类问题比如“今天天气怎么样”每个技能实现里可以考虑对结果做短时缓存比如 5 分钟有效期的天气缓存。这个优化能把响应时间从秒级降到毫秒级体验差距非常明显。但要注意缓存数据不能跨状态持久化太久否则用户会看到过期数据。项目里我用NSCache加有效期双重控制既保证速度也保证准确性。5.5 日志与可视化调试没有可观察日志的 Agent 系统是没法调的主。我的做法是给 Agent 增加一个调试模式输出每个阶段的状态和时间戳[12:00:01.123] 用户输入: 帮我设置明天早上8点的闹钟 [12:00:01.130] 意图分析: matcherkeyword, confidence0.94, skillalarm [12:00:01.135] 参数抽取: { time: 08:00, date: 2025-12-05 } [12:00:01.140] 技能匹配: alarm, 参数校验通过 [12:00:01.145] 技能执行: start [12:00:01.652] 技能执行: success, duration507ms [12:00:01.660] UI 更新: result card rendered这套日志配合时间戳能快速定位延迟瓶颈。比如发现situation analysis阶段耗时特别长那就是意图理解模块的问题如果skill execution耗时异常那就是具体 Skill 的实现问题。日志输出我直接用统一的中间件抽象不散落在各个 Skill 里后续接入第三方日志平台时只需要改一个地方。6. 写在最后经验总结与后续扩展建议写完这个项目之后我最大的一个体会是Agent 应用开发的难点不在“接入大模型”而是“如何把大模型的能力无损地接入到你已有的应用能力体系里”。SwiftUI-Agent-Skill 提供了一个比较清晰的答案把能力封装成协议把调度从 UI 中剥离把状态管理集中在 ViewModel把执行反馈做成用户可以感知的进度。这种结构即使不接大模型纯用规则引擎也可以跑得通本质上前端应用的“工具调用体系”和“插件体系”是同一件事。如果你打算基于这个项目继续扩展这几个方向值得优先尝试。第一把意图理解模块替换成可插拔的模型服务比方说你已经在某个模型平台上注册好服务只需要把匹配层的结果作为兜底降级这个框架不需要大改。第二把技能注册改成动态化从服务器拉取技能配置让 App 可以在不发布新版本的情况下接入新能力。第三在 Agent 层加一个向量记忆库让 Agent 能记住用户的偏好比如“我一般把会议定在下午”这样后续在澄清提问时会聪明很多。最后分享一个小技巧在新版本迭代时我会对每个技能做一个“自检模式”即用固定的测试用例直接触发技能执行不经过意图理解层单独验证技能逻辑的正确性。意图理解层再单独跑一组自然语言用例。这样分层测试定位 bug 的速度比端到端测试快得多。你在实际项目里可以照搬这套做法能把集成期的大部分调试时间省下来。

相关新闻

Linux进程间异步通信:POSIX消息队列实战指南

Linux进程间异步通信:POSIX消息队列实战指南

1. “飞鸽传书”不是童话,而是Linux进程通信的古老隐喻“如何科学养一只鸽子为你飞鸽传书”——看到这个标题,你大概率会笑出声。但别急着划走。这根本不是养鸟指南,而是一篇扎扎实实讲Linux进程间异步通信(IPC)机制的…

2026/10/14 9:22:03 阅读更多 →
用Headroom本地代理压缩上下文,为Claude Code与opencode省下Token开销

用Headroom本地代理压缩上下文,为Claude Code与opencode省下Token开销

最近两周我一直在折腾一件事:把 Claude Code 和 opencode 的 API 请求都改道到本地的 Headroom 代理上,让它先压缩一遍对话历史,再发往模型服务端。起因很实际——前阵子接手一个不算小的代码库,一次连续几小时的重构会话结束后&a…

2026/10/14 9:22:03 阅读更多 →
大模型Agent技能管理实战:从提示词拆解到语义路由与安全编排

大模型Agent技能管理实战:从提示词拆解到语义路由与安全编排

如果你也在做大模型 Agent,一定见过这种场景:系统提示词越写越长,里面塞满各种工具的使用说明、参数格式、注意事项,模型还是经常把日期传错、把 A 工具和 B 工具搞混、出了问题根本没法定位。我们内部这个代号叫 agent-skills 的…

2026/10/14 9:21:01 阅读更多 →

最新新闻

单片机毕设项目:基于51单片机的老人跌倒报警定时定位上报系统设计 基于单片机的老人安全定位监护与跌倒报警系统设计(032602)

单片机毕设项目:基于51单片机的老人跌倒报警定时定位上报系统设计 基于单片机的老人安全定位监护与跌倒报警系统设计(032602)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/14 15:14:02 阅读更多 →
从TOPS到TCO:企业算力规划与成本优化实操指南

从TOPS到TCO:企业算力规划与成本优化实操指南

我不准备逐字背诵任何规范清单,直接按一个资深从业者的口吻,把这篇关于云栖大会与算力规划的文章写透。下面就是正文。1. 云栖大会现场的几个直观信号今年云栖大会逛下来,最强烈的感受是:算力这个词,已经从技术圈的黑话…

2026/10/14 15:14:02 阅读更多 →
PowerShell别名完全指南:从Set-Alias到函数模拟与持久化配置

PowerShell别名完全指南:从Set-Alias到函数模拟与持久化配置

你有没有算过自己每天在 Windows 终端里重复敲了多少次长命令?我最开始折腾 PowerShell 设置命令别名,纯粹是因为Get-Service、Get-Process这种命令不长不短,每次敲都容易在中间卡一下,干脆设一个别名把它缩短。这个问题听起来小&…

2026/10/14 15:14:02 阅读更多 →
SpringBoot+Vue粮仓管理系统架构设计与实现

SpringBoot+Vue粮仓管理系统架构设计与实现

1. 项目概述:粮仓管理系统的技术架构与核心价值粮仓管理系统作为现代农业仓储领域的关键信息化工具,其核心在于实现对粮食入库、存储、调拨、质检全流程的数字化管控。这个基于SpringBootVue的前后端分离方案,完美解决了传统单机版管理软件存…

2026/10/14 15:14:01 阅读更多 →
seisplotjs:浏览器端解析miniSEED与绘制地震波形实战

seisplotjs:浏览器端解析miniSEED与绘制地震波形实战

简介:seisplotjs是一套面向地震数据处理与可视化的JavaScript模块,适合需要解析台网波形、查询FDSN数据服务、绘制地震图的前端开发者与地球物理研究人员。压缩包内共184个文件,体积约1.02MB;其中83个js文件构成核心逻辑&#xff…

2026/10/14 15:14:01 阅读更多 →
Docker优缺点深度剖析:从环境一致性到生产级实践避坑指南

Docker优缺点深度剖析:从环境一致性到生产级实践避坑指南

1. 从一次线上事故说起:为什么我又把Docker翻出来重新审视前阵子帮一个朋友排查他那个小团队的项目部署问题,四个人开发,测试环境跑得好好的,一上生产就各种“在我电脑上明明是好的”。这种场景你应该不陌生,我这些年听…

2026/10/14 15:13:01 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →