Files
2026-10-01 18:36:48 +08:00

22 KiB
Raw Permalink Blame History

MemeMeow API

FastAPI 是唯一业务入口。模型密钥、Base URL 和路径只从服务端 .env 读取,前端不能修改配置或获得绝对路径。

Scope 与部署

应用工厂必须显式注入可信 scope resolver:

from api import create_app
from backend.scope import LocalScopeResolver

app = create_app(scope_resolver=LocalScopeResolver("local"))

模块级 api:app 入口已经显式安装上述 local 适配器;适配宿主应从可信请求上下文提供自己的 resolver,可按需通过 service_factory 与 agent_input_provider 注入 scope 服务和受控 Agent 输入,也可以省略 service_factory 使用核心默认 factory。只要 resolver 不是显式 LocalScopeResolver("local"),默认或宿主 factory 启动都不会调用 for_scope("local")、执行 local storage preflight 或创建 local 业务 service;默认 factory 在请求或任务认领后按可信 scope 懒创建 facade,宿主数据库可以没有 local scope。漏配 resolver、resolver 返回空值/非法值或 scope 服务装配失败时,应用或请求以稳定的 scope_resolution_failed / scope_unavailable 错误失败,不回退到 local。

请求中的 scope_id、user_id、路径前缀和普通业务字段不会改变 resolver 结果。上传、媒体、图片元数据、搜索、合集和公共任务 API 只访问当前请求 scope;属于其他 scope 的资源统一按不存在处理,不返回其存在性、文件路径或物理 namespace。

任务创建时服务端把请求 scope 写入不可为空的 Task.scope_id,payload 不承担授权 scope。Worker、重试、视觉/反向图片 callback 和 Agent 子任务从持久任务或有效 claim 恢复 scope;两个既有 /internal/... callback 不依赖请求上下文。宿主 non-local scope 必须提供受控 agent_input_provider;未配置或返回符号链接/非普通文件时任务稳定失败为 agent_input_provider_unavailable。内部 callback 由 API 独立验证短期任务凭据,网络隔离和内部路由暴露范围仍由部署宿主负责;本 API 不新增身份管理接口。

进程内只运行一个 scope-aware Worker manager,所有 scope 共享线程池、handler registry、全局/资源 lane slot、claim owner 和恢复扫描;请求侧 task facade 不启动 scope 专属 Worker。

检索

POST /search

请求体:

{"query":"开会时忘记准备材料","n_results":8,"llm_enhance":false}

query 必须非空,n_results 为 1 到 30 的整数,llm_enhance 默认 false。成功响应:

{
  "results":["/media/2f3a2a6d-93f6-4cd0-a4c8-1578c5b929b2"],
  "result_media":[
    {
      "meme_id":"2f3a2a6d-93f6-4cd0-a4c8-1578c5b929b2",
      "media_url":"/media/2f3a2a6d-93f6-4cd0-a4c8-1578c5b929b2",
      "thumbnail":{
        "status":"available",
        "media_url":"/media/2f3a2a6d-93f6-4cd0-a4c8-1578c5b929b2/thumbnail",
        "width":320,
        "height":180,
        "media_type":"image/jpeg"
      }
    }
  ]
}

results 保持原有的受控原图 URL 数组契约;result_media 是按结果顺序对应的可选富媒体旁路,条目包含稳定 meme_id、原图 media_url 和 thumbnail。缩略图处于 pending、failed 或 stale 时其 media_url 为 null,客户端应继续使用原图。结果按相关性和稳定 meme_id 排序并去重。语义、向量和索引 generation 均由 PostgreSQL + pgvector 保存;缺少或处于 pending/repair_required 的记录会跳过,不再使用文件名回退。缓存不存在或正在生成时返回 503:

{"error":"cache_not_ready","message":"检索缓存尚未就绪"}

旧的 GET /search?q=... 不是兼容入口,返回 405。

图片库和媒体

  • GET /images?search=&page=1&page_size=50:分页列出当前请求 scope 的扁平图片;每个图片项包含 meme_id、文件名、原图 media_url、thumbnail、metadata.status、文本向量 embedding_status 和图片视觉向量 visual_embedding_status。thumbnail 形状为 { "status": "available|pending|failed|stale", "media_url": "..."|null, "width": 320, "height": 180, "media_type": "image/jpeg" };只有 available 返回宽高、媒体类型和受控缩略图 URL。
  • GET /images/metadata?meme_id=:读取当前 scope 指定 Meme 的图片信息和语境;文件身份按公开展示字段投影,不接受路径式资源标识。
  • POST /images/rename:请求 { "meme_id": "...", "new_name": "new" },保留原扩展名且拒绝覆盖。
  • POST /images/delete:请求 { "meme_id": "..." },隔离并删除图片及数据库 Meme。
  • POST /images/upload:multipart 字段 reverse_image_policy=forbid|auto、auto_name=true|false 和多个 files,单请求最多 20 个文件,逐文件返回成功或失败,图片直接写入当前请求 scope 的受控图片根;成功入库后自动创建或复用逐图处理 job;不接受目标目录、scope_id 或 user_id。部署可通过 MEMEMEOW_MAX_REQUEST_BYTES 启用总请求字节预算,未配置时不启用总量限制;响应丢失后的同名、同 SHA、同大小提交会返回既有 meme_id 和当前处理状态。
  • 上传成功会在 PostgreSQL 创建稳定 meme_id 和 pending 元数据记录;meme_context.title 初始为 null。数据库是唯一结构化事实,运行时不读取或写入 sidecar。
  • GET /media/{meme_id}:按当前请求 scope 稳定 Meme ID 受控读取 PNG/JPG/JPEG/GIF;跨 scope ID 返回 404 meme_not_found。响应使用 Cache-Control: private, no-store 和 Vary: Cookie。
  • GET /media/{meme_id}/thumbnail:按当前请求 scope 和稳定 Meme ID 读取已完成且仍通过源版本、输出 SHA/大小、尺寸及 image/jpeg 校验的缩略图;不存在、未就绪、失效、损坏或跨 scope 的派生对象均按 404 meme_not_found 处理。响应同样使用 Cache-Control: private, no-store 和 Vary: Cookie,不接受客户端路径或内部 output_key。

分析信息

图片详情响应中的 provenance.agent_analysis 是只读摘要,仅包含 model 和 completed_at,从最近一次已保存的 Agent 语境分析记录派生,不使用当前模型配置或元数据更新时间代替。示例:

{"model":"provider/analysis-model","completed_at":"2026-09-16T08:30:00.128901+00:00"}

缺少有效模型或完成时间时不返回该摘要。它不包含任务 ID、配置哈希等内部诊断字段;现有其他 provenance 字段保持不变。API 保留完整完成时间,图片详情的“分析日期”按浏览器本地时区只显示年月日。该时间表示语境分析结果的保存时间,不代表后续自动命名、文本向量生成或整个处理 Job 已成功。接口不会写回数据库或修改图片文件。

缩略图派生

原图成功入库后,服务端在独立的 derived_thumbnail_generation 任务 lane 中幂等提交缩略图任务;该任务不会改变既有图片处理 Job、搜索索引或原图状态。缩略图固定使用 thumbnail-v1 profile,最长边为 320 像素,保持比例、不裁剪且不放大;GIF 只取首帧并输出高压缩静态 JPEG(质量 70),透明区域铺白。生成会复用原图预检的格式、解码、帧数和像素边界,并受输出字节、临时空间、执行超时、并发和背压配置限制。派生文件位于独立 scope-bound 存储,不进入图片列表、搜索索引、合集成员关系或合集导出,也不计入图片数量、存储容量和上传操作额度。

POST /images/thumbnails/reconcile?page=1&page_size=100&limit=100:在当前可信 scope 内分页扫描并幂等提交存量图片的缩略图回填/失败重建,返回 { "scanned": 100, "submitted": 80, "available": 15, "failed": 5 }。该入口受 Settings 管理 token 保护,优先使用 X-Settings-Admin-Token,兼容 X-MemeMeow-Settings-Token 和大小写不敏感的 Authorization: Bearer <token>;token 由服务端 MEMEMEOW_SETTINGS_ADMIN_TOKEN 配置。page、page_size 和 limit 之外的查询参数会被拒绝,scope 从可信请求上下文派生。错误语义为:未授权 403 settings_forbidden,非法查询 400 invalid_request,任务背压 429 thumbnail_backpressure,回填服务或数据库不可用 503 thumbnail_reconcile_unavailable。

  • POST /images/context:请求 { "meme_id": "...", "reverse_image_policy": "forbid|auto", "auto_name": false },异步创建或复用单图处理 job,返回 processing_job_id 和当前 Agent 阶段摘要。缺省策略为 forbid,自动命名缺省关闭。
  • POST /images/context/batch:请求 { "items": [{"meme_id":"..."}], "include_unready": true, "reverse_image_policy": "forbid|auto", "auto_name": false },逐图返回处理 job 结果;省略 items 时不隐式扫描孤立文件。
  • 视觉向量生成使用 POST /images/stages,请求为 { "meme_id": "...", "stage": "visual" };它只创建视觉阶段 Task,不创建 Agent、自动命名或文本向量 Task。视觉阶段不得携带 reverse_image_policy,携带该字段直接返回参数错误。
  • POST /images/metadata/repair:异步执行数据库记录、图片文件和指纹完整性扫描;不读取 sidecar、不默认调用模型或外部搜索。
  • 图片库的“选择图片”“完整重试”和“修复所有未就绪”会调用上述逐图处理接口;有效文本向量写回后会立即具备当前 scope 的搜索资格,不需要为每次上传重建全库缓存。模型切换和存量迁移的显式回填见 docs/image-processing-migration.md。

图片处理 job

POST /images/processing?page=1&page_size=100 按当前 scope 分页枚举图片并提交逐图处理 Job,请求体为 { "reverse_image_policy": "forbid|auto", "auto_name": false }。单图处理按 visual -> agent -> auto_rename -> text_embedding 推进;父 Job 创建时会保存 processing_mode 和每个阶段的 planned/skip_reason。auto_name=false 时自动重命名阶段为 skipped/disabled 且不创建叶子 Task,每个计划阶段拥有独立叶子 Task,视觉和文本阶段不消耗 Agent operation grant。同一图片已有活动 Job 或阶段 Task 时返回 409 image_processing_active,不会复用活动执行。

POST /images/processing/unready 只接受上述两项选项,由服务端枚举当前 scope 全部未就绪图片,不接受前端图片列表、筛选或分页参数;产品名称为“修复所有未就绪”,服务端按图片固定最小修复范围。响应逐图返回 submitted、processing_active、not_needed 或 failed,汇总包含 target_count、submitted_count、固定为零的 reused_count、conflict_count、not_needed_count 和 failed_count。

GET /images/processing/{job_id} 返回有限状态:queued、running、succeeded、failed、blocked 或 unknown_execution,以及 current_stage、每阶段 task_id/attempt/error、auto_name、has_warnings 和有限 warning 摘要。跨 scope 或不存在的 ID 都返回 404 image_processing_job_not_found。

GET /images/processing 返回当前 scope 的完整 Job 父项及 visual、agent、auto_rename、text_embedding 四个阶段;Job 和叶子 Task 的 submission_mode 明确为 pipeline。阶段项包含 planned 和 skip_reason,用于区分本次执行和本次未执行。历史三阶段 Job 读取时只读合成 auto_name=false、auto_rename=skipped/disabled,不回写历史。

POST /images/stages 请求接受 { "meme_id": "...", "stage": "visual|agent|auto_rename|text_embedding", "reverse_image_policy": "forbid|auto" },创建一个无父 Job 的独立阶段 Task。视觉阶段不得提供 reverse_image_policy;Agent 阶段的策略由服务端校验。scope、图片 SHA、配置、grant、标题指纹和目标文件名均由服务端派生;返回 submission_mode=standalone 与 processing_job_id=null。同一图片已有活动 Job 或阶段 Task 时返回 409 image_processing_active,不会复用活动 Task。image_auto_rename 不得通过通用 /tasks/{task_id}/retry 重试。

POST /images/stages/batch 请求 { "items": [{"meme_id":"..."}], "stages": ["visual", "agent", "text_embedding"], "reverse_image_policy": "forbid|auto", "auto_name": false },为每个选中图片和所选阶段创建或复用独立 Task;阶段列表至少一项、最多三项且不得重复,只接受三个核心阶段。完整重试仍使用 /images/context/batch 的完整流水线契约。

POST /images/processing/{job_id}/retry 只接受 failed、blocked 或 unknown_execution job,并创建新的 revision、叶子 Task 和必要的 grant;旧 job、Task 和 grant 保持终态。unknown_execution 表示外部执行窗口已经开始但结果无法证明,恢复流程不会自动重放,必须由人工确认后显式 retry。兼容路径 /image-processing/... 仍可用但不在 OpenAPI 中展示。

合集

合集是当前请求 scope 内的逻辑图片分组,使用稳定 meme_id 建立成员关系,不复制或移动图片文件。接口不接受 scope_id 或 user_id。

  • GET /collections?page=1&page_size=50:按更新时间和合集 ID 稳定分页列出合集,返回 collection_id、名称、成员数量、原图 cover_media_url、cover_meme_id、cover_thumbnail 和时间戳。存在封面 Meme 时返回 cover_media_url、cover_meme_id 和 cover_thumbnail;无封面时后三个字段为空或缺省,不伪造封面身份。封面缩略图沿用上述有限状态;原图封面地址始终保留。
  • POST /collections:请求 { "name": "工作" } 创建空合集;名称会去除首尾空白并限制为 1 至 100 个字符。
  • GET /collections/{collection_id}?page=1&page_size=50:返回合集元数据、成员总数和按加入时间稳定排序的成员。成员包含当前文件名、大小、状态、原图 /media/{meme_id} 和 thumbnail;存在封面 Meme 时合集摘要同时包含 cover_media_url、cover_meme_id 和 cover_thumbnail,无封面时这些字段为空或缺省。
  • PATCH /collections/{collection_id}:请求 { "name": "新名称" } 重命名,不改变成员关系。
  • DELETE /collections/{collection_id}:删除合集及成员关系,不删除 Meme 或图片文件。
  • POST /collections/{collection_id}/items:请求 { "meme_ids": ["..."] } 原子批量加入图片;返回 added_count、existing_count 和最终 member_count。
  • DELETE /collections/{collection_id}/items/{meme_id}:幂等移除单个成员。

同一 scope 内名称精确唯一,重名返回 409 collection_exists;未知合集或图片返回 404;非法名称和空成员数组返回 422。合集删除或图片删除都会由数据库级联清理关系,但不会影响其他图片。

长任务

  • POST /generate-cache:返回 202、task_id、status=queued;同类任务不会并发执行。
  • GET /tasks?status=running&task_type=meme_context_generation&cursor=...&limit=50:按状态、类型和 cursor 分页返回安全任务摘要。
  • GET /tasks/{task_id}:返回任务类型、queued/running/succeeded/failed、进度、消息、时间、错误和有限结果。Agent 失败任务额外返回 resume_available、resume_reason、session_id、executor_attempt_id、resume_attempts、resume_started_at、first_error 和有限 error_history;这些字段只保留脱敏稳定诊断,不返回 prompt、工具参数、密钥或 transcript。自动续跑由 MEMEMEOW_AGENT_RESUME_ENABLED 控制且默认关闭;关闭时已持久化的 session 也显示为 resume_available=false、resume_reason=resume_disabled,但既有任务级 retry 仍按原策略执行。达到续跑次数或累计时间上限时显示 resume_reason=resume_budget_exhausted;额度未耗尽的失败会暂时显示为 queued,表示等待自动恢复。
  • POST /tasks/{task_id}/retry:图片阶段 Task 一律返回稳定错误 image_stage_retry_forbidden;完整 Job 必须调用 Job retry,独立阶段必须调用 /images/stages。普通非图片任务仍可使用该入口。

任务记录、去重、claim generation 和租约持久化在 PostgreSQL;服务重启后 queued 可继续执行,过期 running 按租约重新认领或失败。三类图片处理 Task 只由逐图 ImageProcessingWorker 扫描、认领和执行;进程级通用 Worker manager 会排除它们,也不会为图片链创建旧的批次 cache_generation finalizer。

Operation policy

GET /operations/availability 或传入单个 operation 参数只执行非权威 probe,返回可用状态、稳定拒绝原因和可选 retry_at,不会创建 reservation 或返回 grant。首期 operation 名称为 image.upload、analysis.agent、analysis.reverse_image_search 和 image.delete。适配宿主通过服务端 policy 注入额度与策略规则;grant 只保存在服务端并绑定 scope、稳定幂等键和真实 Task,客户端字段不能伪造或覆盖它。

图片上传、合集导入、删除和 Agent/反向图片外部副作用分别在 durable 副作用边界执行 acquire/commit;只有能够证明副作用尚未开始时才 release。grant association 还会比较服务端 resource、Task、source、units 和 input digest 指纹;已提交、已释放或未知的关联只能供恢复观察,不能再次作为执行授权。策略拒绝、额度限制和策略不可用分别映射为 403 operation_forbidden、429 operation_limit_exceeded 和 503 operation_policy_unavailable。

内部 Agent callback

POST /internal/reverse-image/search 是唯一的内部联网反向图片检索 callback,不是公共接口。它在读取 multipart body 前要求 Runner 注入的短期 HMAC callback token,通过 X-MemeMeow-Callback、X-MemeMeow-Callback-Token 或 Bearer 头传递;token 绑定当前 Task、scope、claim generation、owner、attempt、目标 SHA、operation、issuer/audience、key id 和过期时间。路由还会从 PostgreSQL 复核 Task 类型、运行状态、租约和目标图片,调用方不能自报 scope、策略或任意图片。反向图片请求的 request_id 和 input_digest 都可省略;服务端在 Task/目标校验和受控裁剪后规范化 search type、language、country、query、布尔值和实际图片 SHA,返回持久化的权威 request ID。视觉候选不会通过 callback 查询,而是在 Agent 启动前由服务端冻结候选清单。

Agent 只获得当前任务 token、内部地址和 executor token,不获得 callback 根 secret、SERPAPI_API_KEY 或数据库凭据,也不得绕过 callback 直接访问 provider。callback secret 由 MEMEMEOW_AGENT_CALLBACK_SECRET 配置,生产部署必须通过独立服务身份和网络隔离保护;MEMEMEOW_AGENT_CALLBACK_VERIFICATION_KEYS 可用 kid=secret,kid=secret 提供轮换期间的旧 key 验证窗口。根 secret 缺失、格式错误、必需 callback 复合索引未就绪或 verifier 异常时 callback 保持不可用,不回退到 request-ID-only 逻辑。凭据轮换后旧 claim 应收束并显式重试。无凭据、旧 claim、租约过期、目标 SHA 变化和超限 body 都以稳定错误拒绝。迁移会先扫描历史逻辑重复或字段缺失并 fail-closed,不删除、合并或猜测覆盖既有事实;无 callback 的 local 直连仍使用原有缓存和显式 request ID 语义。

配置与访问策略

  • GET /config:只返回模型名、provider 是否配置和 *_api_key_configured 布尔状态;完整 URL、路径和密钥不返回。上传能力字段包含服务端强制的 max_files_per_request: 20、仅供客户端调度提示的 max_concurrent_upload_requests: 2 和可选 max_request_bytes: null|正整数;服务端不以并发提示字段执行在途请求 admission,文件数和总字节预算仍由服务端执行。
  • 本地视觉活动模型固定为 dinov2_vitb14、768 维和 dinov2_vitb14-rgb224-first-frame-v1;Compose API 从 mememeow-visual:8276/health 读取真实模型状态,权重只在视觉容器内只读挂载并按 MEMEMEOW_VISUAL_WEIGHTS_SHA256 校验,未配置时任务返回 visual_model_not_configured。历史 DINOv3 向量表保留但不会参与活动匹配。
  • 视觉源码目录由 MEMEMEOW_VISUAL_MODEL_REPO 配置,仅服务端读取;源码提交和权重许可要求见 docs/visual-model-baseline.md。
  • Agent 只读取任务启动前由服务端写入的候选图片清单。清单已经固定 scope、图片指纹、候选数量、排序和可读取的图片引用,Agent 不能提交 scope 或 top_k 重新查询图片库。
  • .env 关键字段:EMBEDDING_API_KEY、EMBEDDING_BASE_URL、EMBEDDING_MODEL、MEMEMEOW_OPENCODE_BASE_URL、MEMEMEOW_OPENCODE_API_KEY、MEMEMEOW_OPENCODE_MODEL、MEMEMEOW_OPENCODE_RUNTIME_ROOT、MEMEMEOW_IMAGE_ROOT、MEMEMEOW_AGENT_CALLBACK_SECRET。Compose 首次启动会在 mememeow-agent-executor-secret named volume 中生成 0600 的随机 executor token,API 以只读方式读取;旧版 host 运维模式仍可显式设置 MEMEMEOW_AGENT_EXECUTOR_TOKEN。生产 API 通过 Compose DNS 调用 executor,不使用 Docker CLI 或 socket。
  • Agent 运行模式只接受 auto、executor 和 host。auto 在 executor URL 与 token 同时可用时选择 executor,否则选择 host;显式 executor 缺少配置时失败关闭。旧 Docker runtime、容器名和运行时字段不能启用任何执行分支;回滚使用显式 host,运维诊断使用当前 project 的 docker compose exec <service>。
  • MEMEMEOW_PROTECTED_MODE=true 时仅放行 MEMEMEOW_ALLOWED_ENDPOINTS;限流由 MEMEMEOW_RATE_LIMIT_* 控制,超限返回 429 和 Retry-After。

Agent lane 的 MEMEMEOW_OPENCODE_CONCURRENCY、MEMEMEOW_AGENT_SCOPE_CONCURRENCY 和资源池容量必须为正整数,scope 容量不得超过全局容量。资源槽位只限制运行,不限制排队数量;任务的 lane_resource_key 在提交时冻结,公平状态按资源池分别维护。容量由部署环境决定,设置页保存的并发值需要重启后生效。

系统不提供注册、JWT、角色或多租户权限接口。资源包和社区同步功能已从生产入口移除。