SwiftUI + AI Agent:打造可插拔技能系统实践指南
最近两个月我一直在折腾一件事把AI Agent的能力真正落到一个SwiftUI应用里。网页版的对话助手谁都会用但一旦想让AI自己查日历、读文件、调系统服务事情就完全变样了——你需要的不是聊天框而是一套能让模型长出手脚的技能体系。这个项目我命名为SwiftUI-Agent-Skill核心围绕三件事在SwiftUI中搭建Agent运行时、定义可插拔的Skill技能协议、把大模型的决策能力和App本地能力彻底打通。如果你正在做带AI助手的iOS或macOS应用或者想在SwiftUI里给LLM接上工具调用能力这篇文章应该能帮你省下一个月的踩坑时间。1. 别急着写代码先想清楚Agent在App端到底该长什么样1.1 从聊天到办事Skill层要解决的本质问题大多数人的第一反应是接一个模型API用户输入什么就转发什么把返回文本渲染出来。这个流程做Demo完全没问题但距离能用差得很远。真实用户不会满足于你问我答他要的是帮我落实一件事。比如明天上午有空吗顺手把周会邀请发出去这句话背后至少涉及日历读取、时段判断、日程创建、通知发送四个动作。LLM本身不执行任何操作它只负责输出文本。模型说我帮你查了日历明天上午10点有空但日历到底是谁查的必须是我们App自己的代码去查权限、读事件库、处理时区。于是中间就缺了一层翻译官把模型的意图转成App可执行的代码再把执行结果转成模型能理解的语言。这个翻译层就是Skill。我最早走了一段弯路直接在视图层写了一大堆if else根据模型返回的JSON字段调用不同函数写了大概十几个技能后代码已经变成一团乱麻。后来彻底重构把全部能力抽象成统一的技能协议按注册—调度—执行—反馈四步走代码量反而减少了一半新增一个技能只需十几分钟。1.2 技术选型为什么不直接用现成SDK而是自建技能协议市面上确实有不少AI助手SDK部分也宣称支持工具调用。但实际用下来会发现一个共性痛点它们对技能的定义往往偏向云端服务跟App本地的能力结合很别扭。我想要的是技能跑在用户设备上能直接操作本地日历、文件、剪贴板、通知中心而不是调一个云端HTTP接口。自建技能协议的核心优势在于技能就是一个普通Swift类型遵循统一协议声明自己叫什么、能干什么、参数长什么样、如何执行。App里的任何功能模块只要能满足协议就能作为技能挂载到Agent身上。以后想加新能力写一个结构体注册进去就行Agent核心代码一行都不用改。这也是工程上的解耦思路。Agent只依赖协议不依赖具体实现。你可以在不重启App的情况下动态装卸技能这在做插件化助手时特别有用。提示协议先行是这个项目最值得坚持的决策。先把技能长什么样固定下来再考虑具体技能怎么实现。我中途改过一次协议所有已注册技能全部要跟着改教训很深刻。1.3 一个清晰的技能最小原型在设计协议之前我先定义了一个最小可用的技能形态。一个技能至少要有四样东西名字、描述、参数Schema、执行函数。名字给模型看描述告诉模型什么时候该用我参数Schema告诉模型需要提供哪些字段执行函数做真正的业务操作。protocol AppSkill { /// 技能唯一名称Agent调度时靠它索引 var name: String { get } /// 给模型看的描述决定了模型会不会选这个技能 var description: String { get } /// 参数Schema用JSON格式描述每个参数的字段名、类型、是否必填 var parametersSchema: String { get } /// 执行入口传入解析好的参数字典返回结构化的执行结果 func execute(arguments: [String: Any]) async throws - SkillResult }这个协议看起来简单但已经能支撑大部分场景。关键地方在description——它是模型判断要不要用这个技能的唯一依据措辞直接影响调用的准确率。我后面会专门讲怎么润色描述。2. 技能注册与调度把AI会干什么显式告诉引擎2.1 技能注册中心一个可查询、可枚举的技能清单有了协议下一步就是把技能集中管理起来。我实现了一个注册中心用字典存储所有技能实例提供注册、反注册、按名字查找、枚举能力描述四个核心方法。Observable final class SkillRegistry { private var skills: [String: any AppSkill] [:] private let lock NSLock() func register(_ skill: any AppSkill) { lock.lock() skills[skill.name] skill lock.unlock() } func unregister(name: String) { lock.lock() skills.removeValue(forKey: name) lock.unlock() } func skill(named: String) - (any AppSkill)? { lock.lock() defer { lock.unlock() } return skills[named] } /// 生成模型可读的工具描述列表 func toolDescriptions() - [[String: Any]] { lock.lock() defer { lock.unlock() } return skills.values.map { skill in [ type: function, function: [ name: skill.name, description: skill.description, parameters: parseSchema(skill.parametersSchema) ] ] } } }注册中心看似简单却解决了三个问题。第一Agent核心不需要知道具体有哪些技能每次请求前动态枚举即可新增技能零成本。第二技能的启停可以做到运行时控制甚至可以按用户偏好裁剪技能列表。第三统一的查询入口让调试与排查变得简单——我随时可以打印出当前注册了多少个技能各自是什么状态。这里有一个值得注意的小细节并发安全。SwiftUI的主线程操作和Agent的后台执行线程之间会频繁读写技能列表如果不加锁很容易在边缘情况下遇到字典遍历时被修改的崩溃。我直接用NSLock兜底这个项目不追求极致性能稳定优先。2.2 参数Schema与校验模型输出很飘必须自己把守关卡说句大实话LLM的输出格式再稳定也会偶尔抽风。明明规定了参数必须是字符串它可能给你返回一个数字规定了字段名是startTime它可能冒出一个start_time。如果把不干净的参数直接扔给业务层执行阶段就会出各种诡异问题。我的方案是双重防线。第一道防线在模型侧通过提示词把参数Schema描述得足够清楚第二道防线在执行前做严格校验不符合Schema的请求直接返回错误并把这个错误作为反馈给模型让它重新生成。struct ParameterValidator { /// 简单但实用的类型校验器避免引入额外依赖 static func validate(_ arguments: [String: Any], against schema: [String: Any]) throws { guard let properties schema[properties] as? [String: Any] else { return } let required schema[required] as? [String] ?? [] for key in required { guard arguments[key] ! nil else { throw SkillValidationError.missingField(key) } } for (key, value) in arguments { guard let fieldSchema properties[key] as? [String: Any] else { continue } let type fieldSchema[type] as? String ?? if !validateType(value, type: type) { throw SkillValidationError.typeMismatch(key: key, expected: type) } } } }这段校验器不复杂但能在执行前拦下八成以上的参数错误。校验失败时我把错误信息转换成自然语言追加到对话历史里再让模型重新输出。实测下来二次成功率能到九成以上只有极少数情况需要反复重试。2.3 调度决策Agent循环是怎么想一下、做一下、再看结果的掌握了技能清单之后Agent的核心运行逻辑就呼之欲出了。我的实现是一个循环把对话历史和工具描述一起发给模型如果模型决定调用技能就解析参数、执行技能、把结果追加进上下文然后继续下一轮如果模型没有调用技能那就说明它已经给出了最终回答循环结束。func runAgent(userInput: String) async throws - String { var messages history messages.append(.user(userInput)) for _ in 0..maxIterations { let response try await llm.complete( messages: messages, tools: registry.toolDescriptions() ) if let toolCall response.toolCall { guard let skill registry.skill(named: toolCall.name) else { messages.append(.toolError(技能 \(toolCall.name) 不存在)) continue } do { let result try await skill.execute(arguments: toolCall.arguments) messages.append(.toolResult(toolCall.id, result.formattedForAgent)) } catch { messages.append(.toolError(error.localizedDescription)) } } else { return response.text } } throw AgentError.maxIterationsExceeded }这个循环看着直白但里面藏着三个设计决策我一开始没想清楚后来反复调试才顿悟。第一必须设置最大迭代次数。因为模型偶尔会在工具调用里反复横跳不设上限的话一个请求能来回几十次既烧钱又拖慢响应。我一般设6轮日常技能交互两到三轮就能结束。第二工具调用的结果必须转成模型容易理解的结构化文本。技能返回的是Swift字典或对象我要把它格式化成类似日程查找结果共3条。10:00-11:00 项目评审14:00-15:00 客户会议这样的自然语言。模型对纯文本的理解远比对嵌套字典好后续追问的准确度会高很多。第三执行结果不是必须成功。技能出错时把错误消息也作为工具结果反馈给模型让它自己尝试修正或向用户解释。这样用户看到的就不是一个冷冰冰的操作失败而是模型基于错误信息给出的合理建议。3. SwiftUI状态管理Agent循环千万别塞进View里3.1 用Observable承载Agent运行时状态SwiftUI开发有一个常见的错误习惯把业务状态直接放在View的属性里。开发初期确实很快但随着Agent运行状态越来越复杂——当前是空闲、思考中、执行技能、等待结果还是出错——View就会变得越来越臃肿并且状态流转一旦出问题UI和逻辑就互相纠缠。我的做法是把Agent运行时封装成一个Observable对象View只订阅这个对象暴露出的几个状态属性完全不直接操作Agent内部逻辑。Observable final class AgentViewModel { enum Phase { case idle case thinking case executing(String) case waiting case finished(String) case failed(String) } var phase: Phase .idle var currentMessage: String var streamedText: String private var agentTask: TaskVoid, Never? func send(_ text: String) { agentTask?.cancel() agentTask Task { [weak self] in await self?.run(text) } } private func run(_ text: String) async { phase .thinking do { // 调用Agent核心同时通过回调把流式文本抛回来 let answer try await agent.run(userInput: text) { delta in Task { MainActor in self.streamedText delta } } phase .finished(answer) } catch is CancellationError { phase .idle } catch { phase .failed(error.localizedDescription) } } }把状态收敛进ViewModel之后View层就变得非常干净。界面只需要根据phase切换展示思考中显示加载动画执行技能时显示技能名与进度流式输出时逐字渲染。任何状态变更SwiftUI的观察机制都会自动刷新界面不需要手动调objectWillChange。3.2 异步任务的生命周期取消、重启与竞态处理SwiftUI View出现和消失的频率比你想象得高得多。Tab切换、页面压栈、滑动退出都会触发View的销毁。如果Agent任务没有和View生命周期绑定就会出现一种诡异场景用户已经退出了对话页Agent还在后台跑任务然后试图更新一个已经销毁的对象。这就是引入Task生命周期管理的核心原因。我在ViewModel里持有agentTask每次发送新消息时先取消旧任务。取消不是直接停掉正在运行的网络请求而是向异步函数传播一个取消标记。我在Agent核心循环里也做了配合——每轮模型请求之后检查一下Task.isCancelled如果取消了就提前退出不再继续执行后续技能。// 在Agent核心循环中配合取消 for _ in 0..maxIterations { try Task.checkCancellation() // ...模型调用与技能执行 }竞态问题则是另一个坑。用户手速快的时候可能在Agent还在思考时就连点了两下发送按钮。如果不做处理两个Agent任务并发运行第二个任务和第一个任务都会去更新streamedText导致UI上出现两段文字穿插显示。我的解决办法是发送入口处直接取消旧任务并创建新任务从源头保证同一时刻只有一个Agent在跑。3.3 流式输出用AsyncStream打通模型到界面的管道流式输出体验比等完整响应再显示好一大截这个观点应该没人反对。但流式数据怎么从异步接口安全地飞到SwiftUI的界面上这里面的细节比想象中多。我的设计思路是模型流式返回时底层接口暴露一个AsyncStreamViewModel在Task里消费这个流每收到一段文本就更新一次UI状态。整个管道全部用async/await表达不需要回调闭包层层套娃也不用手动切换线程。func streamResponse(prompt: String) - AsyncStreamString { AsyncStream { continuation in let task Task { for try await chunk in api.streamComplete(prompt: prompt) { if Task.isCancelled { continuation.finish() return } continuation.yield(chunk) } continuation.finish() } // 任务取消时自动终止流 continuation.onTermination { Sendable _ in task.cancel() } } }在SwiftUI侧由于Observable天然需要主线程特性我在ViewModel的run方法前标记了MainActor。这样所有状态更新都发生在主线程不用担心后台线程更新UI的经典崩溃。注意AsyncStream要特别留意continuation的生命周期。如果模型的网络流异常断开却没有调用finish()这个流就会一直挂着ViewModel的Task也在等待中不结束内存上会有隐性问题。我在所有出口都调用了finish()不管正常结束还是取消。4. 写一个真实技能日历查询技能从定义到上线4.1 技能定义与参数Schema编写细节说完了原理来看一个完整例子。我挑一个最常用的日历查询技能从技能定义到参数Schema再到执行逻辑完整过一遍。技能的需求背景是用户说这周有什么会议Agent需要读取用户设备上的日历事件并返回结构化列表。技能实现如下。struct CalendarQuerySkill: AppSkill { let name calendar_query let description 查询用户设备上的日历日程。当用户询问某一天的安排、本周会议、日程表等内容时使用。 时间范围由 startDate 和 endDate 两个参数决定均为 ISO8601 格式字符串。 返回该时间段内的日程列表包括标题、开始时间、结束时间和地点。 let parametersSchema { type: object, properties: { startDate: { type: string, description: 起始时间ISO8601格式例如 2024-06-01T00:00:00Z }, endDate: { type: string, description: 结束时间ISO8601格式例如 2024-06-07T23:59:59Z } }, required: [startDate, endDate] } func execute(arguments: [String: Any]) async throws - SkillResult { guard let start arguments[startDate] as? String, let end arguments[endDate] as? String else { throw SkillValidationError.missingRequiredFields } let startDate try ISO8601DateFormatter().date(from: start) ?? Date() let endDate try ISO8601DateFormatter().date(from: end) ?? Date() let events try await CalendarService.shared.fetchEvents( from: startDate, to: endDate ) return SkillResult.success(data: events.map { [ title: $0.title, start: $0.startDate.description, end: $0.endDate.description, location: $0.location ?? ]}) } }这里有两个细节我特别想强调。一是description的语气必须包含什么时候该用这个技能的触发条件模型看到日程会议哪天有空这些词汇时匹配率会明显提升。二是参数Schema的description字段也不能省模型会读它来理解参数格式我在测试中发现写了参数说明后模型传错格式的概率至少降低一半。4.2 执行逻辑、权限处理与结果格式化日历查询绕不开权限问题。iOS的日历权限属于用户隐私敏感数据首次访问必须弹窗授权。这个逻辑放在技能执行函数里Agent在调用技能时如果有权限弹窗用户体验上会有一个短暂的等待。我的处理方式是在技能执行前先检查权限状态。未授权时返回一个特殊错误Agent会识别这个错误并向用户解释需要前往设置页面开启权限而不是简单地说查询失败。enum CalendarPermissionError: Error, LocalizedError { case denied var errorDescription: String? { 没有日历访问权限请引导用户在设置中开启日历权限 } } func execute(arguments: [String: Any]) async throws - SkillResult { switch await CalendarService.shared.authorizationStatus() { case .authorized: break case .denied, .restricted: throw CalendarPermissionError.denied case .notDetermined: let granted await CalendarService.shared.requestAccess() if !granted { throw CalendarPermissionError.denied } unknown default: throw CalendarPermissionError.denied } // ...继续查询逻辑 }结果格式化同样重要。技能返回的数据不直接给用户看因为模型还需要基于这个结果做二次加工。比如用户问这周会议多不多模型需要统计数量并给出概述而不是机械地把日程表念出来。所以我在SkillResult里既保留结构化数据也提供了一段给模型的自然语言摘要。var formattedForAgent: String { let list events.map { - \($0.title) | \($0.start) - \($0.end) | \($0.location) }.joined(separator: \n) return 共找到 \(events.count) 条日程:\n\(list) }这就是技能设计里的关键原则给模型的数据越结构化越好给用户的呈现越自然越好。技能本身不直接产出最终界面文案而是把素材交给模型去组织。我第一次也是直接返回纯文本后来发现模型在二次追问第一场会议几点结束时缺少结构化数据就答不上来这才改成双轨制。4.3 技能调试心得用模拟对话驱动单技能验证技能写完后调试是另一门学问。我一开始的做法是运行整个App打开对话界面手打测试语料结果效率极低每次都要经历完整交互流程还不好复现模型输出变化。后来我写了一个纯Swift测试入口直接驱动Agent核心不走UI层。通过命令行脚本注入一组模拟用户问题然后打印Agent的完整决策链路调了哪个技能、传了什么参数、返回了什么结果、模型下一步又说了什么。# 模拟输入查询本周日程 swift run agent-cli 这周有哪些会议安排这就是技能开发的正确思路——把技能验证前移到不依赖UI的地方。Agent逻辑是纯粹的业务逻辑不需要SwiftUI就能跑起来。这样做还有一个额外好处回归测试变得可行。我把几十条典型用户问题和期望的技能调用路径固化下来每次改完代码跑一遍就能快速发现是否有技能被改坏了。5. 踩坑实录线程、缓存与UI卡顿的三个经典事故5.1 MainActor传染导致的假死现场Swift 5.5引入async/await后MainActor的隔离检查帮我们避免了很多主线程更新UI的崩溃但它也会带来一个隐蔽的性能问题。我经历过一次诡异的现象界面卡顿点击按钮响应很慢但CPU占用并不高。排查了一下午最后定位到原因Agent核心的runAgent函数在调用链上被标注成了MainActor。原因是我在ViewModel里调用Agent的方法时为了图方便把Agent的某个接口也标记成了MainActor结果整个Agent的执行链路都跑在了主线程上。模型API的网络请求、JSON序列化、技能里的日历读取全部在主线程排队UI自然被拖垮。解决方式说来也简单把Agent核心和UI层彻底隔离Agent核心用普通Actor或纯struct函数不依赖任何UI状态。ViewModel只在状态更新的边界使用MainActor。这样重活全在后台线程跑主线程只处理界面更新。这个教训给我留下很深的印象MainActor非常好用但它是会传染的一旦某个底层函数被标记所有调用它的异步链都会被拽回主线程。需要随时审视哪些代码真的需要主线程哪些只是被传染了。5.2 技能结果体积过大引发的列表渲染卡顿第二个坑来自日历技能的返回结果。有次用户手机里存了上千条日程技能一次性全查出来返回给模型。消息体积暴增至几百KBUI在渲染对话历史时花了近一秒滚动时掉帧明显。根本原因是我在把技能结果插入对话历史时没有做大小限制。模型上下文窗口吃紧事小SwiftUI的List渲染超大字符串文本事大。后来我做了三层优化。第一技能查询加limit参数默认最多返回50条。第二如果确实超过限制将详细数据转成摘要信息只保留标题和时间范围完整数据通过技能内部接口再次加载。第三对话历史里的工具结果做了截断标记超过一定字符数就压缩成已返回N条结果。经过这三层调整单条消息体积控制在几百到几千字符以内UI渲染重新丝滑。5.3 并发执行同一技能时的数据竞态最后一个事故跟多轮对话里重复使用同一技能有关。有几个技能内部使用了一个共享的缓存对象。当一个Agent任务正在写入缓存时另一个任务读取了未完成的数据导致返回了错误的查询结果。起初我不以为意以为同一时间只会有一个Agent在跑。但实际场景是用户在第一个Agent任务还没结束时又发送了新消息我的入口代码虽然取消了旧任务但取消信号的传播是异步的旧任务可能还在某个技能执行中没有及时响应取消新任务已经开始了。两个任务短暂并存并且都进入同一个技能执行路径。解决方案分两步。一是技能内部所有共享状态改为Actor保护让存取操作串行化。二是在技能执行入口就检查Task.isCancelled如果任务已经被取消立即抛出CancellationError不执行任何缓存读写。这两个措施叠加后再也没出现过缓存数据错乱。这个事故也让我重新审视了取消这个话题SwiftUI里的.cancel()不是瞬间杀死任务而是温柔地告诉任务你可以停了真正配合取消的是业务代码自己。结尾项目走到现在我最大的感慨是做一个带技能的SwiftUI Agent应用难点不在调用模型API而在于把Agent的循环、技能协议、状态管理、SwiftUI生命周期这几层东西理顺。协议先行帮我省掉了大量重复改造把技能做成可插拔的模块后每加一个新能力都像插U盘一样简单。如果你也在做类似的东西建议从最简单的两个技能开始先把Agent循环跑通再慢慢丰富技能库。那个MainActor传染和任务取消的坑提前记住就能少走一大段弯路。另外说一个小技巧技能描述的润色值得多花时间同样的技能名字描述写得具体和写得模糊模型调用准确率能差出一大截。

相关新闻

PPO Clip机制原理与大模型RLHF工程实践

PPO Clip机制原理与大模型RLHF工程实践

1. 这不是一篇讲算法公式的文章,而是一次策略优化思想的“考古现场” 你有没有试过,在训练一个强化学习策略时,明明梯度方向是对的,更新一步后策略性能却断崖式下跌?明明用了更大的网络、更长的训练时间,最…

2026/10/14 10:17:00 阅读更多 →
RNA 不只是线,它还会折叠成各种形状来干活 | Info X Med 深度文献解读

RNA 不只是线,它还会折叠成各种形状来干活 | Info X Med 深度文献解读

文献来源:Info X Med 期刊《Nature Reviews Molecular Cell Biology(Review)》DOI:10.1038/s41580-024-00748-6结论先行: 我们常把 RNA 想象成一条“线”,但它其实会折叠成丰富多样的二维和三维结构。这些结…

2026/10/14 10:17:00 阅读更多 →
宽禁带半导体材料的性能优势与产业化挑战

宽禁带半导体材料的性能优势与产业化挑战

1. 新型宽禁带材料的性能优势解析在功率电子领域,宽禁带半导体材料正在掀起一场静悄悄的革命。作为一名从事半导体材料研发十余年的工程师,我亲眼见证了从第一代硅基材料到第三代宽禁带材料的演进历程。最近实验室里那台不断冒出各种报警信号的晶体生长炉…

2026/10/14 10:15:58 阅读更多 →

最新新闻

Java接入个人微信API:消息回调进客服系统的正确姿势

Java接入个人微信API:消息回调进客服系统的正确姿势

Java 团队接个人微信,通常第一反应是:Spring Boot 写个 Controller,接收回调,然后直接在方法里写业务。 本地调试挺顺,一上生产就出现:接口超时、消息重复、Tomcat 线程打满。 问题不在 Java,而…

2026/10/15 3:33:41 阅读更多 →
YOLO26算法室内工作环境手部手势目标检测+训练好的模型+8212张数据集+pyqt可视化界面

YOLO26算法室内工作环境手部手势目标检测+训练好的模型+8212张数据集+pyqt可视化界面

YOLO26算法室内工作环境手部手势目标检测训练好的模型8212张数据集pyqt可视化界面 这套数据共 8212 张图。用 YOLO26 训了 100 轮,mAP50 0.7315,mAP50-95 0.6002。Precision 0.7335、Recall 0.7532。下面按数据构成 → 训练曲线 → 预测效果的顺序过一遍…

2026/10/15 3:33:41 阅读更多 →
仓颉编程语言 I/O 节点流完全指南:标准流与文件流的实战解析

仓颉编程语言 I/O 节点流完全指南:标准流与文件流的实战解析

文档知识库教程OpenHarmony 【免费下载链接】CangjieCorpus 本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 R…

2026/10/15 3:33:41 阅读更多 →
LAMMPS bond_style mm3 键伸缩势详解:YAFF 包的 MM3 非谐势实现、参数配置与实战应用

LAMMPS bond_style mm3 键伸缩势详解:YAFF 包的 MM3 非谐势实现、参数配置与实战应用

科研科学计算高性能计算 【免费下载链接】lammps Public development project of the LAMMPS MD software package 项目地址: https://gitcode.com/gh_mirrors/la/lammps 点击查看 免费下载 导读 bond_style mm3 是 LAMMPS 中用于描述键伸缩运动的非谐势模型&…

2026/10/15 3:33:41 阅读更多 →
Flutter Engine Windows 无障碍实现解析:MSAA 与 UI Automation 在 AXPlatformNodeWin 中的落地

Flutter Engine Windows 无障碍实现解析:MSAA 与 UI Automation 在 AXPlatformNodeWin 中的落地

跨平台前端移动开发OpenHarmony 【免费下载链接】flutter_flutter 本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于…

2026/10/15 3:33:41 阅读更多 →
Natron ErodeSmooth 节点完全指南:归一化功率加权腐蚀/膨胀滤镜的参数解析与源码级解读

Natron ErodeSmooth 节点完全指南:归一化功率加权腐蚀/膨胀滤镜的参数解析与源码级解读

音视频视频处理图形学桌面应用 【免费下载链接】Natron Open-source video compositing software. Node-graph based. Similar in functionalities to Adobe After Effects and Nuke by The Foundry. 项目地址: https://gitcode.com/gh_mirrors/na/Natron 点击查看 …

2026/10/15 3:32:41 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

2026/10/14 16:30:29 阅读更多 →
影刀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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →