Skip to content

Latest commit

 

History

124 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TOMEET 总体技术方案

TOMEET 是一个可以长期对话的社交 Agent。

Agent 通过用户主动提供的文本、图片、短录音和持续对话认识用户。当用户想社交时,系统先使用 LLM 贪心选择当前最高匹配的一位用户;双方接受后建立房间,再逐位邀请新用户直至满员或成员明确停止匹配。活动结束后,用户向 Agent 反馈感受,Agent 据此持续对齐用户意图。

当前实现

仓库已经包含可运行的纯后端框架;可视化前端位于仓库之外:

  • apps/api:Fastify API,部署到 Railway。
  • apps/intelligence-worker:支持多并发槽位的智能任务 Worker,部署到 Railway。
  • apps/wechat-ilink-worker:微信 iLink 长轮询、收发消息与连接生命周期 Worker,部署到 Railway。
  • apps/relationship-worker:把双方确认的关系凭证锚定到 Injective EVM,并处理可重试撤销。
  • contracts/RelationshipRegistry.sol:隐私承诺式、不可转让的链上关系证明注册表。
  • packages/*:契约、Agent、用户模型、匹配、游戏目录、房间、反馈、数据访问和任务编排。
  • supabase/migrations:业务表、索引、私有 Storage Bucket 和并发安全 RPC。
  • Agent Memory V2:独立证据记忆、隐藏 profile、token-budgeted context 与同用户任务 FIFO;详见 docs/agent-memory-context.md

Supabase 是正式持久化路径。内存 Store 只用于自动测试和 DEMO_MODE 本地预览。

本地快速预览

仓库要求 Node.js 22 或更高版本,并通过 packageManager 固定使用 pnpm 10.14.0。仓库根目录的 .nvmrc 可用于切换 Node.js 主版本;首次运行前启用 Corepack:

nvm use 22
corepack enable
pnpm --version

pnpm --version 应输出 10.14.0。不要使用 Node.js 20 或未锁定版本的包管理器生成 lockfile。

无需 Supabase 凭据即可先验证完整流程:

pnpm install --frozen-lockfile
cp .env.example .env

.env 中的 DEMO_MODE 改为 true,然后运行:

pnpm dev
  • API:http://localhost:4000

本仓库不再提供本地页面。接口联调使用自动测试、docs/openapi.yaml 或仓库外部客户端。

当前 .env.example 默认使用真实硅基流动模型。填写 LLM_API_KEY 后,使用以下组合可以在不接 Supabase 的情况下做真实模型场景测试:

DEMO_MODE=true
LLM_API_BASE_URL=https://api.siliconflow.cn/v1
LLM_TEXT_MODEL=Qwen/Qwen3-Omni-30B-A3B-Instruct
LLM_VISION_MODEL=Qwen/Qwen3-Omni-30B-A3B-Instruct
TAVILY_API_KEY=...

这时只有业务数据和测试成员保存在内存中,Agent 理解、图片理解、动作识别、组人和游戏选择全部由真实模型完成。运行时不提供 Mock 模型;Mock 只存在于自动测试代码中。

连接托管 Supabase

  1. 创建 Supabase 项目。
  2. 使用 Supabase CLI 将迁移推送到该项目:
supabase login
supabase link --project-ref <project-ref>
supabase db push
  1. 如需单人走通匹配,在开发项目的 SQL Editor 执行 supabase/seed.sql。这些种子成员只用于测试,会自动确认房间。
  2. 配置 API 和 Worker 的 SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY
  3. API 使用 DEMO_MODE=false,Worker 必须配置真实模型。

使用真实托管模型时设置:

LLM_API_KEY=...
LLM_API_BASE_URL=https://api.siliconflow.cn/v1
LLM_TEXT_MODEL=Qwen/Qwen3-Omni-30B-A3B-Instruct
LLM_VISION_MODEL=Qwen/Qwen3-Omni-30B-A3B-Instruct
LLM_AUDIO_MODEL=FunAudioLLM/SenseVoiceSmall
TAVILY_API_KEY=...
TAVILY_API_BASE_URL=https://api.tavily.com

LLM 适配器采用 OpenAI 兼容的 Chat Completions HTTP 边界,当前已通过硅基流动 Qwen/Qwen3-Omni-30B-A3B-Instruct 的文本、图片和 JSON 输出验证。

Agent 会先生成受约束的搜索计划。明确要求联网、询问实时信息或出现无法可靠识别的专名时,Worker 使用 Tavily 搜索,再让模型只依据搜索证据生成候选回复,并在发布前独立核验活动名称、地点、日期和日程。来源保留在 webSearch.sources 元数据中,不拼接到 Agent 消息正文。普通聊天、稳定常识和单纯的社交意图不会调用搜索。未配置 TAVILY_API_KEY 时服务仍可启动,但 Agent 会明确说明无法联网核实,不会根据模型记忆猜测实时事实。

真实模型会输出受约束的结构化动作,包括开始/取消/重开匹配、选择/刷新候选、授权或停止主动推送、把 watching 重新激活为实时匹配、退出/确认/完成房间和提交反馈。Worker 执行动作前仍会通过领域规则和 Supabase RPC 校验,模型不能绕过房间状态或匹配约束。

AdventureX 冷启动匹配使用在线贪心竞价:当前 waiting 用户优先,已授权主动推送的 watching 用户次之;活动最低人数是硬约束,只有 Agent 判定为 goodexcellent 的人与活动组合才会发送。30 秒只是后台清算 tick,90 秒只在 1–3 个真实候选实际生成后开始。没有合格候选时系统如实说明池小或契合度不足,并询问是否以后通过微信主动推送。

业务流程产生候选、成局、超时、房间变化或渠道能力边界时,只向 Hosted Agent 提交结构化事实,不在生产代码中拼接固定回复。Agent 先结合用户当前对话和记忆摘要生成措辞,再进行一次基于同一事实载荷的发布前校验;选项编号必须完整覆盖,确认成员与可能成员不能混写,也不能新增人格或兴趣标签。唯一明确保留的硬编码产品话术是首次 AdventureX 欢迎语。

Railway 部署

生产上线的完整变量表、部署顺序、Supabase Auth 和冒烟验收步骤见 docs/railway-production.md。前端对接同时参考 docs/api.md 和机器可读的 docs/openapi.yaml

正式发布在同一个 Railway Project 中保留 Intelligence Worker、Web API、WeChat API、WeChat Worker 和 Relationship Worker,并统一从同一个 main commit 发布。详细变量和发布顺序见 docs/agent-layer-release.md

API Service

  • Config file:/railway.api.toml
  • 环境变量:NODE_ENV=productionSUPABASE_URLSUPABASE_SERVICE_ROLE_KEYFRONTEND_ORIGINDEMO_MODE=falseADVENTUREX_MATCHING_V1=true
  • FRONTEND_ORIGIN 支持逗号分隔多个来源,例如本地测试台和现有 Vercel 域名。
  • Agent 等用户接口要求 Authorization: Bearer <Supabase access token>;公开微信扫码接口改用一次性 X-WeChat-Session-Token,详见 docs/wechat-qr-api.md
  • 新微信用户会收到一次性 Web 注册链接;注册页领取同一个匿名 Supabase 用户后,可升级为邮箱+密码、手机号+密码或 Google 登录,详见 docs/wechat-web-registration.md
  • Railway 通过 /health 做存活检查,通过 /ready 检查 Supabase 连接。

Intelligence Worker Service

  • Config file:/railway.worker.toml
  • 环境变量:SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY、真实模型配置、用于联网搜索的 TAVILY_API_KEY,以及与 API 一致的 ADVENTUREX_MATCHING_V1=true
  • WORKER_CONCURRENCY 默认 8,单实例最大允许 32;也可以在 Railway 横向增加副本。

Worker 使用 Supabase PostgreSQL 的 FOR UPDATE SKIP LOCKED 领取任务。多槽位和多副本不会重复领取同一任务;交互回复使用 partition_key=user:{userId},后台记忆与反馈使用 partition_key=memory:{userId},避免较慢的记忆任务阻塞用户当前回复,同时各自保持 FIFO。失败任务使用指数退避,处于未来退避时间的旧任务不会占住分区,进程中断后的锁会自动回收。

Relationship Worker Service

  • Config file:/railway.relationship.toml
  • 环境变量:Supabase 服务端凭据、Injective EVM RPC/Chain ID、已部署的 Registry 地址和专用 relayer 私钥。
  • 合约通过 pnpm contracts:test 验证;部署前为 Ignition 明确传入冷钱包 admin 和专用热钱包 attester,不要在生产沿用默认同一账户。

外部扫码前端接入

当前外部前端只负责微信扫码连接,不承载 Agent Layer。它将 API Base URL 指向 Railway Web API,并仅调用 docs/wechat-qr-api.md 定义的四个 /wechat/connect/sessions* 接口;完整机器可读契约见 docs/openapi.yaml

并发与一致性

  • 一个用户只能存在一个活跃匹配请求,由部分唯一索引保证。
  • LLM 任务通过唯一幂等键去重。
  • Worker 使用 SKIP LOCKED 并发领取任务。
  • 同一用户的交互回复与后台记忆分别按 user:*memory:* FIFO;两条队列互不阻塞。
  • 建房在单个数据库事务内锁定全部匹配请求,并再次校验等待状态、成员对应关系、重复成员和游戏人数范围。
  • 重叠匹配并发发生时,只有第一个事务能成功分配成员。
  • 建房记录 source_job_id,Worker 在建房后异常重试不会创建重复房间。
  • 用户模型采用版本号乐观锁,冲突时 Worker 自动重新读取并重试。
  • 房间确认、活动完成和反馈写入均在 Supabase RPC 内校验状态并原子提交。

工程验证

pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm check

pnpm check 按 lint、typecheck、test、build 的顺序执行,是本地和后续 CI 的统一质量门禁。

当前测试覆盖完整核心流程、同一用户 50 个并发匹配请求去重、32 个 Worker 槽位任务领取不重复,并会在 PGlite 轻量 PostgreSQL 内核中执行 Supabase migrations 和关键 RPC。PGlite 测试属于 migration/RPC smoke,不等同于本地 Supabase Auth、RLS、Storage 或 Realtime 集成测试;详细基线和缺口见 docs/qa-baseline.md

验证真实模型的结构化动作和匹配约束:

pnpm --filter @tomeet/intelligence-worker smoke:llm

该检查会真实调用配置的模型,验证发起匹配、确认房间、完成活动、反馈整理,以及匹配结果必须包含触发用户。

同时配置 LLM_API_KEYTAVILY_API_KEY 后,可以验证真实联网搜索、证据引用和 AdventureX 官方来源:

pnpm --filter @tomeet/intelligence-worker smoke:web-search

安装 k6 后可运行基础 API 压测:

API_BASE_URL=https://your-api.up.railway.app k6 run tests/load/k6-api.js

1. 产品流程

产品流程描述用户如何使用 TOMEET,不代表系统架构。

flowchart LR
    Conversation["与 Agent 长期对话"]
    Understanding["持续认识用户"]
    Intent["用户表达社交意图"]
    Match["LLM 贪心选择下一位用户"]
    Room["双边接受后建房并逐位扩充"]
    Offline["线下参与游戏"]
    Feedback["向 Agent 反馈感受"]
    Alignment["更新用户理解和意图"]

    Conversation --> Understanding
    Understanding --> Intent
    Intent --> Match
    Match --> Room
    Room --> Offline
    Offline --> Feedback
    Feedback --> Alignment
    Alignment --> Conversation
Loading

2. 系统架构

flowchart TB
    DNS["Cloudflare DNS"]
    Web["Next.js Web · Vercel"]
    API["Application API · Railway"]
    Worker["Intelligence Worker · Railway"]
    DB["PostgreSQL · Supabase"]
    Storage["Multimodal Storage · Supabase"]
    LLM["Multimodal LLM / Text LLM"]
    Search["Tavily Web Search"]

    DNS --> Web
    DNS --> API
    Web --> API
    Web --> Storage
    API --> DB
    API --> Storage
    API --> Worker
    Worker --> DB
    Worker --> Storage
    Worker --> LLM
    Worker --> Search
Loading

采用单仓库、模块化单体和独立智能任务 Worker:

  • Vercel 运行前端。
  • Railway 运行 API 服务和 Intelligence Worker。
  • Supabase 保存业务数据和多模态原文件。
  • Cloudflare 只负责域名 DNS 解析。
  • LLM 负责用户理解、长期记忆更新、组人和游戏选择。
  • Tavily 只接收模型生成的短搜索查询,为实时外部事实提供网页证据。

3. 核心模块

Agent Core

  • 维护用户与 Agent 的长期对话。
  • 按 token 预算组装最近对话、短 checkpoint、隐藏 profile 和按需检索的详细记忆。
  • 先冻结回复计划与业务 action,再由只读证据 finalizer 生成最终回复。
  • 通过对话确认用户当前社交意图。

User Model

user_models 保留兼容字段、当前意图和业务历史;新的长期理解使用独立的证据层与摘要层:

  • UserMemory:带来源、状态、确认/使用次数和 TTL 的低敏感自然语言证据。
  • UserMemoryProfile:可从 active memory 重建的隐藏 profileNarrativematchingNarrative
  • CurrentIntent:用户当前的社交意图。
  • SocialHistory:历史匹配和线下游戏。
  • FeedbackMemory:兼容保留的活动反馈摘要。
  • MultimodalUnderstanding:有界的图片/短录音近期理解;不会自动成为稳定事实。

默认上下文不再注入完整 user_models。详细策略见 Agent Memory / Context V2

LLM Matchmaking

只有当用户明确表达想社交时,系统才创建 MatchRequest

LLM 匹配任务只读取受治理的自然语言输入:

  • 当前等待中的匹配请求。
  • 每位用户本次表达社交意图时的原话。
  • 由明确低敏感记忆整合出的连续自然语言 matchingNarrative
  • 人工策划游戏的说明、人数、条件和执行过程。

原始 profile、详细记忆、多模态原文、兴趣标签、intentTagstraits、性格类型、人口属性、关键词计数和标签分数都不会进入匹配模型输入。游戏目录可以保留运营元数据,但匹配只看到自然语言体验说明与硬性人数条件。

初始 LLM 输出结构化 MatchDecision

  • 触发用户和当前最高匹配的另一位用户,共 2 人。
  • 选中的线下游戏。
  • 匹配判断摘要。

系统先创建双边邀请,双方接受后才创建房间。房间处于 active 时,每次只为当前最高匹配的一位等待用户创建入房邀请;接受后原子加入并继续下一轮。达到 capacity 自动变为 full,任一成员明确说“停止匹配”等同义指令后变为 stopped

Offline Game Catalog

线下游戏由策划人员提前录入,Agent 和 LLM 只能选择已有游戏。

每个游戏保存:

  • 游戏名称和说明。
  • 最少与最多参与人数。
  • 适合的社交意图。
  • 体验特点和参与条件。
  • 线下执行说明。

Match Room

匹配房间只保存:

  • 2–10 名成员。
  • 选中的线下游戏。
  • 成员确认状态。
  • 持续匹配状态和房间人数上限。
  • 活动完成状态。

房间不承载在线游戏过程。

Post-event Feedback

活动结束后,用户通过 Agent 表达:

  • 对本次人群的感受。
  • 对线下游戏的感受。
  • 是否建立了想继续发展的连接。
  • 下一次希望保持或改变什么。

LLM 独立整理 CurrentIntent;反馈原话进入受隐私策略约束的异步记忆提取与 profile 重建流程。

4. 后台任务

Railway 上的 Intelligence Worker 处理:

  • 文本、图片和短录音理解。
  • Agent 回复生成。
  • 独立记忆提取和 profile 整合。
  • LLM 组人和线下游戏选择。
  • 活动后反馈整理。

任务写入 Supabase 的 llm_jobs 表。Worker 使用数据库锁领取任务,执行完成后写回结果和状态,不增加额外队列服务。

5. 核心数据表

users
conversations
messages
user_models
user_memories
user_memory_profiles
multimodal_inputs
match_requests
match_rooms
room_members
offline_games
post_event_feedback
llm_jobs

关键关系:

User
 ├── Conversation → Messages
 ├── UserModel
 ├── MultimodalInputs
 ├── MatchRequests
 └── PostEventFeedback

MatchRequest
 └── MatchRoom
      ├── RoomMembers
      ├── OfflineGame
      └── PostEventFeedback

6. 核心类型

export interface UserModel {
  userId: string;
  vibeNarrative: string;
  longTermProfile: Record<string, unknown>;
  currentIntent: Record<string, unknown>;
  socialHistory: string[];
  feedbackMemory: string[];
  multimodalUnderstanding: Record<string, unknown>;
  version: number;
}

export interface MatchRequest {
  requestId: string;
  userId: string;
  intentSnapshot: Record<string, unknown>;
  status: "matching" | "invited" | "matched" | "cancelled" | "expired";
  roomId: string | null;
  inviteId: string | null;
}

export interface MatchDecision {
  memberIds: string[];
  offlineGameId: string;
  summary: string;
}

export interface MatchRoom {
  roomId: string;
  memberIds: string[];
  offlineGameId: string;
  status: "confirming" | "confirmed" | "completed";
  matchingStatus: "active" | "stopped" | "full";
  capacity: number;
}

export interface PostEventFeedback {
  userId: string;
  roomId: string;
  peopleFeedback: string;
  gameFeedback: string;
  connectionUserIds: string[];
  nextIntent: string;
}

7. API

POST /agent/messages
POST /agent/multimodal-inputs

POST /match-requests
GET  /match-requests/:id
POST /match-requests/:id/cancel
GET  /match-invites/:id
POST /match-invites/:id/accept
POST /match-invites/:id/decline

GET  /rooms/:id
POST /rooms/:id/confirm
POST /rooms/:id/stop-match
POST /rooms/:id/complete
POST /rooms/:id/feedback

前端通过轮询获取匹配状态。Agent 回复可以使用普通请求或 SSE 流式返回。

8. 模板仓库结构

tomeet/
├── apps/
│   ├── api/                       # API 服务,部署到 Railway
│   ├── intelligence-worker/       # 智能任务,部署到 Railway
│   └── wechat-ilink-worker/       # 微信 iLink Worker,部署到 Railway
├── packages/
│   ├── contracts/
│   ├── agent-core/
│   ├── user-model/
│   ├── matchmaking/
│   ├── game-catalog/
│   ├── room/
│   ├── feedback/
│   ├── data/                      # Supabase / 内存数据适配器
│   └── intelligence/              # 后台任务编排
├── supabase/
│   └── migrations/
├── docs/
│   ├── product-flow.md
│   └── architecture.md
├── tests/
│   └── core-flow/
├── .env.example
├── package.json
├── pnpm-workspace.yaml
└── README.md

9. 部署

Supabase

  • 创建 PostgreSQL 项目。
  • 执行 supabase/migrations
  • 创建保存图片和短录音的 Storage Bucket。
  • Railway 使用服务端数据库连接和 Service Role Key。
  • Vercel 只使用公开配置或后端生成的上传授权。

Railway

在同一个 Railway Project 中保留四个 Service:

  • Web API 和 WeChat API:均运行 apps/api 的同一个 main commit。
  • Intelligence Worker:运行 apps/intelligence-worker
  • WeChat Worker:运行 apps/wechat-ilink-worker

四个 Service 连接同一套受控环境配置和 Supabase 项目。

Vercel

  • 继续使用已经部署的正式前端项目。
  • NEXT_PUBLIC_API_BASE_URL 指向 Railway API 域名。
  • 图片和短录音上传需要 Supabase 公开配置,但 Service Role Key 只能留在 Railway。
  • 本仓库不包含前端源码或可部署前端。

Cloudflare DNS

Cloudflare 管理域名解析:

@ / www / app  → Vercel 提供的域名记录
api            → Railway 提供的域名记录

初始使用 DNS only,HTTPS 证书由 Vercel 和 Railway 自动签发。

10. 环境变量

Vercel

NEXT_PUBLIC_API_BASE_URL
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY

Railway API

DATABASE_URL
SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
LLM_API_KEY
FRONTEND_ORIGIN
ADVENTUREX_MATCHING_V1

Railway Intelligence Worker

DATABASE_URL
SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
LLM_API_KEY
TAVILY_API_KEY
ADVENTUREX_MATCHING_V1

11. 完成标准

  • Agent 能通过长期对话和多模态输入持续认识用户。
  • 用户表达社交意图后,系统能创建匹配请求。
  • 用户能通过 Agent 收到并自然语言选择 1–3 个现场活动候选。
  • 系统只用明确接受的候选原子创建 3–10 人确认房间,并支持合适的开放局补位。
  • 成员、集合信息和招募状态变化能通过幂等 Agent 消息主动通知。
  • LLM 能按贪心机制逐位选择候选用户并选择一款已有线下游戏。
  • 系统能在双边接受后建房,持续邀请新成员,并在满员或收到停止指令后终止匹配。
  • 活动结束后,用户能向 Agent 反馈感受和连接结果。
  • 反馈能更新用户意图,并影响下一次匹配和游戏选择。

详细文档

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages