Refactor chat storage design by replacing history with message and resume models

- Removed the `history` model and replaced it with a new `message` model to streamline chat message storage and retrieval.
- Introduced a `resume` model to enhance the chat's ability to recover from interruptions, capturing essential execution state data.
- Updated the `CHAT_STORAGE_DESIGN.md` to reflect these changes, including new fields and relationships for the message and resume models.
- Revised related functions and tests to support the new data structures, ensuring improved integrity and performance in chat interactions.
This commit is contained in:
Max 2025-12-09 10:20:07 +08:00
parent 0c9cb15d94
commit 81c32ce7a5
8 changed files with 580 additions and 328 deletions

View file

@ -151,7 +151,7 @@ Stores user-visible messages (both user input and assistant responses).
| `block_id` | string(64) | Yes | Yes | Block grouping ID |
| `thread_id` | string(64) | Yes | Yes | Thread grouping ID |
| `assistant_id` | string(200) | Yes | Yes | Assistant ID (join to get name/avatar) |
| `sequence` | integer | No | Yes | Message order within chat |
| `sequence` | integer | No | - | Message order within chat (in composite) |
| `metadata` | json | Yes | - | Additional metadata |
| `created_at` | timestamp | No | Yes | Creation timestamp |
| `updated_at` | timestamp | No | - | Last update timestamp |
@ -434,26 +434,26 @@ Stores execution state for resume/retry functionality. **Only written when reque
**Table Name:** `agent_resume`
| Column | Type | Nullable | Index | Description |
| ----------------- | ----------- | -------- | ------ | -------------------------------- |
| `id` | ID | No | PK | Auto-increment primary key |
| `resume_id` | string(64) | No | Unique | Unique resume record identifier |
| `chat_id` | string(64) | No | Yes | Parent chat ID |
| `request_id` | string(64) | No | Yes | Request ID |
| `assistant_id` | string(200) | No | Yes | Assistant executing this step |
| `stack_id` | string(64) | No | Yes | Stack node ID for this execution |
| `stack_parent_id` | string(64) | Yes | Yes | Parent stack ID (for A2A calls) |
| `stack_depth` | integer | No | - | Call depth (0=root, 1+=nested) |
| `type` | enum | No | Yes | Step type |
| `status` | enum | No | Yes | Status: `interrupted`, `failed` |
| `input` | json | Yes | - | Step input data |
| `output` | json | Yes | - | Step output data (partial) |
| `space_snapshot` | json | Yes | - | Space data snapshot for recovery |
| `error` | text | Yes | - | Error message if failed |
| `sequence` | integer | No | Yes | Step order within request |
| `metadata` | json | Yes | - | Additional metadata |
| `created_at` | timestamp | No | Yes | Creation timestamp |
| `updated_at` | timestamp | No | - | Last update timestamp |
| Column | Type | Nullable | Index | Description |
| ----------------- | ----------- | -------- | ------ | ---------------------------------------- |
| `id` | ID | No | PK | Auto-increment primary key |
| `resume_id` | string(64) | No | Unique | Unique resume record identifier |
| `chat_id` | string(64) | No | Yes | Parent chat ID |
| `request_id` | string(64) | No | Yes | Request ID |
| `assistant_id` | string(200) | No | Yes | Assistant executing this step |
| `stack_id` | string(64) | No | Yes | Stack node ID for this execution |
| `stack_parent_id` | string(64) | Yes | Yes | Parent stack ID (for A2A calls) |
| `stack_depth` | integer | No | - | Call depth (0=root, 1+=nested) |
| `type` | enum | No | Yes | Step type |
| `status` | enum | No | Yes | Status: `interrupted`, `failed` |
| `input` | json | Yes | - | Step input data |
| `output` | json | Yes | - | Step output data (partial) |
| `space_snapshot` | json | Yes | - | Space data snapshot for recovery |
| `error` | text | Yes | - | Error message if failed |
| `sequence` | integer | No | - | Step order within request (in composite) |
| `metadata` | json | Yes | - | Additional metadata |
| `created_at` | timestamp | No | Yes | Creation timestamp |
| `updated_at` | timestamp | No | - | Last update timestamp |
**Space Snapshot:**

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,8 @@ import (
var systemModels = map[string]string{
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
"__yao.agent.chat": "yao/models/agent/chat.mod.yao",
"__yao.agent.history": "yao/models/agent/history.mod.yao",
"__yao.agent.message": "yao/models/agent/message.mod.yao",
"__yao.agent.resume": "yao/models/agent/resume.mod.yao",
"__yao.attachment": "yao/models/attachment.mod.yao",
"__yao.audit": "yao/models/audit.mod.yao",
"__yao.config": "yao/models/config.mod.yao",

View file

@ -197,7 +197,8 @@ var testServer *http.Server = nil
var testSystemModels = map[string]string{
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
"__yao.agent.chat": "yao/models/agent/chat.mod.yao",
"__yao.agent.history": "yao/models/agent/history.mod.yao",
"__yao.agent.message": "yao/models/agent/message.mod.yao",
"__yao.agent.resume": "yao/models/agent/resume.mod.yao",
"__yao.attachment": "yao/models/attachment.mod.yao",
"__yao.audit": "yao/models/audit.mod.yao",
"__yao.config": "yao/models/config.mod.yao",

View file

@ -1,53 +1,103 @@
{
"name": "Chat",
"label": "Chat",
"description": "Chat table for storing chat metadata and information",
"description": "Chat session table for storing chat metadata and session information",
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": { "name": "agent_chat", "comment": "Agent chat table" },
"table": { "name": "agent_chat", "comment": "Agent chat session table" },
"columns": [
{
"name": "id",
"type": "ID",
"label": "Chat ID",
"comment": "Unique chat identifier"
"label": "ID",
"comment": "Auto-increment primary key"
},
{
"name": "chat_id",
"type": "string",
"label": "Chat ID",
"comment": "Chat identifier",
"length": 200,
"comment": "Unique chat identifier",
"length": 64,
"nullable": false,
"unique": true,
"index": true
"unique": true
},
{
"name": "title",
"type": "string",
"label": "Title",
"comment": "Chat title",
"length": 200,
"length": 500,
"nullable": true
},
{
"name": "assistant_id",
"type": "string",
"label": "Assistant ID",
"comment": "Assistant identifier",
"comment": "Associated assistant ID",
"length": 200,
"nullable": false,
"index": true
},
{
"name": "mode",
"type": "string",
"label": "Mode",
"comment": "Chat mode (default: chat)",
"length": 50,
"nullable": false,
"default": "chat"
},
{
"name": "status",
"type": "enum",
"label": "Status",
"comment": "Chat status",
"option": ["active", "archived"],
"default": "active",
"nullable": false,
"index": true
},
{
"name": "public",
"type": "boolean",
"label": "Public",
"comment": "Whether shared across all teams",
"default": false,
"nullable": false
},
{
"name": "share",
"type": "enum",
"label": "Share",
"comment": "Sharing scope",
"option": ["private", "team"],
"default": "private",
"nullable": false,
"index": true
},
{
"name": "sort",
"type": "integer",
"label": "Sort",
"comment": "Sort order for display",
"default": 9999
},
{
"name": "last_message_at",
"type": "datetime",
"label": "Last Message At",
"comment": "Timestamp of last message",
"nullable": true,
"index": true
},
{
"name": "silent",
"type": "boolean",
"label": "Silent Mode",
"comment": "Whether this is a silent chat",
"default": false,
"index": true
"name": "metadata",
"type": "json",
"label": "Metadata",
"comment": "Additional metadata",
"nullable": true
}
],
"relations": {
@ -57,20 +107,19 @@
"key": "assistant_id",
"foreign": "assistant_id"
},
"history": {
"messages": {
"type": "hasMany",
"model": "__yao.agent.history",
"model": "__yao.agent.message",
"key": "chat_id",
"foreign": "cid"
"foreign": "chat_id"
},
"resumes": {
"type": "hasMany",
"model": "__yao.agent.resume",
"key": "chat_id",
"foreign": "chat_id"
}
},
"indexes": [
{
"name": "idx_agent_chat_silent",
"columns": ["silent", "created_at"],
"type": "index",
"comment": "Index for silent mode filtering"
}
],
"option": { "timestamps": true, "soft_deletes": false, "permission": true }
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
}

View file

@ -1,126 +0,0 @@
{
"name": "History",
"label": "Chat History",
"description": "Chat history table for storing detailed chat message information",
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": { "name": "agent_history", "comment": "Agent chat history table" },
"columns": [
{
"name": "id",
"type": "ID",
"label": "Record ID",
"comment": "Unique record identifier"
},
{
"name": "chat_id",
"type": "string",
"label": "Chat ID",
"comment": "Chat identifier",
"length": 200,
"nullable": true,
"index": true
},
{
"name": "name",
"type": "string",
"label": "Role Name",
"comment": "Role display name",
"length": 200,
"nullable": true
},
{
"name": "content",
"type": "text",
"label": "Message Content",
"comment": "Text content of the message",
"nullable": true
},
{
"name": "context",
"type": "json",
"label": "Context",
"comment": "Message context data",
"nullable": true
},
{
"name": "assistant_id",
"type": "string",
"label": "Assistant ID",
"comment": "Assistant identifier",
"length": 200,
"nullable": true,
"index": true
},
{
"name": "assistant_name",
"type": "string",
"label": "Assistant Name",
"comment": "Assistant display name",
"length": 200,
"nullable": true
},
{
"name": "assistant_avatar",
"type": "string",
"label": "Assistant Avatar",
"comment": "Assistant avatar URL",
"length": 200,
"nullable": true
},
{
"name": "mentions",
"type": "json",
"label": "Mentions",
"comment": "Mention information in the message",
"nullable": true
},
{
"name": "silent",
"type": "boolean",
"label": "Silent Mode",
"comment": "Whether this is a silent message",
"default": false,
"index": true
},
{
"name": "expired_at",
"type": "timestamp",
"label": "Expired At",
"comment": "Record expiration time",
"nullable": true,
"index": true
}
],
"relations": {
"chat": {
"type": "hasOne",
"model": "__yao.agent.chat",
"key": "chat_id",
"foreign": "chat_id"
},
"assistant": {
"type": "hasOne",
"model": "__yao.agent.assistant",
"key": "assistant_id",
"foreign": "assistant_id"
},
"user": {
"type": "hasOne",
"model": "__yao.user",
"key": "user_id",
"foreign": "user_id"
}
},
"indexes": [
{
"name": "idx_agent_history_expired",
"columns": ["expired_at", "silent"],
"type": "index",
"comment": "Index for expiration and cleanup"
}
],
"option": { "timestamps": true, "soft_deletes": false, "permission": true }
}

View file

@ -0,0 +1,134 @@
{
"name": "Message",
"label": "Message",
"description": "Chat message table for storing user-visible messages",
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": { "name": "agent_message", "comment": "Agent chat message table" },
"columns": [
{
"name": "id",
"type": "ID",
"label": "ID",
"comment": "Auto-increment primary key"
},
{
"name": "message_id",
"type": "string",
"label": "Message ID",
"comment": "Unique message identifier",
"length": 64,
"nullable": false,
"unique": true
},
{
"name": "chat_id",
"type": "string",
"label": "Chat ID",
"comment": "Parent chat ID",
"length": 64,
"nullable": false,
"index": true
},
{
"name": "request_id",
"type": "string",
"label": "Request ID",
"comment": "Request ID for grouping",
"length": 64,
"nullable": true,
"index": true
},
{
"name": "role",
"type": "enum",
"label": "Role",
"comment": "Message role",
"option": ["user", "assistant"],
"nullable": false,
"index": true
},
{
"name": "type",
"type": "string",
"label": "Type",
"comment": "Message type (text, image, loading, tool_call, retrieval, etc.)",
"length": 50,
"nullable": false
},
{
"name": "props",
"type": "json",
"label": "Props",
"comment": "Message properties (content, url, etc.)",
"nullable": false
},
{
"name": "block_id",
"type": "string",
"label": "Block ID",
"comment": "Block grouping ID",
"length": 64,
"nullable": true,
"index": true
},
{
"name": "thread_id",
"type": "string",
"label": "Thread ID",
"comment": "Thread grouping ID for concurrent operations",
"length": 64,
"nullable": true,
"index": true
},
{
"name": "assistant_id",
"type": "string",
"label": "Assistant ID",
"comment": "Assistant ID (join to get name/avatar)",
"length": 200,
"nullable": true,
"index": true
},
{
"name": "sequence",
"type": "integer",
"label": "Sequence",
"comment": "Message order within chat",
"nullable": false
},
{
"name": "metadata",
"type": "json",
"label": "Metadata",
"comment": "Additional metadata (tool_call_id, tool_name, etc.)",
"nullable": true
}
],
"relations": {
"chat": {
"type": "hasOne",
"model": "__yao.agent.chat",
"key": "chat_id",
"foreign": "chat_id"
},
"assistant": {
"type": "hasOne",
"model": "__yao.agent.assistant",
"key": "assistant_id",
"foreign": "assistant_id"
}
},
"indexes": [
{
"name": "idx_msg_chat_seq",
"columns": ["chat_id", "sequence"],
"type": "index",
"comment": "Index for message ordering within chat"
}
],
"option": { "timestamps": true, "soft_deletes": false }
}

View file

@ -0,0 +1,170 @@
{
"name": "Resume",
"label": "Resume",
"description": "Resume table for storing execution state for resume/retry functionality",
"tags": ["agent", "system"],
"builtin": true,
"readonly": true,
"sort": 9999,
"table": { "name": "agent_resume", "comment": "Agent resume/recovery table" },
"columns": [
{
"name": "id",
"type": "ID",
"label": "ID",
"comment": "Auto-increment primary key"
},
{
"name": "resume_id",
"type": "string",
"label": "Resume ID",
"comment": "Unique resume record identifier",
"length": 64,
"nullable": false,
"unique": true
},
{
"name": "chat_id",
"type": "string",
"label": "Chat ID",
"comment": "Parent chat ID",
"length": 64,
"nullable": false,
"index": true
},
{
"name": "request_id",
"type": "string",
"label": "Request ID",
"comment": "Request ID",
"length": 64,
"nullable": false,
"index": true
},
{
"name": "assistant_id",
"type": "string",
"label": "Assistant ID",
"comment": "Assistant executing this step",
"length": 200,
"nullable": false,
"index": true
},
{
"name": "stack_id",
"type": "string",
"label": "Stack ID",
"comment": "Stack node ID for this execution",
"length": 64,
"nullable": false,
"index": true
},
{
"name": "stack_parent_id",
"type": "string",
"label": "Stack Parent ID",
"comment": "Parent stack ID (for A2A calls)",
"length": 64,
"nullable": true,
"index": true
},
{
"name": "stack_depth",
"type": "integer",
"label": "Stack Depth",
"comment": "Call depth (0=root, 1+=nested)",
"nullable": false,
"default": 0
},
{
"name": "type",
"type": "enum",
"label": "Type",
"comment": "Step type",
"option": [
"input",
"hook_create",
"llm",
"tool",
"hook_next",
"delegate"
],
"nullable": false,
"index": true
},
{
"name": "status",
"type": "enum",
"label": "Status",
"comment": "Resume status (only interrupted or failed are stored)",
"option": ["interrupted", "failed"],
"nullable": false,
"index": true
},
{
"name": "input",
"type": "json",
"label": "Input",
"comment": "Step input data",
"nullable": true
},
{
"name": "output",
"type": "json",
"label": "Output",
"comment": "Step output data (partial)",
"nullable": true
},
{
"name": "space_snapshot",
"type": "json",
"label": "Space Snapshot",
"comment": "Space data snapshot for recovery",
"nullable": true
},
{
"name": "error",
"type": "text",
"label": "Error",
"comment": "Error message if failed",
"nullable": true
},
{
"name": "sequence",
"type": "integer",
"label": "Sequence",
"comment": "Step order within request",
"nullable": false
},
{
"name": "metadata",
"type": "json",
"label": "Metadata",
"comment": "Additional metadata",
"nullable": true
}
],
"relations": {
"chat": {
"type": "hasOne",
"model": "__yao.agent.chat",
"key": "chat_id",
"foreign": "chat_id"
},
"assistant": {
"type": "hasOne",
"model": "__yao.agent.assistant",
"key": "assistant_id",
"foreign": "assistant_id"
}
},
"indexes": [
{
"name": "idx_resume_request_seq",
"columns": ["request_id", "sequence"],
"type": "index",
"comment": "Index for resume ordering within request"
}
],
"option": { "timestamps": true, "soft_deletes": false }
}