news 2026/10/13 16:14:30

SwiftUI接入Agent+Skill:从聊天式AI到技能编排的架构重构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SwiftUI接入Agent+Skill:从聊天式AI到技能编排的架构重构

1. 从“聊天式AI”到“技能编排”:SwiftUI应用接入Agent的一次架构重构

过去一年我一直在做一款面向个人效率管理的原生应用,用SwiftUI搭界面、SwiftData存数据。功能做到一半发现一个尴尬问题:市面上的AI能力基本都停留在“聊天”层面,用户问一句、模型回一段话,然后呢?没有然后。对效率工具来说,这种交互是断裂的——用户需要的不是一段文字,而是让App替他完成一个动作。

后来我把思路从“Chat”转到了“Agent + Skill”上,也就是这个项目名SwiftUI-Agent-Skill的由来:SwiftUI负责渲染与交互,Agent负责理解用户意图和调度,Skill则是一组可执行的具体能力单元——查天气、记备忘、启动计时器、读取健康数据,每一项都是一个独立技能。用户只需要说一句模糊的话,Agent解析后自动匹配技能、执行动作、把结构化结果交还给UI层渲染。

这篇文章是我在自建这套体系过程中沉淀下来的完整拆解,内容包括:为什么传统的“API直连”模式不适合复杂App、技能协议怎么设计才能不绑架业务代码、Agent调度器如何做意图路由与回退、以及我会重点讲几个线上踩过的坑——比如技能注册顺序导致的诡异路由问题、Task里更新UI状态的线程冲突、还有大模型返回格式不稳定时的兜底策略。

如果你跟我一样是SwiftUI开发者,正琢磨怎么把AI能力“嵌入”到界面和业务流里,而不是做一个孤零零的对话框,这篇文章应该能帮你在动手之前少走弯路。

2. 为什么我放弃了“UI层直接调大模型”的写法

很多人的第一个念头很直接:界面里有一个输入框,用户输入文本,我直接把文本拼成Prompt发给云端模型,把返回的Markdown渲染出来。这套写法做Demo没问题,但往真实业务里一放就露馅了。

2.1 UI直连的三个致命缺陷

第一个缺陷是意图与动作的割裂。用户说“明天早上八点提醒我买牛奶”,模型能理解这句话,但它没有能力直接写入你的提醒列表。你需要自己去解析返回值、自己写解析规则、自己处理“模型没按格式返回”的意外。这等于把Agent的一大部分工作又甩回给了UI层。

第二个缺陷是技能无法复用。如果只有一个页面这样写,倒还勉强能接受。但当你需要多个页面共享同一套能力——比如首页、语音快捷指令、小组件都要调用“查询天气”这个动作——你只能在每个地方都写一遍解析逻辑,稍有不同步就会出现“首页能用、小组件失灵”的割裂局面。

第三个缺陷是状态同步的灾难。SwiftUI是声明式UI,一切围绕状态驱动。但大模型的返回是异步、非结构化的,你无法预测它会返回什么字段,也就无法干净地映射到View Model的State里。结果是代码里到处是if let嵌套、类型强转、默认值兜底,维护成本直线上升。

2.2 Agent-Skill模型如何解决这些问题

我采用的方案是给App加一层薄薄的“技能编排层”:

  • Skill(技能):一个最小可执行单元,有名称、有描述、有入参定义、有返回类型。它不关心自己是被谁调用的,只负责完成一个动作并返回结构化结果。
  • Agent(调度者):接收用户原始输入,通过大模型做“意图解析”,判断应当调用哪个技能、传什么参数;再把技能返回的结果整理成UI可以直渲染的结构化数据。
  • SwiftUI ViewModel(绑定层):暴露用户输入入口,监听Agent的执行状态流,把技能结果映射到界面状态。

这样带来的最大好处是:界面代码不再直接面向大模型,而是面向一组稳定、可预期的技能接口。大模型在任何环节返回了意外格式,都被封在Agent调度层里面消化掉,UI层看到的永远是你定义好的状态模型。

2.3 何时不该用这套架构

坦率讲,这套方案有额外成本,不是所有场景都值得上。如果你的需求只是“AI问答”“文档总结”“把用户输入润色一下”,那直接一个流式返回界面就够了,不需要技能编排。

我当时判断的标准是三条:

  1. 用户输入是否天然包含“做某事”的意图(如提醒、查询、记录、控制)?
  2. 多个页面或入口是否共享同一批动作能力?
  3. 技能执行结果是否需要触发UI状态变化或系统能力(如写数据库、发通知)?

三条里中两条以上,就可以考虑引入Agent-Skill层。只中一条的话,还是老实做直连吧。

3. 核心架构设计:Skill协议、Agent调度器与SwiftUI状态机

我最终落地的架构分为三层,责任边界非常清晰。先把三层各自负责的内容说清楚,再讲每层的具体设计。

3.1 Skill协议的最小定义

所有技能必须遵循统一的协议。我最初设计协议时分了两版,第一版过度设计,约束太多导致每个技能都背着沉重的模板代码,后来砍到了最核心的四件事:

/// 所有Agent技能的公共协议 protocol AgentSkill: Identifiable { /// 技能唯一标识,用于注册与路由 var id: String { get } /// 技能的人类可读名称,用于调试日志 var displayName: String { get } /// 技能的语义描述,Agent用它对用户意图做匹配 var semantics: String { get } /// 技能的入参模型(JSON可解码) associatedtype Input: Codable /// 技能的出参模型(JSON可编码) associatedtype Output: Codable /// 执行一次技能,返回结构化结果 func execute(input: Input) async throws -> Output }

重点说一下semantics字段。这是整个协议里最容易被忽略但最重要的一环。调度器在匹配意图时,靠的不是技能ID,而是这个描述文本。写得好不好,直接决定大模型能否准确路由。

比如计时器技能:

"startTimer"技能可以启动一个倒计时器。使用场景:用户说出类似 “三分钟后提醒我”“帮我定个八分钟的闹钟”“倒计时半小时”等带有时间 长度和目的的表达。入参中duration是倒计时秒数,timerName标识计时用途。

这里要写得像一个“使用说明书”,带有典型例句,而不是干巴巴一句话“启动计时器”。我实测下来,描述越贴近用户可能说的自然语言,路由准确率越高。

3.2 Agent调度器:意图、参数与回退三步走

调度器负责串联“收到一句话”到“技能出结果”全流程。我把它的实现拆成了三个步骤,每一步都有明确的边界和错误处理策略。

第一步:意图解析(Intent Resolution)

把用户原文发给大模型,要求输出一个结构化的“意图调用包”,包括技能ID和JSON格式参数。为了保证格式稳定,我给模型写了一套非常严格的输出规范:

{ "skill_id": "startTimer", "input": { "duration": 480, "timerName": "煮鸡蛋" }, "confidence": 0.95 }

这里有个残酷的经验:不要相信大模型会老老实实按你的JSON Schema输出。它经常会把字段名大写、多写注释、把数字当字符串、甚至在JSON外面包一层Markdown代码块。所以我的调度器在拿到原始响应后,会先做一层“JSON净化”——把代码块包裹剥掉、去掉注释、再传给JSONDecoder做容错解码。这层净化逻辑我后面单独讲。

第二步:技能路由(Skill Routing)

拿到意图调用包后,调度器在注册表中查找对应的技能处理器。查找不到时不会直接报错,而是把“未匹配的意图”作为一个独立技能重新交给大模型,让它生成一句友好的引导语,告诉用户当前可用的能力有哪些。

第三步:结果润色(Result Polishing)

技能执行完成后,输出结果可能是一段文本、一组数据、或一个错误。调度器会把这些结果统一封装成AgentMessage类型,附带执行状态、耗时、时间戳,再交给UI层。

3.3 SwiftUI状态机的五态模型

界面层需要能清晰反映Agent的执行状态,用户才知道当前发生了什么——是正在理解、正在执行、还是执行失败了。我设计了一个五态枚举,并且通过@Observable驱动界面更新:

/// Agent执行状态,直接映射到SwiftUI界面 enum AgentExecutionState { /// 空闲,等待用户输入 case idle /// 正在把用户意图解析成技能调用 case parsing /// 正在执行某个技能,附带技能名称 case executing(skillName: String) /// 执行完成,附带结构化结果 case finished(AgentMessage) /// 执行失败,附带错误信息 case failed(String) }

这五态对应到界面上就是:输入框可编辑/提交后转圈+A面文案/正在执行的技能名称展示/结果卡片刷新/错误横幅提示。状态切换全部发生在MainActor上,避免后台上报状态时触发SwiftUI的线程安全崩坏。

3.4 技术选型的几个关键决定

  • 基类视图模型用@Observable宏(需要iOS 17+)。相比ObservableObject,它更轻量、更新更精准,配合@Environment注入可以避免多层传递。
  • 所有技能执行用async/await,不使用回调闭包。回调嵌套在技能数量一多以后,排查链路会让你怀疑人生。
  • 超时控制统一在调度器做,技能各自不处理超时。记住:大模型服务可能在任何时候不响应,30秒无返回就必须走失败分支。

4. 工程落地:依赖选择、权限声明与调试基建

架构设计完,真正往工程里落的时候,我发现有一堆跟架构无关但直接影响稳定性的细节。这些坑属于“文档里不会写、不踩不知道”的那种。

4.1 最低系统版本与依赖清单

我把最低部署版本定为iOS 17,原因有三:

  1. @Observable宏和@Environment注入需要iOS 17。
  2. SwiftData在iOS 17才稳定,我后续的技能实现大量用到它。
  3. iOS 17的设备在2025年的渗透率已经足够高,不值得为老版本牺牲架构简洁性。

依赖方面我只保留了两类:一类是网络请求层(任意封装的URLSession或轻量网络库),一类是JSON解析层。大模型的接口调用我没有引入SDK,而是直接用URLSession构造HTTP请求。这样做的好处是技能层完全不受具体模型厂商的SDK绑定,换模型服务商只改调度器里的一个网络客户端实现,技能层零改动。

4.2 Info.plist权限声明清单

技能要访问系统能力,权限声明是第一个暗坑。我整理了一张表,照着配就行:

权限Info.plist Key何时触发备注
网络无(默认允许)技能需访问云端时ATS需允许HTTPS
通知无(需用户授权)计时/提醒技能使用UNUserNotificationCenter
健康数据NSHealthShareUsageDescription读取健康统计技能还分读/写两种,要分别声明
麦克风NSMicrophoneUsageDescription语音输入技能(如有)仅App内语音识别时需要
文件/照片视具体技能文件读写技能用UIDocumentPicker可绕过

有一个经验:健康数据的权限描述文案一定要写到具体功能。我遇到过审核因为“描述太泛”被拒的情况,后来改成“用于在健康统计技能中展示您的步数和心率趋势”,才顺利通过。这属于审核经验范畴,但技术上不写清楚一样会出问题。

4.3 技能沙盒调试日志

技能多了以后,定位问题最大的敌人是“不知道哪一步出了问题”。我给整个链路加了一条贯穿式调试日志,每个技能执行时自动打印以下内容:

  • 用户原文
  • Agent解析出的技能ID
  • 原始大模型响应(完整保留)
  • 净化后的JSON
  • 技能入参解码成功与否
  • 技能执行耗时
  • 技能出参编码成功与否

打印到一个统一的格式里。可读之后,排查效率会提升一个量级——你能一眼确认问题出在意图识别、入参解码还是技能执行本身。后续做离线回放测试时,这些日志还能当测试样本用。

5. 四项可复用的Agent技能实现详解

架构和工程准备就绪后,最关键的就是技能本身。我从实际场景里挑了四个典型技能展开讲:计时器(本地动作)、天气查询(网络动作)、健康统计(系统权限动作)、备忘管理(数据持久化动作)。这四个技能覆盖了大多数SwiftUI App会用到的技能类型。

5.1 计时器技能:本地不依赖网络的技能范式

计时器是“技能需要主动触发UI更新”的一个好案例。基础实现如下:

/// 计时器技能的入参 struct TimerInput: Codable { /// 倒计时秒数 let duration: Int /// 计时关联的用途名称,如“煮面”“午休” let timerName: String } /// 计时器技能的出参 struct TimerOutput: Codable { /// 倒计时结束时间(ISO8601字符串) let fireDate: String /// 展示文本 let message: String } /// 计时器技能 struct StartTimerSkill: AgentSkill { let id = "startTimer" let displayName = "设置倒计时" let semantics = "用户想要启动一个倒计时或定时提醒时触发……" func execute(input: TimerInput) async throws -> TimerOutput { let fireDate = Date().addingTimeInterval(TimeInterval(input.duration)) let content = UNMutableNotificationContent() content.title = "定时提醒" content.body = input.timerName let trigger = UNTimeIntervalNotificationTrigger( timeInterval: TimeInterval(input.duration), repeats: false ) let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: trigger ) try await UNUserNotificationCenter.current().add(request) return TimerOutput( fireDate: ISO8601DateFormatter().string(from: fireDate), message: "已设置\(input.duration)秒倒计时:\(input.timerName)" ) } }

有两个细节要提醒:一是UNUserNotificationCenter.add本身是异步安全的,可以直接await;但需要在调用前确保通知权限已经申请过。二是计时器结束后面向UI的反馈,不应该由计时器技能直接把状态推到界面——技能只负责“创建通知”,界面监听通知中心的回调来更新UI状态。这样技能保持纯粹,界面逻辑不会被技能代码绑架。我最初把“更新UI状态”写进了技能内部,导致计时器技能深深耦合了视图模型,后面重构才拆开的。

5.2 天气查询技能:处理外部服务的不稳定性

天气查询涉及外部网络服务。技能内部封装了“地理位置获取、天气数据拉取、格式整理”三条链路。

struct WeatherInput: Codable { /// 城市名或经纬度字符串 let location: String } struct WeatherOutput: Codable { /// 温度(摄氏度) let temperature: Double /// 天气状况文案 let condition: String /// 今日最高/最低温 let high: Double let low: Double } struct WeatherSkill: AgentSkill { let id = "queryWeather" let displayName = "天气查询" let semantics = "用户询问某地天气、温度、是否下雨等……" func execute(input: WeatherInput) async throws -> WeatherOutput { // 1. 地理编码:把城市名或自然语言描述转成坐标 let coordinate = try await Geocoder.shared .coordinate(for: input.location) // 2. 拉取实时天气 let weather = try await WeatherService.shared .fetchRealtime(coordinate: coordinate) // 3. 映射为结构化输出 return WeatherOutput( temperature: weather.temperature, condition: weather.conditionText, high: weather.high, low: weather.low ) } }

这里的核心难点不是天气服务本身,而是用户输入的位置描述非常不标准。用户可能说“我这儿的天气”“北京天气”“明天杭州会不会下雨”。三句话对应三种解析深度。我的处理是:第一类直接取系统定位权限后的当前位置;第二类走地理编码;第三类需要通过Agent解析出“明天”这个时间语义,再用天气服务的历史预报接口查数据。

外部服务的另一大问题是可能下线或变慢。我给这类技能统一加了重试机制:第一次失败后隔两秒重试一次,最多重试三次。如果三次都失败,调度器会走失败分支,把“天气服务暂时不可用”的明确提示返回给UI,而不是直接抛一个看不懂的底层错误。

5.3 健康统计技能:系统权限与数据整合的实现细节

健康数据是系统权限最重的技能,写起来很考验对HealthKit框架的熟悉程度。

struct HealthStatInput: Codable { /// 统计类型:步数、心率、睡眠等 let metricType: String /// 时间范围:今天、本周、本月 let period: String } struct HealthStatOutput: Codable { /// 数值数组(按天排列) let values: [Double] /// 日期标签(便于UI展示) let dateLabels: [String] /// 汇总信息 let summary: String } struct HealthStatSkill: AgentSkill { let id = "queryHealthStat" let displayName = "健康数据统计" let semantics = "用户可以要求App读取并展示健康数据……" func execute(input: HealthStatInput) async throws -> HealthStatOutput { let type: HKQuantityType switch input.metricType { case "步数": type = HKQuantityType(.stepCount) case "心率": type = HKQuantityType(.heartRate) case "睡眠": type = HKCategoryType(.sleepAnalysis) default: throw SkillError.unsupportedMetric } // 聚合查询最近的周期数据 let samples = try await HealthStore.shared .querySamples(type: type, period: input.period) // 转成按天聚合的数组 return aggregate(samples, period: input.period) } }

写这类技能最重要的经验是:HealthKit查询必须在HKHealthStore()的正确实例上进行,而且iOS 17以后的查询接口统一改成了异步版本,老的闭包式写法还能用但不推荐。另外一个坑是权限——一次只能请求一个数据类型的读取权限,多个类型一起请求会出现不必要的权限弹窗轰炸,用户拒绝率极高。所以我在技能前先做了一次“引导授权页”,让用户一次性勾选多个权限,技能只负责在授权通过后读取数据。

5.4 备忘管理技能:SwiftData持久化与UI实时联动

最后一个技能演示了技能如何跟SwiftData协同工作。用户的原始需求可能是“记一下我明天下午要开会,顺便提醒我带笔记本”——这是一个非常典型的模糊多意图输入。

struct MemoInput: Codable { let title: String let dueDate: Date? let reminderBody: String? } struct MemoOutput: Codable { let memoID: String let createdDate: Date let actionableCount: Int } struct CreateMemoSkill: AgentSkill { let id = "createMemo" let displayName = "快捷备忘" let semantics = "用户需要快速记录一段事项……" func execute(input: MemoInput) async throws -> MemoOutput { // 1. 写入SwiftData let memo = MemoItem( title: input.title, createdAt: .now, dueDate: input.dueDate ) let context = ModelContext() context.insert(memo) try context.save() // 2. 如需提醒,创建本地通知 if let dueDate = input.dueDate, let body = input.reminderBody { try await scheduleNotification(date: dueDate, body: body) } return MemoOutput( memoID: memo.id.uuidString, createdDate: .now, actionableCount: 1 ) } }

不过需要注意:SwiftData的ModelContext不能跨线程使用——技能在后台Task里执行时,要么在任务内部创建独立的ModelContext,要么通过MainActor.run切回主线程操作。我采用的方式是让“写数据库”这个环节强制回主线程执行,虽然牺牲一点性能,但换来的是记忆一致性,实际体感差别很小。

还有一件重要的事:备忘创建成功后,UI列表需要自动刷新。我的解法是调度器在技能完成后发出一条“数据变更”广播,所有监听备忘列表的视图模型收到后重新查询SwiftData。技能本身不关心谁在监听,只负责把数据写进去,查询和刷新是界面层的责任。

6. 线上踩坑实录:线程冲突、注册失效与大模型格式炸弹

架构跑通之后,我一度以为可以高枕无忧了。真到线上测试,发现三个问题几乎榨干了我的排查耐心——现在都写出来,希望你能避免。

6.1 Task里更新UI状态引发的“MainActor隔离”崩溃

这个坑是这样的:技能在后台Task里执行,执行完了直接调用了self.state = .finished(result)更新视图模型,然后就崩了。错误信息指向MainActor隔离。

原因很好懂:SwiftUI的视图模型必须在主线程更新状态。async/await的优雅之处在于它会自动帮你在两套执行上下文之间切换,但前提是视图模型类标注了@MainActor。我的视图模型当时漏掉了这个标注,导致编译器没有强制主线程更新,运行期才炸。

修复方式很简单,给视图模型加一行标注:

@MainActor @Observable final class AgentViewModel { var state: AgentExecutionState = .idle // ... }

这个坑典型的教训是:任何持有UI状态的类,从一开始就标注@MainActor,别等崩溃了再去补。异步代码在主线程隔离和后台并发混合时,编译器不会永远帮你兜底。

6.2 技能注册顺序导致的路由失效

第二个坑更隐蔽。我一开始把技能注册表设计成了“顺序遍历匹配”,最先匹配到的技能生效。表面上没问题,但实际的语义匹配是基于大模型的,模型并不是按描述接近度排序的——它是在所有可用技能里“选一个最像的”。注册顺序会影响模型能看到哪些技能,但不影响最终匹配结果。

问题真正出现在技能注册表更新不及时上。我用了懒加载注册,第一次执行Agent前才把技能列表注入。后来在运行时动态增加了一个“查快递”技能,但没有触发重新注册,调度器里仍然查不到它——更糟糕的是,大模型此时仍然能看到新增能力的描述,导致它生成了“查快递”的意图包,路由层却找不到处理器,最后只能走失败分支回退。

修复方案是放弃懒加载,改为启动时一次性全部注册,并把注册表设计成“快照不可变”的模式。要加新技能,必须像发版一样走完整注册流程,而不是热插拔。这样虽然少了点动态性,但换来了确定性,我后来还想搞技能热更新,想想还是作罢——稳定性优先。

问题类型表现根因修复方案
注册顺序部分意图匹配错技能注册表未全量刷新启动时全量注册,不可变快照
线程崩溃UI状态更新时抛MainActor错误视图模型未标注@MainActor视图模型统一标注
模型格式JSON解码失败率30%大模型输出不规范净化层+容错解码+强约束输出

6.3 大模型格式炸弹与容错解码

这条是最让我头疼的。我实测用了两套不同的云端模型服务做对比,同一套Prompt,格式化输出成功率从95%到60%不等。后者简直是一场灾难。

最后我总结出的对抗策略是“三层防线”:

第一层,Prompt强约束。在Prompt末尾加上“你必须只输出一个JSON对象,不要包含任何解释性文字、Markdown标记或注释。若无法理解用户意图,输出:{\“skill_id\”:null}”。

第二层,净化器。把大模型返回的字符串预处理一遍:剥离首尾的反引号包裹、去除json标记、裁掉说明性文字的前缀后缀。净化后交给默认解码逻辑。

第三层,容错解码。多个已知容错路径进行尝试:

static func decodeAgentCall(from raw: String) throws -> AgentCall { // 第一遍:净化后直接解码 let cleaned = purifyJSON(raw) if let data = cleaned.data(using: .utf8) { do { return try JSONDecoder().decode(AgentCall.self, from: data) } catch { // 第二遍:尝试把字符串值转成数字(模型偶尔会给数字加引号) let repaired = repairNumericQuotes(cleaned) ... } } // 兜底:从原始字符串中用正则抽取关键字段 throw AgentError.undefinedIntent }

这一套下来,解码失败率从30%降到了大约1%。剩下的1%用引导语兜底。

6.4 技能超时与资源释放

最后一个经验是:技能必须要有一个“全局超时”机制。我最开始没在意,某个网络技能在外部服务挂掉时一直等待,直到用户界面卡死。后来在调度器里加了统一的Task超时封装,单个技能最长执行时间限制为20秒,超时自动转入失败状态,同时取消底层网络请求。

func runWithTimeout<T>(_ operation: () async throws -> T, timeout: Duration = .seconds(20)) async throws -> T { try await withThrowingTaskGroup(of: T.self) { group in group.addTask { try await operation() } group.addTask { try await Task.sleep(for: timeout) throw SkillError.timeout } // 第一个完成的任务胜出,其余自动取消 guard let result = try await group.next() else { throw SkillError.emptyResult } return result } }

这里有个细节:withThrowingTaskGroup会等所有子任务完成才返回,所以如果超时任务先抛错,操作任务还在挂起,会导致资源不释放。需要在超时分支里主动调用group.cancelAll(),再抛错。这个小细节困惑了我整整一个下午。

7. 进一步演进:从单Agent到多Agent协作与离线意图识别

现在这套架构跑得已经很稳了。但如果你的需求继续变大,有几个方向是值得往下走的。

7.1 多Agent按领域拆分

技能数量超过15个以后,单个Agent的语义空间变得过于拥挤,路由准确率开始下降。我的解法是拆成多个Agent:有一个日程调度Agent负责所有跟时间相关的技能,一个数据查询Agent负责外部信息获取,一个系统控制Agent负责设备本地操作。

调度层再加一个“主控Agent”,先判断用户意图属于哪个领域,再交给对应领域的Agent处理。这个层级关系做出来后,任务划分的粒度反而变清晰了,新增技能时不需要动主控逻辑,只往对应Agent注册即可。

7.2 端侧小模型做意图初筛

大模型在线解析有一个无法回避的问题:延迟。哪怕一次意图解析只要800毫秒,也比纯本地方案慢很多。我测试过把“意图解析”拆成两步:先用端侧小型分类模型判断意图属于哪个领域(比如“计时”“查询”“备忘”),只有领域内技能确实不匹配时才需要大模型介入。

不过端侧模型对中文语义理解能力波动大,我目前的策略是“高置信度才走端侧,低置信度全部走云端”。这样既保证了大部分简单交互的低延迟,又不会因为端侧误判导致体验割裂。

7.3 技能结果缓存与离线可用性

网络型技能(天气、新闻等)天然依赖在线。但“缓存”和“最后成功结果”可以在断网时大幅提升体验。我的做法是:每个网络技能在成功执行后,把输出结果编码存档到本地;下次执行时如果网络请求失败,返回缓存结果并附上“数据更新于几小时前”的标注。这种方法简单、实用、而且很受用户欢迎。

8. 给想上手的人一句话:从小处做起

最后说点个人体会。SwiftUI-Agent-Skill这套东西刚有雏形的时候,我也犯过一个典型错误:想着把所有功能一次性全部技能化,结果做了两个星期发现一半技能根本用不上,反而消耗了巨大精力。

后面学乖了,从最核心的三个高频场景开始——计时提醒、快捷备忘、天气查阅——这些是用户每天都在用、且意图足够清晰的功能。先把这三个跑通,再逐步扩展。Agent和Skill的设计红利不在于一开始就搭一个庞大的宇宙,而在于你后面每加一个新技能,都只写一个实现、注册一行,UI层不用动。这种“渐变式扩展”的舒适感,才是这套架构最大的回报。

如果你准备自己动手,建议先把AgentSkill协议和调度器的三层流程(意图解析、路由、结果处理)跑通,再往里填技能。协议和调度是地基,技能是砖瓦。地基搞得足够扎实,后面每次盖楼才会顺手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 16:14:14

秒杀系统异步下单全链路设计:Redis预扣与MQ削峰实战

1. 项目概述&#xff1a;为什么秒杀系统必须走异步下单做过电商业务的开发者&#xff0c;十有八九都遇到过秒杀场景。商品数量有限、瞬间涌入大量用户、并发请求动辄上万&#xff0c;这时候如果还按照普通的“同步下单、同步扣库存、同步返回结果”链路去做&#xff0c;系统会在…

作者头像 李华
网站建设 2026/10/13 16:12:32

精准测试用例集缩容:如何用 20% 的核心单测覆盖 90% 的业务高频路径

在大规模微服务持续集成的质量保障体系中&#xff0c;测试工程师往往面临着一个经典的“膨胀陷阱”&#xff1a; 随着系统历经数年的迭代演进&#xff0c;单元测试与集成测试用例库逐渐膨胀到令人窒息的规模——数千个用例堆叠在仓库中&#xff0c;测试套件的整体运行时间被无情…

作者头像 李华
网站建设 2026/10/13 16:12:09

Spring Boot宠物商城源码全解析:从建表到支付回调的实战指南

拿到一套基于Spring Boot开发的宠物用品商城源码&#xff0c;还带着完整文档&#xff0c;这事放在平时其实挺常见的。但如果你真打算靠它学东西或者直接改造成自己的项目&#xff0c;光会启动、会跑起来是远远不够的。我前后把这套商城源码完整过了一遍&#xff0c;从建表到下单…

作者头像 李华
网站建设 2026/10/13 16:11:34

广工大OS实验:基于Linux 2.6内核的进程调度、内存管理与文件系统实战

简介&#xff1a;本资源是广东工业大学操作系统课程配套的完整实验实践包&#xff0c;面向计算机专业本科生及操作系统初学者&#xff0c;聚焦进程调度、作业调度、主存管理与文件系统四大核心模块&#xff0c;助力理论知识向底层实现能力转化。压缩包共12个文件&#xff0c;含…

作者头像 李华
网站建设 2026/10/13 16:08:53

知识工作插件化指南:从采集到发布的全流程自动化

你知道那种感觉吗&#xff1f;浏览器里开了四十多个标签页&#xff0c;笔记软件里堆着三年前的剪藏&#xff0c;邮箱里躺着一堆“稍后处理”的附件。每天工作八小时&#xff0c;至少有两个小时花在“搬运信息”上&#xff1a;从网页复制到笔记&#xff0c;从笔记整理成文档&…

作者头像 李华
网站建设 2026/10/13 16:07:18

操作系统课堂小测复盘:线程与调度核心考点与避坑指南

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

作者头像 李华