Files
eryao/docs/protocols/divination/divination-run-protocol.md
ZL-Q b9617ae152 refactor(settings): 统一语言设置,合并 interface_language 和 ai_language
- 后端 Schema 将 interface_language 和 ai_language 合并为 language
- 前端设置界面只保留一个语言选项
- AI 回复语言统一使用 language 设置
- 更新协议文档
- 新增数据库迁移脚本
2026-04-28 17:19:47 +08:00

14 KiB

Divination Run Protocol (Frontend <-> Backend)

This document defines the structured contract for divination run input, backend hexagram derivation, and run event output.

Protocol verification status:

  • Backend route source: backend/src/v1/agent/router.py
  • Backend derivation source: backend/src/core/divination/derivation.py
  • Runtime payload schema source: backend/src/schemas/domain/divination.py

Compatibility strategy

  • Run/events contract: backward-compatible additive evolution.
  • History contract (GET /agent/history) currently requires-migration (see migration notes in this document).
  • Existing required fields cannot be removed or renamed without migration notes.
  • Canonical divination terminology values must remain Chinese.

Route overview

  • Submit run: POST /api/v1/agent/runs
  • Cancel run: POST /api/v1/agent/runs/{threadId}/cancel?runId=...
  • Stream events: GET /api/v1/agent/runs/{threadId}/events?runId=...
  • History snapshot: GET /api/v1/agent/history
  • Delete session: DELETE /api/v1/agent/sessions/{threadId}
  • Upload attachment: POST /api/v1/agent/attachments
  • Get attachment signed URL: GET /api/v1/agent/attachments/signed-url?bucket=...&path=...
  • Audio transcribe: POST /api/v1/agent/transcribe

Run request contract

RunAgentInput uses AG-UI shape. This protocol constrains two sections:

  1. messages[0].content (question text)
  2. forwardedProps.divinationPayload (structured divination input)

Required request shape

{
  "threadId": "uuid",
  "runId": "run_20260403_xxx",
  "state": {},
  "messages": [
    {
      "id": "msg_run_20260403_xxx_user_0",
      "role": "user",
      "content": "我最近换工作是否合适?"
    }
  ],
  "tools": [],
  "context": [],
  "forwardedProps": {
    "runtime_mode": "chat",
    "client_time": {
      "device_timezone": "Asia/Shanghai",
      "client_now_iso": "2026-04-03T20:30:00+08:00",
      "client_epoch_ms": 1775219400000
    },
    "divinationPayload": {
      "divinationMethod": "手动起卦",
      "questionType": "事业",
      "question": "我最近换工作是否合适?",
      "divinationTimeIso": "2026-04-03T20:30:00+08:00",
      "yaoLines": [
        "少阳",
        "少阴",
        "老阳",
        "少阴",
        "少阳",
        "老阴"
      ]
    }
  }
}

AG-UI required base fields

  • state: required object, frontend sends {} by default.
  • messages[0].id: required string id for user message.
  • tools: required array, frontend sends empty array when no tools requested.
  • context: required array, frontend sends empty array when no extra context.

divinationPayload strict rules

  • divinationMethod: enum, allowed values 手动起卦 | 自动起卦
  • questionType: non-empty string, max 32 chars, recommended Chinese category labels
  • question: non-empty string, max 300 chars
  • divinationTimeIso: RFC3339 datetime with timezone offset
  • yaoLines: exactly 6 items, order is 初爻 -> 上爻
  • yaoLines item enum: 少阳 | 少阴 | 老阳 | 老阴
  • Additional fields are forbidden.

Frontend coin-face to yaoLines derivation rules

This section is normative for frontend collection flows (手动起卦 and 自动起卦).

  • Both manual and auto flows MUST use the same canonical conversion logic.
  • Conversion baseline is manual flow semantics (huaCount baseline).
  • Auto flow (ziCount baseline) MUST be converted to huaCount before mapping.
  • Do not maintain separate mapping tables per page/screen.

Canonical mapping (huaCount -> yaoType):

  • 0 -> 老阴
  • 1 -> 少阳
  • 2 -> 少阴
  • 3 -> 老阳

Equivalent auto mapping (ziCount -> yaoType):

  • 0 -> 老阳
  • 1 -> 少阴
  • 2 -> 少阳
  • 3 -> 老阴

Implementation requirement:

  • Frontend should centralize this conversion in one reusable converter and use it in both manual and auto screens.
  • yaoLines sent to backend MUST always be derived from this canonical mapping and keep order 初爻 -> 上爻.

runtime_mode rules

  • Allowed values: chat | follow_up.
  • chat: first run for a session.
  • follow_up: follow-up run in existing session.
  • Missing or invalid runtime_mode MUST return 422 with code AGENT_RUNTIME_MODE_INVALID.
  • Current backend behavior still requires forwardedProps.divinationPayload in both modes.

Follow-up request note

  • Follow-up submit still uses POST /api/v1/agent/runs.
  • Required differences from first run:
    • threadId must be existing session id.
    • forwardedProps.runtime_mode must be follow_up.
    • messages[0].content is follow-up question text.
    • If threadId does not exist, backend returns 404 (AGENT_SESSION_NOT_FOUND).

Event output contract

During run streaming, backend emits standard AG-UI lifecycle events and two divination-relevant payload events:

  • Lifecycle: RUN_STARTED, RUN_FINISHED or RUN_ERROR.
  • Step events: STEP_STARTED, STEP_FINISHED with stepName (for example: worker).
  • Payload events: DIVINATION_DERIVED, TEXT_MESSAGE_END.

1) DIVINATION_DERIVED

  • Emitted once after backend derives hexagram data.
  • Payload field: divination (strict object).
  • Emitted only when runtime_mode=chat.

divination object:

{
  "question": "我最近换工作是否合适?",
  "questionType": "事业",
  "divinationMethod": "手动起卦",
  "divinationTime": "2026年04月03日 20:30",
  "binaryCode": "101001",
  "changedBinaryCode": "100001",
  "guaName": "山火贲",
  "guaNameHant": "山火賁",
  "upperName": "艮",
  "lowerName": "离",
  "targetGuaName": "山雷颐",
  "targetGuaNameHant": "山雷頤",
  "worldPosition": 1,
  "responsePosition": 4,
  "hasChangingYao": true,
  "ganzhi": {
    "yearGanZhi": "丙午",
    "monthGanZhi": "壬辰",
    "dayGanZhi": "辛亥",
    "timeGanZhi": "乙巳",
    "yearKongWang": "子丑",
    "monthKongWang": "午未",
    "dayKongWang": "寅卯",
    "timeKongWang": "戌亥",
    "yueJian": "辰土",
    "riChen": "亥水",
    "yuePo": "戌土",
    "riChong": "巳火"
  },
  "wuXingStatuses": {
    "木": "囚",
    "火": "休",
    "土": "旺",
    "金": "相",
    "水": "死"
  },
  "yaoInfoList": [
    {
      "position": 1,
      "spiritName": "虎",
      "spiritNameHant": "虎",
      "relationName": "官鬼",
      "relationNameHant": "官鬼",
      "tiganName": "卯",
      "elementName": "木",
      "isYang": true,
      "isChanging": false,
      "specialMark": "世"
    }
  ],
  "targetYaoInfoList": [],
  "fushenPositions": [2],
  "fushenInfoList": [
    {
      "position": 2,
      "relationName": "父母",
      "relationNameHant": "父母",
      "tiganName": "午",
      "elementName": "火"
    }
  ],
  "specialStatus": [],
  "interactions": [],
  "timeEffect": [],
  "riChenZhangSheng": []
}

Field notes:

  • guaNameHant, targetGuaNameHant: Traditional Chinese variants for卦名.
  • spiritNameHant, relationNameHant: Traditional Chinese variants for六神/六亲 names.
  • specialStatus: Special hexagram status indicators.
  • interactions: 爻位 interaction descriptions.
  • timeEffect: Time-based effect descriptions.
  • riChenZhangSheng: 日辰长生相关 information.

2) TEXT_MESSAGE_END

  • Standard final answer event.
  • runtime_mode=chat fields: status, sign_level, conclusion, focus_points, advice, keywords, answer, error, divination_derived.
  • runtime_mode=follow_up fields: status, answer, error.
  • runtime_mode=follow_up MUST NOT include sign_level, conclusion, focus_points, advice, keywords, divination_derived.
  • Language rule: conclusion, focus_points, advice, keywords, answer should follow user language preference unless user explicitly requests otherwise.
  • Canonical six-yao terms remain Chinese in protocol text (for example: 世爻、应爻、动爻、静爻、六亲、六神、伏神、月建、日辰、月破、日冲、空亡、五行旺衰).

Frontend should combine:

  • structural divination data from DIVINATION_DERIVED
  • interpretation text from TEXT_MESSAGE_END

History snapshot contract

GET /api/v1/agent/history is the canonical replay source for frontend history list and result reconstruction.

  • When threadId is provided, backend returns full session messages ordered by seq asc.
  • When threadId is omitted, backend returns one latest assistant message per session for history list summary.
  • threadId is the session identifier (same value as AG-UI run/events path parameter).

Required response shape

{
  "scope": "history_session_full",
  "threadId": "uuid|null",
  "day": null,
  "hasMore": false,
  "messages": [
    {
      "id": "uuid",
      "threadId": "session-uuid",
      "seq": 12,
      "role": "assistant",
      "content": "...",
      "timestamp": "2026-04-05T12:34:56+00:00",
      "agent_output": {
        "status": "success",
        "sign_level": "中上签",
        "conclusion": ["..."],
        "focus_points": ["..."],
        "advice": ["..."],
        "keywords": ["..."],
        "answer": "...",
        "divination_derived": {
          "binaryCode": "101001",
          "changedBinaryCode": "100001",
          "guaName": "山火贲"
        }
      }
    },
    {
      "id": "uuid",
      "threadId": "session-uuid",
      "seq": 11,
      "role": "user",
      "content": "我最近换工作是否合适?",
      "timestamp": "2026-04-05T12:34:12+00:00",
      "attachments": [
        {
          "mimeType": "image/png",
          "url": "https://...signed..."
        }
      ]
    }
  ]
}

Rules:

  • scope=history_session_full means full-thread replay with messages ordered by seq.
  • scope=history_sessions_latest_assistant means cross-session summary list.
  • Each messages[i].threadId is required and points to the owning session. Frontend must use this value to open detail replay and follow-up.
  • day and hasMore are retained for compatibility with old clients, but in history_session_full mode backend currently returns day=null and hasMore=false.
  • In history_sessions_latest_assistant, backend computes hasMore from session_limit + 1 query.
  • assistant message MUST provide agent_output when backend has valid worker output metadata.
  • agent_output.divination_derived uses the same shape as DIVINATION_DERIVED.divination payload.
  • Frontend reconstructs divination result page from agent_output data, not from local mock data.
  • agent_output.sign_level allowed values: 上上签 / 中上签 / 中下签 / 下下签.

Migration note (requires-migration)

Change set

  1. GET /agent/history?threadId=... now returns full session replay (scope=history_session_full) instead of day-window snapshot semantics.
  2. messages[i].threadId is now required.
  3. ui_schema is removed from history payload.
  4. runtime_mode=follow_up uses minimal TEXT_MESSAGE_END schema and no longer emits DIVINATION_DERIVED.
  5. Added DELETE /api/v1/agent/sessions/{threadId} with idempotent 204 No Content (already deleted or not found also returns 204).

Frontend migration steps

  1. Parse history as ordered message stream by messages[].seq.
  2. Use messages[].threadId as session id for opening follow-up.
  3. Do not depend on ui_schema in history payload.
  4. Keep day/hasMore optional-read only; do not use them for pagination in thread replay mode.

Rollback notes

  • If backend rolls back to day-window semantics, frontend must switch detail replay back to day-based load logic and stop requiring messages[].threadId.
  • Keep legacy parser branch behind feature flag during staged rollout.

Error contract linkage

  • All errors use RFC7807 with extension code and optional params.
  • Error code registry source: docs/protocols/common/http-error-codes.md.

Session delete contract

DELETE /api/v1/agent/sessions/{threadId}

  • Authorization: current user must own the session.
  • Semantics: soft delete session (deleted_at), history reads filter deleted sessions by default.
  • Success: 204 No Content.
  • Idempotent: already deleted or not found also returns 204 No Content.

Run cancel contract

POST /api/v1/agent/runs/{threadId}/cancel?runId=...

  • Authorization: current user must own the session.
  • Request: runId query parameter required.
  • Success response:
{
  "threadId": "uuid",
  "runId": "run_xxx",
  "accepted": true
}

Error codes (see common registry):

  • AGENT_SESSION_NOT_FOUND
  • AGENT_FORBIDDEN

Attachment upload contract

POST /api/v1/agent/attachments

  • Authorization: authenticated user.
  • Request: multipart/form-data with fields:
    • threadId (string, required)
    • file (binary, required)
  • Max file size: 5MB
  • Success response:
{
  "attachment": {
    "bucket": "agent-inputs",
    "path": "agent-inputs/{userId}/{filename}",
    "mimeType": "image/png",
    "url": "https://...signed..."
  }
}

Error codes (see common registry):

  • AGENT_ATTACHMENT_EMPTY
  • AGENT_ATTACHMENT_TOO_LARGE
  • AGENT_ATTACHMENT_STORAGE_UNAVAILABLE
  • AGENT_SESSION_NOT_FOUND
  • AGENT_FORBIDDEN

Attachment signed URL contract

GET /api/v1/agent/attachments/signed-url?bucket=...&path=...

  • Authorization: authenticated user.
  • Query params:
    • bucket (string, required)
    • path (string, required)
  • Path must be under agent-inputs/{userId}/ scope.
  • Success response:
{
  "bucket": "agent-inputs",
  "path": "agent-inputs/{userId}/{filename}",
  "url": "https://...signed..."
}

Error codes (see common registry):

  • AGENT_ATTACHMENT_BUCKET_INVALID
  • AGENT_ATTACHMENT_PATH_SCOPE_INVALID
  • AGENT_SIGNED_URL_GENERATION_FAILED

Transcribe contract

POST /api/v1/agent/transcribe

Request:

  • multipart/form-data
  • field name: audio
  • allowed content types: audio/wav, audio/x-wav, audio/wave
  • max payload bytes: 10MB

Response:

{
  "transcript": "我今天适合出门谈合作吗?"
}

Error codes (see common registry):

  • AGENT_AUDIO_UNSUPPORTED_FORMAT
  • AGENT_AUDIO_TOO_LARGE
  • AGENT_AUDIO_EMPTY
  • AGENT_ASR_UNAVAILABLE