- Implement tests for the ImageProcessingWorker to validate task deduplication and policy interactions. - Create tests for operation policy to ensure proper grant handling and error responses. - Add tests for reverse image provider response structure to validate empty and malformed results. - Develop comprehensive tests for scope resolution and service validation to ensure correct behavior in various scenarios.
14 KiB
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 不依赖用户 request scope。宿主 non-local scope 必须提供受控 agent_input_provider;未配置或返回符号链接/非普通文件时任务稳定失败为 agent_input_provider_unavailable。内部 callback 由 API 独立验证短期任务凭据,网络隔离和内部路由暴露范围仍由部署宿主负责;本 API 不新增用户登录或 scope 管理接口。
进程内只运行一个 scope-aware Worker manager,所有 scope 共享线程池、handler registry、lane 背压、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"]}
结果只包含受控媒体 URL,按相关性和稳定 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、文件名、媒体 URL、metadata.status、文本索引embedding_status和图片视觉向量visual_embedding_status。 -
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 字段auto_name、多个files,逐文件返回成功或失败,图片直接写入当前请求 scope 的受控图片根;成功入库后自动创建或复用逐图处理 job;不接受目标目录、scope_id或user_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。 -
POST /images/context:请求{ "meme_id": "...", "reverse_image_policy": "forbid|auto" },异步创建或复用单图处理 job,返回processing_job_id和当前 Agent 阶段摘要。缺省策略为forbid。 -
POST /images/context/batch:请求{ "items": [{"meme_id":"..."}], "include_unready": true, "reverse_image_policy": "forbid|auto" },逐图返回处理 job 结果;省略items时不隐式扫描孤立文件。 -
POST /images/visual-embedding和/images/visual-embedding/batch:为既有图片提交完整图片处理 Job 的视觉前置;视觉任务失败必须使用完整 Job 重试或受限独立阶段入口。 -
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" }。单图处理固定为 visual -> agent -> text_embedding 三个阶段;每个阶段拥有独立叶子 Task,视觉和文本阶段不消耗 Agent operation grant。活动 job 的配置、目标 SHA、metadata hash 或策略不一致时返回 409 generation_policy_conflict。
GET /images/processing/{job_id} 返回有限状态:queued、running、succeeded、failed、blocked 或 unknown_execution,以及 current_stage、每阶段 task_id/attempt/error。跨 scope 或不存在的 ID 都返回 404 image_processing_job_not_found。
GET /images/processing 返回当前 scope 的完整 Job 父项及 visual、agent、text_embedding 三个阶段;Job 和叶子 Task 的 submission_mode 明确为 pipeline。
POST /images/stages 请求仅接受 { "meme_id": "...", "stage": "visual|agent|text_embedding", "reverse_image_policy": "forbid|auto" },创建或复用无父 Job 的独立阶段 Task。scope、图片 SHA、配置、grant 和 callback 均由服务端派生;返回 submission_mode=standalone 与 processing_job_id=null。
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、名称、成员数量、封面媒体 URL 和时间戳。POST /collections:请求{ "name": "工作" }创建空合集;名称会去除首尾空白并限制为 1 至 100 个字符。GET /collections/{collection_id}?page=1&page_size=50:返回合集元数据、成员总数和按加入时间稳定排序的成员。成员包含当前文件名、大小、状态和/media/{meme_id}。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、进度、消息、时间、错误和有限结果。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。策略拒绝、额度限制和策略不可用分别映射为 403 operation_forbidden、429 operation_limit_exceeded 和 503 operation_policy_unavailable。
内部 Agent callback
POST /internal/reverse-image/search 与 POST /internal/visual-search/match 不是公共接口。它们在读取 multipart/JSON 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、策略或任意图片。发布、轮换、旧任务收束和禁用式回滚见 docs/agent-callback-migration.md。
Agent 只获得当前任务 token、内部地址和 executor token,不获得 callback 根 secret、SERPAPI_API_KEY 或数据库凭据。callback secret 由 MEMEMEOW_AGENT_CALLBACK_SECRET 配置,生产部署必须通过独立服务身份和网络隔离保护;MEMEMEOW_AGENT_CALLBACK_VERIFICATION_KEYS 可用 kid=secret,kid=secret 提供轮换期间的旧 key 验证窗口。根 secret 缺失、格式错误或 verifier 异常时 callback 保持不可用,不回退到无认证。凭据轮换后旧 claim 应收束并显式重试。无凭据、旧 claim、租约过期、目标 SHA 变化和超限 body 都以稳定错误拒绝。
配置与访问策略
GET /config:只返回模型名、provider 是否配置和*_api_key_configured布尔状态;完整 URL、路径和密钥不返回。- 本地视觉活动模型固定为
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 通过内部
POST /internal/visual-search/match发送{ "task_id": "...", "top_k": 20, "exclude_self": true }。scope、查询图片和向量空间均从运行中的 Agent 任务推导;候选必须同 scope、当前 SHA 有效并有成功 research provenance。 .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-secretnamed volume 中生成 0600 的随机 executor token,API 以只读方式读取;旧版 host 运维模式仍可显式设置MEMEMEOW_AGENT_EXECUTOR_TOKEN。生产 API 通过 Compose DNS 调用 executor,不使用 Docker CLI 或 socket。MEMEMEOW_PROTECTED_MODE=true时仅放行MEMEMEOW_ALLOWED_ENDPOINTS;限流由MEMEMEOW_RATE_LIMIT_*控制,超限返回429和Retry-After。
系统不提供用户登录、注册、JWT、角色或多租户权限接口。资源包和社区同步功能已从生产入口移除。