ARTICLE DETAIL

资讯详情

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

Friend Goals API 跨平台契约审计:基于 gt-goals.md 的 Mac/Windows 对齐与 Ground Truth 验证方法

Friend Goals API 跨平台契约审计:基于 gt-goals.md 的 Mac/Windows 对齐与 Ground Truth 验证方法 Friend Goals API 跨平台契约审计基于 gt-goals.md 的 Mac/Windows 对齐与 Ground Truth 验证方法【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本文为 Friend 桌面客户端Mac 与 Windows 两端的 Goals目标追踪功能做了一次“以真实源码为准”的契约审计它逐一核对后端backend/routers/goals.py、Pydantic 模型与 Mac/Windows 客户端实现确认了哪些 API 真实存在、哪些调用会稳定失败completeGoal拿到 400、getCompletedGoals拿到 404并给出每条结论的代码级证据链。读完本文你不仅能拿到一份可直接用于跨平台对齐的 Goals API 契约清单还能学会“ground truth 审计”这种方法先冻结参考实现再逐行追踪 请求体 → Pydantic 解析 →exclude_unset→ 路由检查 的完整链路从而判断一个客户端功能到底是“没接好线”还是“API 本身不存在”。1. 审计方法为什么需要 Ground Truth以及如何验证本文基于仓库中的审计文档 gt-goals.mdTrack 3 目标功能真值清单标注 2026-07-14 已对照真实源码验证而非文档摘要展开。该审计的方法论是跨平台对齐的关键后端是唯一权威DEFINITIVEMac 和 Windows 都必须向后端契约对齐而不是反向——以 Mac 或 Windows 的写法为准去“修正”另一端是常见错误。逐行追踪而非看注释每个结论都要把 请求构造 → 序列化 → Pydantic 解析 → 路由参数检查 的整条链路走一遍。例如“PATCH 只带is_active/completed_at会 400”必须确认 Pydantic 解析后model_dump(exclude_unsetTrue)产出空字典、再由路由显式抛出 400才算坐实。冻结参考实现文档当时引用了冻结版本的 Mac 源码GoalsAIService.swift、GoalGenerationService.swift、GoalPrompts.swift、GoalCelebrationView.swift、GoalsWidget.swift、APIClient.swift的 goals 扩展段与 Windows 的lib/goals.ts、pages/Goals.tsx、omiApi.generated.ts。当前主分支中这些文件均已演化下文在引用时以当前实际路径给出并对已发生的变化明确标注。2. 后端 Goals 路由全集完整清单与“不存在的路由”/v1/goals*命名空间下的权威路由清单审计时的确定集合如下全部位于 backend/routers/goals.pyMethodPathHandler说明GET/v1/goalsget_current_goal向后兼容返回第一个 active 目标或 nullGET/v1/goals/allget_all_goals最多 4 个 active 目标POST/v1/goalscreate_goalbody GoalCreatetitle、goal_type、target_value 必填current_value、min_value、max_value、unitPATCH/v1/goals/{goal_id}update_goalbody GoalUpdate见 §3PATCH/v1/goals/{goal_id}/progressupdate_goal_progressquery 参数current_value: float不是 bodyGET/v1/goals/{goal_id}/historyget_goal_historyquerydaysHistoryDays默认 30DELETE/v1/goals/{goal_id}delete_goal硬删除现已标记deprecatedTrue软废弃并保留链接GET/v1/goals/suggestsuggest_goal限流goals:suggest零请求载荷GET/v1/goals/{goal_id}/adviceget_goal_advice限流goals:advice目标不存在时 404GET/v1/goals/adviceget_current_goal_advice当前 active 目标的建议委托给上一条POST/v1/goals/extract-progressextract_and_update_progressbody{text}限流goals:extract审计中最有价值的负结论之一是/v1/goals/completed路由在任何地方都不存在。数据库层database/goals.py::get_all_goals(uid, include_inactiveTrue)作为 helper 存在但当时并未接线到任何 HTTP 端点——即“获取已完成/非激活目标历史”在 HTTP 层当时完全没有通路。对照当前主分支 backend/routers/goals.py 可以看到契约正在演化GET /v1/goals/all已新增include_ended: bool Query(False)参数L58-L66底层调用goals_db.get_all_goals(uid, include_inactiveinclude_ended)——这正是审计“Flags”一节所呼吁的“接线include_inactive的历史路由”已经部分落地新增了一组 canonical统一任务系统路由GET/POST /v1/goals/canonical*、POST/DELETE /v1/goals/{goal_id}/focus、POST /v1/goals/{goal_id}/lifecycle、GET /v1/goals/{goal_id}/detail、POST/GET /v1/goals/{goal_id}/progress-events以及GET /v1/goals/{goal_id}单目标按 id 读取注意其声明位置刻意放在所有静态路由之后避免被{goal_id}通配吞掉见 L372-L385DELETE /v1/goals/{goal_id}被标注为deprecatedTrue描述改为“soft-abandon and retain links”生命周期语义迁往 lifecycle 路由。这些新增路由都要求require_canonical_task_user鉴权并携带Idempotency-Key/X-Account-Generation头L41-L42属于统一任务系统的生成围栏generation-fenced体系与原始 11 条基础路由的auth.get_current_user_uid鉴权路径不同——客户端接入时需注意自己走的是哪一套契约。3.GoalUpdate模型PATCH 请求体到底接受什么这是整个审计的基石。审计时点的模型定义backend/models/goal.py:35-43为class GoalUpdate(BaseModel): title: Optional[str] None target_value: Optional[float] None current_value: Optional[float] None min_value: Optional[float] None max_value: Optional[float] None unit: Optional[str] None不存在is_active、completed_at、ended_at或status字段。当时的关键机制是Pydantic v2 默认extraignore未知键在解析阶段被静默丢弃根本到不了model_dump(exclude_unsetTrue)。路由侧goals.py:71-74随即检查update_data updates.model_dump(exclude_unsetTrue) if not update_data: raise HTTPException(status_code400, detailNo updates provided)因此一个“只带未知字段”的 PATCH 请求会解析出空的update_data稳定触发400 No updates provided。该检查在当前主分支中仍然存在L234-L240。当前主分支的 backend/models/goal.py 已大幅扩展GoalUpdateL125-L159新增了desired_outcome、why_it_matters、success_criteria、horizon_at、metricGoalMetric、clear_metric字段并改为ConfigDict(extraforbid)——也就是说现在发送未知字段如is_active不会再被静默忽略而是直接收到 Pydantic 422 校验错误400 “No updates provided” 的失败模式被 422 取代但结论不变PATCH 没有is_active/completed_at写路径。GoalCreate采用“canonical 形状 已发布客户端兼容字段”双轨设计L55-L122旧版扁平字段goal_type/target_value/current_value/min_value/max_value/unit经normalize_legacy_metric提升为metric对象旧description字段被提升为desired_outcomesource做了旧值归一化ai→ai_suggested等创建时不允许直接focused状态需先创建再显式 focus。GoalResponseL211-L238同时返回 canonical 字段与“已发布客户端兼容别名”goal_type、target_value、current_value、is_active、advice等——这正是 Mac/Windows 客户端按扁平字段解析响应仍然能工作的原因。4. 坐实 Mac 的两个静默故障completeGoal()400 与getCompletedGoals()404审计确认这两个调用是 Mac 端当前真实行为不是假设性推演且错误被 catch 后仅记日志、不向用户暴露功能静默失效。4.1completeGoal()→ 400Mac 端实现当时位于APIClient.swift:3382-3400当前主分支对应 APIClient.swift 的completeGoal(id:)L1222-L1244struct CompleteGoalRequest: Encodable { let is_active: Bool let completed_at: String } // PATCH v1/goals/{id} with { is_active: false, completed_at: ISO8601 }按 §3 的模型与exclude_unset链路两个字段全部被丢弃 → 空update_data→400 No updates provided。审计特别强调这条链路是“Pydantic 解析 →exclude_unset→ 路由检查”逐步追踪确认的。4.2getCompletedGoals()→ 404实现当时位于APIClient.swift:3376-3379当前主分支 L1216-L1219func getCompletedGoals() async throws - [Goal] { let goals: [Goal] try await get(v1/goals/completed) ... }而GET /v1/goals/completed不在 §2 的路由清单中 → FastAPI 返回404。4.3 受影响的调用方两个调用方当时均为GoalsAIService.fetchRichContext()GoalsAIService.swift:176-187供自动generateGoal()使用和GoalGenerationService.removeStaleGoals()GoalGenerationService.swift:41-58对每个找到的陈旧 AI 目标调用completeGoal(id:)。当前主分支的 GoalGenerationService.swift 中removeStaleGoals仍调用APIClient.shared.completeGoal(id: goal.id)L56——陈旧目标清理至今仍依赖一个会 400 的调用静默 no-op这是可以继续在 Mac 端修复的活问题。4.4 正确的“完成目标”方式按真实后端契约没有专用完成端点当时可工作的路径只有两条把进度推到目标值PATCH /v1/goals/{id}/progress?current_valuetarget。这是 Windows 端今天的做法Goals.tsx的toggleComplete→updateProgress(g, target)并在客户端以current_value target_value推导“已完成”。给 Pydantic 模型增加is_active字段并配套 DB 更新路径——但这是后端改动客户端无法单方面实现该方向后来在 §2 提到的 lifecycle 路由上以 canonical 契约的形式推进。Windows 当前的策略进度推到目标值并把/v1/goals/all中is_active false的目标也视为完成——覆盖后端create_goal最大目标数驱逐所停用的目标是对真实 API 唯一可行的客户端模式。这与 wiring 审计的 C10 结论完全一致——Windows 已在Goals.tsx:49-57的注释中独立得出并记录了同一结论。当前主分支 Goals.tsx 中的注释仍然在位L44-L45“There is no /v1/goals/completed GET endpoint.”L199-L203“The live backend has no write path for is_active/status (PATCH rejects them with 400 No updates provided) and no /complete route”——Windows 工程师把 §3、§1 的发现直接写进了源码注释形成了“文档真值”与“实现自证”的互锁。5.GET /v1/goals/{goal_id}/advice存在、富上下文、但 Windows 零调用审计对这条路由的请求/响应形状与实现做了完整确认请求无 body、除鉴权外无 query 参数仅 path 参数goal_id限流键goals:advice当前主分支 L293-L302。响应AdviceResponse { advice: str }单字段纯字符串backend/models/goal.py L261-L262。后端实现backend/utils/llm/goals.py 的get_goal_advice_get_goal_context是混合检索架构当前主分支_get_goal_context位于 L24get_goal_advice位于 L188向量检索query_vectorsk10取与目标标题语义相关的对话取前 5 条非锁定overview 截断至 300 字符标记[Relevant]近期对话limit 20、已完成、近 7 天再取至多 10 条overview 截断 250 字符标记[Recent]近期聊天消息limit 15、保留最近 10 条、每条 200 字符、按时间序记忆limit 30、前 15 条非锁定、每条 150 字符。 最终拼成一个包含目标标题/进度 四个上下文块prompt 组装时分别限 1500/800/600 字符的提示词调用get_llm(goals_advice)专用/更优模型通道去引号后返回。Windows 现状生成式 API 客户端omiApi.generated.ts:9372的get_goal_advice_v1_goals__goal_id__advice_get当前目标变体在:9278存在且完整类型化但在整个 renderer 树中零调用审计对整个 renderer 目录做了 grep 确认Goals.tsx也没有任何“Get insight”/建议 UI。这条结论的价值在于优先级排序它是三个目标建议实现中最富的一个比 Mac 本地GoalsAIService.getGoalInsight更强——后者只有 memories(15)conversations(10)没有向量检索、没有聊天上下文却完全闲置——纯 UI 接线缺口零后端工作量。6.GET /v1/goals/suggest确认零载荷请求与三条降级路径路由定义当时routers/goals.py:118-121当前主分支 L287-L290router.get(/v1/goals/suggest, ...) def suggest_goal(uid: str Depends(auth.with_rate_limit(auth.get_current_user_uid, goals:suggest))) - dict: return suggest_goal_llm(uid)无请求体、无 query 参数——仅uid经鉴权依赖注入。确认。处理器实现utils/llm/goals.py::suggest_goal当时 lines 106-182当前主分支 L109的行为特征拉取至多 100 条记忆memories_db.get_memories(uid, limit100, offset0)若无记忆返回硬编码兜底Learn something new every dayscale 0-10否则取前 50 条非锁定记忆的内容再截断到前 20 条memory_texts[:20]进入 prompt 上下文无对话、无行动项、无 persona、无现有/已完成/已放弃目标列表——因此它可能、也一定会建议出与现有/已完成/已放弃目标重复的内容单步 prompt、get_llm(goals)用正则提取{...}JSON 块解析失败时回退到第二条硬编码建议Track your daily progress任何异常时回退第三条Make progress every day。注意返回形状GoalSuggestionResponsebackend/models/goal.py L252-L258包含suggested_title、suggested_type、suggested_target、suggested_min默认 0、suggested_max默认 10、reasoning。7. Mac 客户端侧目标生成数据采集与 5 步 prompt移植决策依据GoalsAIService.fetchRichContext()GoalsAIService.swift:144-237并行、无截断地采集APIClient.shared.getMemories(limit: 500)APIClient.shared.getConversations(limit: 100, statuses: [.completed])APIClient.shared.getActionItems(limit: 100, completed: false)含任务 ID用于链接APIClient.shared.getPersona()APIClient.shared.getGoals()active 目标APIClient.shared.getCompletedGoals()——损坏404§4因此goalHistory实践中恒为[]completed/abandoned划分按completedAt ! nil也因此恒为空。整体静默降级为“memories conversations tasks persona”而非完整规格。5 步 promptGoalPrompts.generateGoalGoalPrompts.swift:24-79(1) 从 personamemories 理解人格与抱负(2) 审视进行中的对话/任务找未满足需求(3) 复查现有/已完成/已放弃目标以避免重复(4) 合成一个具体、可度量的目标含数值目标与隐含时间框架(5) 链接相关既有任务 ID。结构化 JSON 响应含linked_task_ids。成功后调用createGoal(..., source: ai_suggested)再对每个有效任务 ID 调updateActionItem(id:goalId:)建立链接。当前主分支的 GoalPrompts.swift 与 GoalsAIService.swift 仍在 ProactiveAssistants 体系下可对照阅读。触发节奏GoalGenerationService.swift当前主分支 L10-L17 可见开关键受kAutoGenerationEnabled门控默认false——即使 Mac 端默认也是关闭的触发点是onConversationCreated()hook触发时先removeStaleGoals()任何 active 的 AI 来源目标——source ai_suggested或旧值ai——若updated_at≥3 天未变化就对其调用completeGoal()按 §4当前 400 并静默 no-op再checkDailyGeneration()active 目标 3 且自kLastGenerationDateUserDefaults以来跨了自然日时当天生成一次generateNow()是手动触发变体GoalsWidget.swift中“Generate AI Goal”按钮使用最多重试 3 次、5 秒退避。Windows 的移植选项审计列出并给出权衡未做导航位置决策——标记为 G-CA. 对齐 Mac 的客户端侧生成Windows renderer 直接经既有callAgentLLM/agent-LLM 通路onboarding 已在lib/goals.ts中使用调用getMemories(500)/getConversations(100)/getActionItems(100)/getPersona()/getGoals()复刻 5 步 prompt并跳过getCompletedGoals()/completeGoal()§4 的死路或改用/v1/goals/allis_active的客户端过滤替代。优点信号远比后端 suggest 丰富500 条记忆 vs 后端 20 条、任务链接、绕开 404 后有历史去重意识缺点复制了 Mac 已有 bug 的业务逻辑把一个后台自动功能耦合到 agent-LLM 调用路径测试面更大。B. 直接使用后端/v1/goals/suggest或扩展它Windows 的手动 “Suggest” 按钮已经在调它。优点单一事实来源、无客户端 prompt 复制后端改动将来也能惠及 MacMac 修好后改调共享端点而非本地 Gemini缺点后端当前版本明显更薄20 条记忆、无对话/任务/persona/历史、会建议重复——要达到 Mac 的质量需要一个后端增强 PR 来补上对话任务persona目标历史上下文。这超出审计范围仅在此标记。无论选哪个上下文采集方案自动每日节奏 陈旧目标清理在 Windows 端完全无对应物——Windows 今天 100% 是手动按钮触发。8. Goals 页面 UI 规格emoji 自动图标、进度条阈值配色、完成庆祝动画这一节是可移植性判断的规格基线“按简报全部值得移植”。Emoji 自动图标GoalsWidget.swift:363-534goalEmoji计算属性纯客户端关键词匹配作用于goal.title.lowercased()约 30 个类别桶按固定优先级检查money → growth/users → startup → invest → workout → running → weight → meditation → sleep → water → health → reading → learning → coding → language → writing → video → music → art → photo → tasks → habits → time/focus → project → travel → home → saving → social → family → relationship → win → growth/improve → star每个桶是对关键词列表的简单.contains()子串检查首个命中获胜。默认/兜底 emoji 为 。渲染在目标行左侧的 36x36 圆角矩形磁贴backgroundRaised.opacity(0.9)圆角 12。当前主分支中该逻辑位于 GoalsHistoryPage.swiftgoalEmoji计算属性 L109兜底L123文件位置随 UI 重组而变但机制不变。进度条配色GoalsWidget.swift:168-181GoalRowView的progressColor——离散的阈值型纯色不是真渐变以displayProgress0-1为键displayProgress颜色≥ 0.8#22C55E绿≥ 0.6#84CC16青柠≥ 0.4#FBBF24黄≥ 0.2#F97316橙 0.2OmiColors.textTertiary中性灰条本身高 6px拖拽中 8px圆角 3背景轨道Color.white.opacity(0.12)可拖拽拖拽手势实时更新进度释放时提交按minValue/targetValue区间四舍五入到最近整数。白色圆形拖拽拇指14x14黑色阴影始终显示在当前填充边缘。完成庆祝GoalCelebrationView.swift当前主分支 GoalCelebrationView.swift——由NotificationCenter的.goalCompleted通知携带Goal对象触发全屏 overlay4 阶段动画Dimt00.3s ease-out黑色 overlay 淡入至 0.4 不透明度Confettit0.3s0.3s ease-outdim 加深到 0.5GoalConfettiView出现——40 个粒子圆形/圆角矩形随机混合、9 色调色板黄、金#FFD700、绿#22C55E系、蓝、粉、橙、青、薄荷、紫 70% 紫、随机尺寸 4-10pt、随机径向角度/距离80-300pt从中心爆发并带最高 1080° 随机旋转0.8s ease-out 入场t1.5s相对 confetti 视图自身出现开始淡出Textt0.8sspring response 0.5/damping 0.7Goal Completed! 32pt 粗体 黄→橙→黄横向渐变 黄色发光阴影下方目标标题18pt 白色居中target unit reached说明14pt70% 白Fade outt3.0s0.5s ease-out整体淡出0.5s 后状态复位。Windows 现状对照Goals.tsx:174updateProgress()在value target_value时触发toast(Goal complete , { tone: success, body: g.title })。没有按目标的 emoji目标列表是纯 checkbox无图标、没有阈值配色进度条只有bg-white/45/ 完成时bg-emerald-400/70两态而非五态、没有 confetti/全屏动画。差距明确规格可直接照 §8 移植。9. Windows 现状onboarding-only 的lib/goals.ts与主页面Goals.tsxlib/goals.ts只是 onboarding 专用2 个纯函数 2 个网络封装当前主分支 goals.ts 仅 55 行buildGoalPrompt(apps)单轮 prompt让 agent-LLM 生成一个可度量的目标句子喂入 onboarding 脑图里已知的 apps 做个性化否则给通用生产力目标parseTargetValue(text)正则提取目标文本中的第一个数字默认 1因为POST /v1/goals缺target_value会 422generateGoal(apps)用该 prompt 调callAgentLLMtrim/去引号createGoal(title)带解析出的 target POST/v1/goals。这是与主 Goals 页截然不同的、薄得多的代码路径——只被 onboarding 的GoalStep.tsx使用。pages/Goals.tsx663 行当前主分支 Goals.tsx 已扩至 733 行是主页面拉取/v1/goals/all不调用/v1/goals/completed——L40 注释明确注明该端点不存在见 L44-L45客户端侧按isCompleted()is_active false或current_value target_value划分 active/completed客户端计算progressPct/progressLabelCRUD创建POST空白/非法 target 默认 1——注释注明缺它会 422、内联编辑标题PATCH/v1/goals/{id}仅带{title}——真实可接受字段、删除DELETE、更新进度PATCH/v1/goals/{id}/progress?current_value乐观更新、失败回滚toggleComplete()显式记录了当时 lines 184-187当前 L199-L203为何要“进度推到 target/0”——“live backend 没有 is_active/status 写路径PATCH 以 400 No updates provided 拒绝也没有 /complete 路由”即 Windows 工程师已独立验证并注释了 §4 与 §2 的发现getSuggestion()/acceptSuggestion()调用GET /v1/goals/suggest预览后再 POST没有advice/insight UI、没有自动生成、没有目标历史/已完成目标视图“Completed”筛选 tab 只展示同一次/v1/goals/all拉取中is_active false或进度完成的目标——不存在能看到更早被完全驱逐/老化出局的目标的方式因为没有历史端点。10. 开放决策项与后端侧行动审计末尾向编排者orchestrator留下了两个 flagG-C导航位置/自动生成的 UI 或设置开关放哪里——按简报不在此文档决策留给编排者/parity-lead 解决是否新增后端include_inactive/返回历史的 goals 路由修复 Mac 的 404并给 Windows 一个真正的 “Completed” 历史视图是后端侧决策超出该真值文档范围——但既然 Mac 和 Windows 都能受益审计在此标记它需要一个后端 PR把database/goals.py::get_all_goals(uid, include_inactiveTrue)接线到路由任何平台客户端都无法单方面完成。对照当前主分支这一项已部分兑现GET /v1/goals/all?include_endedtrue现在可以把已结束目标一并返回backend/routers/goals.py L58-L66。因此 Mac 的getCompletedGoals()404 与 Windows “Completed 视图看不到历史”的缺口修复方向已经从“新建端点”收敛为“客户端改用既有include_ended参数 废弃v1/goals/completed路径”成本显著低于审计时点的估计。11. 方法论小结从源码结构看这次审计的可复用价值从源码结构看这份 ground truth 文档示范了一个可复用的跨平台 API 契约审计流程值得在任何多客户端共享后端的项目中照搬先立权威以服务端路由文件为 DEFINITIVE 清单客户端一律向其对齐负结论同样要证据“某路由不存在”“某字段被静默丢弃”这类结论必须追到解析器与框架行为Pydanticextra默认值、FastAPI 404 语义才成立区分“接线缺口”与“契约缺失”advice 端点零调用是前者零后端工作量/v1/goals/completed404 是后者需后端 PR——两者的修复路径与排期完全不同让源码注释成为自证材料WindowsGoals.tsx的注释与文档结论互锁任何一端漂移都能在 review 中立刻暴露标注文档与实现的时差本文 §2、§3 展示了审计快照与当前主分支的差异新增 canonical 路由、extraforbid、include_ended参数引用真值文档时务必核对引用时点避免把“当时的契约”误当“今天的契约”。【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表