Merge pull request #1372 from trheyi/main
Refactor chat storage design by replacing history with message
This commit is contained in:
commit
f040361f44
9 changed files with 1118 additions and 436 deletions
|
|
@ -13,6 +13,7 @@ const (
|
|||
TypeThinking = "thinking" // Reasoning/thinking process
|
||||
TypeLoading = "loading" // Loading/processing indicator
|
||||
TypeToolCall = "tool_call" // LLM tool/function call
|
||||
TypeRetrieval = "retrieval" // KB/Web search results (for feedback & analytics)
|
||||
TypeError = "error" // Error message
|
||||
TypeImage = "image" // Image content
|
||||
TypeAudio = "audio" // Audio content
|
||||
|
|
@ -281,7 +282,137 @@ msg := output.NewToolCallMessage(
|
|||
|
||||
---
|
||||
|
||||
### 6. Error (`error`)
|
||||
### 6. Retrieval (`retrieval`)
|
||||
|
||||
**Purpose:** Knowledge base and web search results (for feedback, analytics, and source attribution)
|
||||
|
||||
**Props Structure:**
|
||||
|
||||
```go
|
||||
type RetrievalProps struct {
|
||||
Query string `json:"query"` // Search query
|
||||
Sources []RetrievalSource `json:"sources"` // Retrieved sources
|
||||
TotalResults int `json:"total_results,omitempty"` // Total matching results
|
||||
QueryTimeMs int64 `json:"query_time_ms,omitempty"` // Query execution time
|
||||
Provider string `json:"provider,omitempty"` // Search provider (e.g., "tavily", "bing")
|
||||
}
|
||||
|
||||
type RetrievalSource struct {
|
||||
ID string `json:"id"` // Unique source ID within this retrieval
|
||||
Type string `json:"type"` // Source type: "kb", "web", "file", "api", "mcp"
|
||||
Title string `json:"title,omitempty"` // Source title
|
||||
Content string `json:"content"` // Retrieved content/snippet
|
||||
Score float64 `json:"score,omitempty"` // Relevance score
|
||||
URL string `json:"url,omitempty"` // URL for web sources
|
||||
CollectionID string `json:"collection_id,omitempty"` // KB collection ID
|
||||
DocumentID string `json:"document_id,omitempty"` // KB document ID
|
||||
ChunkID string `json:"chunk_id,omitempty"` // KB chunk ID
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // Additional metadata
|
||||
}
|
||||
```
|
||||
|
||||
**Example (Knowledge Base):**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "retrieval",
|
||||
"props": {
|
||||
"query": "How to configure Yao models?",
|
||||
"sources": [
|
||||
{
|
||||
"id": "src_001",
|
||||
"type": "kb",
|
||||
"collection_id": "col_docs",
|
||||
"document_id": "doc_123",
|
||||
"chunk_id": "chunk_456",
|
||||
"title": "Model Configuration Guide",
|
||||
"content": "To configure a model in Yao, create a .mod.yao file...",
|
||||
"score": 0.92,
|
||||
"metadata": {
|
||||
"file_path": "/docs/model.md",
|
||||
"page": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"total_results": 15,
|
||||
"query_time_ms": 120
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example (Web Search):**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "retrieval",
|
||||
"props": {
|
||||
"query": "latest AI news 2024",
|
||||
"sources": [
|
||||
{
|
||||
"id": "src_001",
|
||||
"type": "web",
|
||||
"url": "https://example.com/ai-news",
|
||||
"title": "AI Breakthroughs in 2024",
|
||||
"content": "Summary of the article...",
|
||||
"score": 0.95,
|
||||
"metadata": {
|
||||
"domain": "example.com",
|
||||
"published_at": "2024-01-10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"provider": "tavily",
|
||||
"total_results": 10,
|
||||
"query_time_ms": 850
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Helper:**
|
||||
|
||||
```go
|
||||
msg := output.NewRetrievalMessage(
|
||||
"How to configure Yao models?",
|
||||
[]output.RetrievalSource{
|
||||
{
|
||||
ID: "src_001",
|
||||
Type: "kb",
|
||||
CollectionID: "col_docs",
|
||||
DocumentID: "doc_123",
|
||||
ChunkID: "chunk_456",
|
||||
Title: "Model Configuration Guide",
|
||||
Content: "To configure a model in Yao...",
|
||||
Score: 0.92,
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
**Source Types:**
|
||||
|
||||
| Type | Description | Key Fields |
|
||||
| ------ | ----------------------- | ------------------------------------------ |
|
||||
| `kb` | Knowledge base document | `collection_id`, `document_id`, `chunk_id` |
|
||||
| `web` | Web search result | `url` |
|
||||
| `file` | Uploaded file | `file_id`, `file_path` |
|
||||
| `api` | External API result | `api_name`, `endpoint` |
|
||||
| `mcp` | MCP tool result | `server`, `tool` |
|
||||
|
||||
**Use Cases:**
|
||||
|
||||
- **Source Attribution**: Display citations in the chat UI
|
||||
- **User Feedback**: Allow users to rate individual sources (👍/👎)
|
||||
- **Analytics**: Track which documents/sources are most useful
|
||||
- **RAG Optimization**: Improve retrieval based on feedback data
|
||||
|
||||
**Adapter Behavior:**
|
||||
|
||||
- **CUI**: Renders as expandable source cards with feedback buttons
|
||||
- **OpenAI**: Converts to markdown citations or footnotes
|
||||
|
||||
---
|
||||
|
||||
### 7. Error (`error`)
|
||||
|
||||
**Purpose:** Error message
|
||||
|
||||
|
|
@ -316,7 +447,7 @@ msg := output.NewErrorMessage("Connection timeout", "TIMEOUT")
|
|||
|
||||
---
|
||||
|
||||
### 7. Action (`action`)
|
||||
### 8. Action (`action`)
|
||||
|
||||
**Purpose:** System-level action/command (not displayed to user, only processed by client)
|
||||
|
||||
|
|
@ -387,7 +518,7 @@ output.Send(ctx, output.NewTextMessage("I've opened the user details panel for y
|
|||
|
||||
---
|
||||
|
||||
### 8. Event (`event`)
|
||||
### 9. Event (`event`)
|
||||
|
||||
**Purpose:** Lifecycle event messages (stream_start, stream_end, connecting, etc.)
|
||||
|
||||
|
|
@ -479,7 +610,7 @@ output.Send(ctx, output.NewEventMessage("stream_end", "Stream completed", endDat
|
|||
|
||||
---
|
||||
|
||||
### 9. Image (`image`)
|
||||
### 10. Image (`image`)
|
||||
|
||||
**Purpose:** Image content
|
||||
|
||||
|
|
@ -522,7 +653,7 @@ msg := output.NewImageMessage("https://example.com/avatar.jpg", "User avatar")
|
|||
|
||||
---
|
||||
|
||||
### 10. Audio (`audio`)
|
||||
### 11. Audio (`audio`)
|
||||
|
||||
**Purpose:** Audio content
|
||||
|
||||
|
|
@ -567,7 +698,7 @@ msg := output.NewAudioMessage("https://example.com/audio.mp3", "mp3")
|
|||
|
||||
---
|
||||
|
||||
### 11. Video (`video`)
|
||||
### 12. Video (`video`)
|
||||
|
||||
**Purpose:** Video content
|
||||
|
||||
|
|
@ -644,6 +775,7 @@ OpenAI adapter converts built-in types to OpenAI format:
|
|||
| `thinking` | `delta.reasoning_content` | `props.content` | Reasoning content (o1 models) |
|
||||
| `loading` | `delta.reasoning_content` | `props.message` | Shows as thinking in OpenAI clients |
|
||||
| `tool_call` | `delta.tool_calls` | `props.{id, name, arguments}` | |
|
||||
| `retrieval` | `delta.content` | `props.sources` | Markdown citations/footnotes with source links |
|
||||
| `error` | `error` | `props.{message, code}` | |
|
||||
| `image` | `delta.content` | `props.{url, alt}` | Markdown: `` - displays inline |
|
||||
| `audio` | `delta.content` | `props.url` | Markdown link (can't display inline) |
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ Stores chat metadata and session information.
|
|||
| `assistant_id` | string(200) | No | Yes | Associated assistant ID |
|
||||
| `mode` | string(50) | No | - | Chat mode (default: "chat") |
|
||||
| `status` | enum | No | Yes | Status: `active`, `archived` |
|
||||
| `preset` | boolean | No | - | Whether this is a preset chat |
|
||||
| `public` | boolean | No | - | Whether shared across all teams |
|
||||
| `share` | enum | No | Yes | Sharing scope: `private`, `team` |
|
||||
| `sort` | integer | No | - | Sort order for display |
|
||||
|
|
@ -152,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 |
|
||||
|
|
@ -163,28 +162,34 @@ Stores user-visible messages (both user input and assistant responses).
|
|||
| ------------------- | --------------------- | ----- |
|
||||
| `idx_msg_chat_seq` | `chat_id`, `sequence` | index |
|
||||
| `idx_msg_request` | `request_id` | index |
|
||||
| `idx_msg_role` | `role` | index |
|
||||
| `idx_msg_block` | `block_id` | index |
|
||||
| `idx_msg_thread` | `thread_id` | index |
|
||||
| `idx_msg_assistant` | `assistant_id` | index |
|
||||
|
||||
**Message Types (Built-in):**
|
||||
**Message Types:**
|
||||
|
||||
All built-in types defined in `agent/output/BUILTIN_TYPES.md` are stored. See that document for complete Props structures.
|
||||
All message types are stored, including built-in types and custom types. See `agent/output/BUILTIN_TYPES.md` for built-in Props structures.
|
||||
|
||||
| Type | Description | Props Example | Stored? |
|
||||
| ------------ | -------------------------------- | ------------------------------------------------------------------------------------------- | ----------- |
|
||||
| ------------ | -------------------------------- | ------------------------------------------------------------------------------------------- | ------- |
|
||||
| `user_input` | User input (frontend display) | `{"content": "Hello", "role": "user", "name": "John"}` | ✅ Yes |
|
||||
| `text` | Text/Markdown content | `{"content": "Hello **world**!"}` | ✅ Yes |
|
||||
| `thinking` | Reasoning process (o1, DeepSeek) | `{"content": "Let me analyze..."}` | ✅ Yes |
|
||||
| `loading` | Loading/processing indicator | `{"message": "Searching knowledge base..."}` | ✅ Yes |
|
||||
| `tool_call` | LLM tool/function call | `{"id": "call_abc123", "name": "get_weather", "arguments": "{\"location\":\"SF\"}"}` | ✅ Yes |
|
||||
| `retrieval` | KB/Web search results | `{"query": "...", "sources": [...], "total_results": 10}` | ✅ Yes |
|
||||
| `error` | Error message | `{"message": "Connection timeout", "code": "TIMEOUT", "details": "..."}` | ✅ Yes |
|
||||
| `image` | Image content | `{"url": "...", "alt": "...", "width": 200, "height": 200, "detail": "auto"}` | ✅ Yes |
|
||||
| `audio` | Audio content | `{"url": "...", "format": "mp3", "duration": 120.5, "transcript": "...", "controls": true}` | ✅ Yes |
|
||||
| `video` | Video content | `{"url": "...", "format": "mp4", "thumbnail": "...", "width": 640, "height": 360}` | ✅ Yes |
|
||||
| `action` | System action (CUI only) | `{"name": "open_panel", "payload": {"panel_id": "user_profile"}}` | ✅ Yes |
|
||||
| `event` | Lifecycle event (CUI only) | `{"event": "stream_start", "message": "...", "data": {...}}` | ⚠️ Optional |
|
||||
| `event` | Lifecycle event (CUI only) | `{"event": "stream_start", "message": "...", "data": {...}}` | ❌ No |
|
||||
| `*` (custom) | Any custom type | `{"chartType": "bar", "data": [...], "options": {...}}` | ✅ Yes |
|
||||
|
||||
**Note on `event` type:** Lifecycle events (`stream_start`, `stream_end`, etc.) are typically transient and may not need persistent storage. Consider storing only significant events or skipping entirely based on use case.
|
||||
**Note on `event` type:** Lifecycle events (`stream_start`, `stream_end`, etc.) are transient control signals and are NOT stored. They are only used for real-time streaming coordination.
|
||||
|
||||
**Note on custom types:** Any type not in the built-in list is stored as-is with its original `type` and `props` structure.
|
||||
|
||||
**Tool Call Storage:**
|
||||
|
||||
|
|
@ -272,6 +277,157 @@ User input with multimodal content (text + images + files) is stored as `user_in
|
|||
}
|
||||
```
|
||||
|
||||
### Knowledge Base & Web Search Results
|
||||
|
||||
Retrieval results from knowledge bases and web searches need to be stored for:
|
||||
|
||||
1. **User Feedback** - Users can rate (👍/👎) individual sources
|
||||
2. **Quality Analytics** - Track which documents/sources are most useful
|
||||
3. **Source Attribution** - Display citations in the UI
|
||||
4. **RAG Optimization** - Improve retrieval based on feedback
|
||||
|
||||
**Storage Approach:** Store retrieval results as a special message type `retrieval` with structured props.
|
||||
|
||||
**Retrieval Message Structure:**
|
||||
|
||||
```json
|
||||
{
|
||||
"message_id": "msg_retrieval_001",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "retrieval",
|
||||
"props": {
|
||||
"query": "How to configure Yao models?",
|
||||
"sources": [
|
||||
{
|
||||
"id": "src_001",
|
||||
"type": "kb",
|
||||
"collection_id": "col_docs",
|
||||
"document_id": "doc_123",
|
||||
"chunk_id": "chunk_456",
|
||||
"title": "Model Configuration Guide",
|
||||
"content": "To configure a model in Yao, create a .mod.yao file...",
|
||||
"score": 0.92,
|
||||
"metadata": {
|
||||
"file_path": "/docs/model.md",
|
||||
"page": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "src_002",
|
||||
"type": "kb",
|
||||
"collection_id": "col_docs",
|
||||
"document_id": "doc_124",
|
||||
"chunk_id": "chunk_789",
|
||||
"title": "Advanced Model Options",
|
||||
"content": "Models support various options including soft_deletes...",
|
||||
"score": 0.87,
|
||||
"metadata": {
|
||||
"file_path": "/docs/advanced.md",
|
||||
"page": 12
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "src_003",
|
||||
"type": "web",
|
||||
"url": "https://yaoapps.com/docs/models",
|
||||
"title": "Yao Models Documentation",
|
||||
"content": "Official documentation for Yao model system...",
|
||||
"score": 0.85,
|
||||
"metadata": {
|
||||
"domain": "yaoapps.com",
|
||||
"fetched_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"total_results": 15,
|
||||
"query_time_ms": 120
|
||||
},
|
||||
"block_id": "B1",
|
||||
"assistant_id": "docs_assistant",
|
||||
"sequence": 2
|
||||
}
|
||||
```
|
||||
|
||||
**Source Types:**
|
||||
|
||||
| Type | Description | Key Fields |
|
||||
| ------ | ----------------------- | ------------------------------------------ |
|
||||
| `kb` | Knowledge base document | `collection_id`, `document_id`, `chunk_id` |
|
||||
| `web` | Web search result | `url`, `domain` |
|
||||
| `file` | Uploaded file | `file_id`, `file_path` |
|
||||
| `api` | External API result | `api_name`, `endpoint` |
|
||||
| `mcp` | MCP tool result | `server`, `tool` |
|
||||
|
||||
**Source Feedback:**
|
||||
|
||||
User feedback on retrieval sources is handled by the Knowledge Base module. See [KB Feedback](../../kb/README.md) for details.
|
||||
|
||||
**Example: KB Search in Create Hook:**
|
||||
|
||||
```typescript
|
||||
// In Create hook, search knowledge base and store results
|
||||
const results = await ctx.kb.search("col_docs", query, { limit: 5 });
|
||||
|
||||
// Send retrieval message (stored automatically)
|
||||
ctx.Send({
|
||||
type: "retrieval",
|
||||
props: {
|
||||
query: query,
|
||||
sources: results.documents.map((doc, idx) => ({
|
||||
id: `src_${idx}`,
|
||||
type: "kb",
|
||||
collection_id: "col_docs",
|
||||
document_id: doc.document.metadata.document_id,
|
||||
chunk_id: doc.document.id,
|
||||
title: doc.document.metadata.title || "Untitled",
|
||||
content: doc.document.content,
|
||||
score: doc.score,
|
||||
metadata: doc.document.metadata,
|
||||
})),
|
||||
total_results: results.total,
|
||||
query_time_ms: results.query_time_ms,
|
||||
},
|
||||
});
|
||||
|
||||
// Also send loading message for user feedback
|
||||
ctx.Send({
|
||||
type: "loading",
|
||||
props: { message: `Found ${results.total} relevant documents...` },
|
||||
});
|
||||
```
|
||||
|
||||
**Example: Web Search Results:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "retrieval",
|
||||
"props": {
|
||||
"query": "latest AI news 2024",
|
||||
"sources": [
|
||||
{
|
||||
"id": "src_001",
|
||||
"type": "web",
|
||||
"url": "https://example.com/ai-news",
|
||||
"title": "AI Breakthroughs in 2024",
|
||||
"content": "Summary of the article...",
|
||||
"score": 0.95,
|
||||
"metadata": {
|
||||
"domain": "example.com",
|
||||
"published_at": "2024-01-10",
|
||||
"fetched_at": "2024-01-15T10:30:00Z",
|
||||
"snippet": "The year 2024 has seen remarkable..."
|
||||
}
|
||||
}
|
||||
],
|
||||
"provider": "tavily",
|
||||
"total_results": 10,
|
||||
"query_time_ms": 850
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Resume Table
|
||||
|
||||
Stores execution state for resume/retry functionality. **Only written when request is interrupted or failed.**
|
||||
|
|
@ -279,7 +435,7 @@ 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 |
|
||||
|
|
@ -294,7 +450,7 @@ Stores execution state for resume/retry functionality. **Only written when reque
|
|||
| `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 |
|
||||
| `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 |
|
||||
|
|
@ -344,6 +500,7 @@ If interrupted during delegate, the `space_snapshot` allows restoring `ctx.Space
|
|||
| ---------------------- | ------------------------ | ----- |
|
||||
| `idx_resume_chat` | `chat_id` | index |
|
||||
| `idx_resume_request` | `request_id`, `sequence` | index |
|
||||
| `idx_resume_type` | `type` | index |
|
||||
| `idx_resume_status` | `status` | index |
|
||||
| `idx_resume_stack` | `stack_id` | index |
|
||||
| `idx_resume_parent` | `stack_parent_id` | index |
|
||||
|
|
@ -351,20 +508,16 @@ If interrupted during delegate, the `space_snapshot` allows restoring `ctx.Space
|
|||
|
||||
## Write Strategy
|
||||
|
||||
### Two-Write Strategy
|
||||
### Single-Write Strategy
|
||||
|
||||
All data is buffered in memory during execution and written to database only **twice**:
|
||||
|
||||
1. **Write 1 (Entry)**: When `Stream()` starts - save user input message
|
||||
2. **Write 2 (Exit)**: When `Stream()` exits - batch save messages (and steps only on error/interrupt)
|
||||
All data is buffered in memory during execution and written to database **only once** when `Stream()` exits:
|
||||
|
||||
**Note**: Request tracking (status, tokens, duration) is handled by [OpenAPI Request Middleware](../../openapi/request/REQUEST_DESIGN.md).
|
||||
|
||||
```
|
||||
Stream() Entry
|
||||
│
|
||||
├── 【Write 1】Save user input
|
||||
│ - User message (role=user)
|
||||
├── Buffer user input message (role=user)
|
||||
│
|
||||
├── Execution (all in memory)
|
||||
│ - ctx.Send() → messageBuffer
|
||||
|
|
@ -372,10 +525,10 @@ Stream() Entry
|
|||
│ - ctx.Replace() → update messageBuffer
|
||||
│ - Each step → stepBuffer
|
||||
│
|
||||
└── 【Write 2】Save final state (via defer)
|
||||
└── 【Single Write】Save final state (via defer)
|
||||
│
|
||||
├── Always:
|
||||
│ - Batch write all assistant messages
|
||||
│ - Batch write all messages (user input + assistant responses)
|
||||
│ - Update token usage in openapi_request (via request_id)
|
||||
│
|
||||
└── Only on error/interrupt:
|
||||
|
|
@ -385,58 +538,58 @@ Stream() Entry
|
|||
### Write Points
|
||||
|
||||
| Event | Message Table | Step Table | Token Usage |
|
||||
| ---------------- | -------------------- | ----------------------------------- | ----------- |
|
||||
| Stream entry | Write 1 (user input) | - | - |
|
||||
| ---------------- | -------------------------------------- | ----------------------------------- | ----------- |
|
||||
| Stream entry | Buffer user input | - | - |
|
||||
| During execution | Buffer in memory | Buffer in memory | - |
|
||||
| **Completed** | **Batch write all** | **❌ Skip (no need to resume)** | ✅ Update |
|
||||
| On interrupt | Batch write buffered | ✅ Batch write (status=interrupted) | ✅ Update |
|
||||
| On error | Batch write buffered | ✅ Batch write (status=failed) | ✅ Update |
|
||||
| **Completed** | **Batch write all (user + assistant)** | **❌ Skip (no need to resume)** | ✅ Update |
|
||||
| On interrupt | Batch write all buffered | ✅ Batch write (status=interrupted) | ✅ Update |
|
||||
| On error | Batch write all buffered | ✅ Batch write (status=failed) | ✅ Update |
|
||||
|
||||
**Why skip Steps on success?**
|
||||
|
||||
- Steps are only needed for resume/retry operations
|
||||
- If completed successfully, there's nothing to resume
|
||||
- Reduces database writes and keeps Step table clean
|
||||
- Reduces database writes and keeps Resume table clean
|
||||
|
||||
### Why Two Writes?
|
||||
### Why Single Write?
|
||||
|
||||
| Scenario | What Happens | Data Safe? |
|
||||
| ------------------ | ----------------------------------- | ---------- |
|
||||
| Normal completion | `defer` triggers → Write 2 executes | ✅ |
|
||||
| User clicks stop | `defer` triggers → Write 2 executes | ✅ |
|
||||
| LLM timeout | `defer` triggers → Write 2 executes | ✅ |
|
||||
| Tool failure | `defer` triggers → Write 2 executes | ✅ |
|
||||
| Network disconnect | `defer` triggers → Write 2 executes | ✅ |
|
||||
| ------------------ | --------------------------------- | ---------- |
|
||||
| Normal completion | `defer` triggers → Write executes | ✅ |
|
||||
| User clicks stop | `defer` triggers → Write executes | ✅ |
|
||||
| LLM timeout | `defer` triggers → Write executes | ✅ |
|
||||
| Tool failure | `defer` triggers → Write executes | ✅ |
|
||||
| Network disconnect | `defer` triggers → Write executes | ✅ |
|
||||
| Process crash | Service is down, user must retry | N/A |
|
||||
|
||||
**Note**: Process crash is a catastrophic failure handled at infrastructure level, not application level.
|
||||
|
||||
### Write Count Comparison
|
||||
|
||||
For a typical request: user input → hook_create → llm → tool → llm → hook_next → 5 messages
|
||||
For a typical request: user input → hook_create → llm → tool → hook_next → 5 messages
|
||||
|
||||
| Strategy | Database Writes | Notes |
|
||||
| ---------------------- | --------------- | ------------------ |
|
||||
| ------------------------- | --------------- | --------------------- |
|
||||
| Write per operation | 1 + 5 + 5 = 11 | One write per step |
|
||||
| **Two-write strategy** | **2** | Entry + Exit only |
|
||||
| **Single-write strategy** | **1** | Exit only (via defer) |
|
||||
|
||||
### Implementation
|
||||
|
||||
````go
|
||||
func (ast *Assistant) Stream(ctx, inputMessages, options) {
|
||||
// ========== Write 1: Entry ==========
|
||||
userMsg := createUserMessage(ctx, inputMessages)
|
||||
chatStore.SaveMessages(ctx.ChatID, []*Message{userMsg})
|
||||
|
||||
// ========== Memory Buffers ==========
|
||||
messageBuffer := NewMessageBuffer()
|
||||
stepBuffer := NewStepBuffer()
|
||||
|
||||
// Buffer user input message (not written yet)
|
||||
userMsg := createUserMessage(ctx, inputMessages)
|
||||
messageBuffer.Add(userMsg)
|
||||
|
||||
// Track current step for error handling
|
||||
var currentStep *Step
|
||||
|
||||
defer func() {
|
||||
// ========== Write 2: Exit (always executes) ==========
|
||||
// ========== Single Write: Exit (always executes) ==========
|
||||
// Determine final status for incomplete steps
|
||||
finalStatus := "completed"
|
||||
if ctx.IsInterrupted() {
|
||||
|
|
@ -451,9 +604,13 @@ func (ast *Assistant) Stream(ctx, inputMessages, options) {
|
|||
currentStep.Status = finalStatus
|
||||
}
|
||||
|
||||
// Batch write all buffered data
|
||||
// Batch write all buffered messages (user input + assistant responses)
|
||||
chatStore.SaveMessages(ctx.ChatID, messageBuffer.GetAll())
|
||||
chatStore.SaveSteps(stepBuffer.GetAll())
|
||||
|
||||
// Only save steps on error/interrupt (not on success)
|
||||
if finalStatus != "completed" {
|
||||
chatStore.SaveResume(stepBuffer.GetAll())
|
||||
}
|
||||
|
||||
// Update token usage in OpenAPI request record
|
||||
if ctx.RequestID != "" && completionResponse != nil {
|
||||
|
|
@ -580,7 +737,6 @@ type Chat struct {
|
|||
AssistantID string `json:"assistant_id"`
|
||||
Mode string `json:"mode"`
|
||||
Status string `json:"status"`
|
||||
Preset bool `json:"preset"`
|
||||
Public bool `json:"public"`
|
||||
Share string `json:"share"` // "private" or "team"
|
||||
Sort int `json:"sort"`
|
||||
|
|
@ -639,6 +795,20 @@ type ChatFilter struct {
|
|||
AssistantID string `json:"assistant_id,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Keywords string `json:"keywords,omitempty"`
|
||||
|
||||
// Time range filter
|
||||
StartTime *time.Time `json:"start_time,omitempty"` // Filter chats after this time
|
||||
EndTime *time.Time `json:"end_time,omitempty"` // Filter chats before this time
|
||||
TimeField string `json:"time_field,omitempty"` // Field for time filter: "created_at" or "last_message_at" (default)
|
||||
|
||||
// Sorting
|
||||
OrderBy string `json:"order_by,omitempty"` // Field to sort by (default: "last_message_at")
|
||||
Order string `json:"order,omitempty"` // Sort order: "desc" (default) or "asc"
|
||||
|
||||
// Response format
|
||||
GroupBy string `json:"group_by,omitempty"` // "time" for time-based groups, empty for flat list
|
||||
|
||||
// Pagination
|
||||
Page int `json:"page,omitempty"`
|
||||
PageSize int `json:"pagesize,omitempty"`
|
||||
}
|
||||
|
|
@ -648,18 +818,29 @@ type MessageFilter struct {
|
|||
RequestID string `json:"request_id,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
BlockID string `json:"block_id,omitempty"`
|
||||
ThreadID string `json:"thread_id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
}
|
||||
|
||||
// ChatList paginated response
|
||||
// ChatList paginated response with time-based grouping
|
||||
type ChatList struct {
|
||||
Data []*Chat `json:"data"`
|
||||
Groups []*ChatGroup `json:"groups,omitempty"` // Time-based groups for UI display
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pagesize"`
|
||||
PageCount int `json:"pagecount"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// ChatGroup represents a time-based group of chats
|
||||
type ChatGroup struct {
|
||||
Label string `json:"label"` // "Today", "Yesterday", "This Week", "This Month", "Earlier"
|
||||
Key string `json:"key"` // "today", "yesterday", "this_week", "this_month", "earlier"
|
||||
Chats []*Chat `json:"chats"` // Chats in this group
|
||||
Count int `json:"count"` // Number of chats in group
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
|
@ -671,13 +852,12 @@ A typical conversation with various message types stored in `agent_message`:
|
|||
```
|
||||
User: "What's the weather in SF? Also show me a chart."
|
||||
|
||||
Timeline:
|
||||
1. User sends multimodal input
|
||||
2. Hook shows loading state
|
||||
Timeline (user input → hook_create → llm → tool → hook_next):
|
||||
1. User sends input
|
||||
2. Create hook shows loading state
|
||||
3. LLM thinks and calls tool
|
||||
4. Tool returns result
|
||||
5. LLM generates text response
|
||||
6. Hook sends image chart
|
||||
4. Tool executes and returns result
|
||||
5. Next hook generates text response and image chart
|
||||
```
|
||||
|
||||
**Stored Messages:**
|
||||
|
|
@ -745,7 +925,7 @@ Timeline:
|
|||
"sequence": 4
|
||||
},
|
||||
|
||||
// 5. Tool result (role=assistant, type=text, with tool metadata)
|
||||
// 5. Tool result from Next hook (role=assistant, type=text, with tool metadata)
|
||||
{
|
||||
"message_id": "msg_005",
|
||||
"chat_id": "chat_123",
|
||||
|
|
@ -753,39 +933,23 @@ Timeline:
|
|||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": {
|
||||
"content": "Weather data retrieved: 18°C, sunny, humidity 65%"
|
||||
"content": "The weather in San Francisco is currently **18°C** and sunny with 65% humidity. Perfect weather for outdoor activities!"
|
||||
},
|
||||
"block_id": "B2",
|
||||
"block_id": "B3",
|
||||
"metadata": {
|
||||
"tool_call_id": "call_weather_001",
|
||||
"tool_name": "get_weather",
|
||||
"is_tool_result": true
|
||||
"tool_name": "get_weather"
|
||||
},
|
||||
"assistant_id": "weather_assistant",
|
||||
"sequence": 5
|
||||
},
|
||||
|
||||
// 6. LLM text response (role=assistant, type=text)
|
||||
// 6. Chart image from Next hook (role=assistant, type=image)
|
||||
{
|
||||
"message_id": "msg_006",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": {
|
||||
"content": "The weather in San Francisco is currently **18°C** and sunny with 65% humidity. Perfect weather for outdoor activities!"
|
||||
},
|
||||
"block_id": "B2",
|
||||
"assistant_id": "weather_assistant",
|
||||
"sequence": 6
|
||||
},
|
||||
|
||||
// 7. Chart image from Next hook (role=assistant, type=image)
|
||||
{
|
||||
"message_id": "msg_007",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "image",
|
||||
"props": {
|
||||
"url": "https://charts.example.com/weather_sf.png",
|
||||
|
|
@ -795,7 +959,7 @@ Timeline:
|
|||
},
|
||||
"block_id": "B3",
|
||||
"assistant_id": "weather_assistant",
|
||||
"sequence": 7
|
||||
"sequence": 6
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
@ -904,13 +1068,54 @@ Multimedia content storage:
|
|||
### 5. Load Chat History
|
||||
|
||||
```go
|
||||
// Get chat list
|
||||
// Example 1: Flat list (default)
|
||||
chats, _ := chatStore.ListChats(ChatFilter{
|
||||
UserID: "user123",
|
||||
Status: "active",
|
||||
OrderBy: "last_message_at",
|
||||
Order: "desc",
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
})
|
||||
// Response: chats.Data = [...], chats.Groups = nil
|
||||
|
||||
// Example 2: Grouped by time
|
||||
chats, _ := chatStore.ListChats(ChatFilter{
|
||||
UserID: "user123",
|
||||
GroupBy: "time", // Enable time-based grouping
|
||||
OrderBy: "last_message_at",
|
||||
Order: "desc",
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
})
|
||||
// Response includes time-based groups:
|
||||
// chats.Groups = [
|
||||
// { Key: "today", Label: "Today", Chats: [...], Count: 3 },
|
||||
// { Key: "yesterday", Label: "Yesterday", Chats: [...], Count: 5 },
|
||||
// { Key: "this_week", Label: "This Week", Chats: [...], Count: 8 },
|
||||
// { Key: "this_month", Label: "This Month", Chats: [...], Count: 4 },
|
||||
// { Key: "earlier", Label: "Earlier", Chats: [...], Count: 0 },
|
||||
// ]
|
||||
|
||||
// Example 3: Filter by time range
|
||||
startTime := time.Now().AddDate(0, 0, -7) // Last 7 days
|
||||
chats, _ := chatStore.ListChats(ChatFilter{
|
||||
UserID: "user123",
|
||||
StartTime: &startTime,
|
||||
TimeField: "last_message_at", // Filter by last message time
|
||||
OrderBy: "last_message_at",
|
||||
Order: "desc",
|
||||
})
|
||||
|
||||
// Example 4: Filter specific date range
|
||||
start := time.Date(2024, 12, 1, 0, 0, 0, 0, time.Local)
|
||||
end := time.Date(2024, 12, 31, 23, 59, 59, 0, time.Local)
|
||||
chats, _ := chatStore.ListChats(ChatFilter{
|
||||
UserID: "user123",
|
||||
StartTime: &start,
|
||||
EndTime: &end,
|
||||
TimeField: "created_at", // Filter by creation time
|
||||
})
|
||||
|
||||
// Get messages for a chat
|
||||
messages, _ := chatStore.GetMessages("chat_123", MessageFilter{
|
||||
|
|
@ -1054,6 +1259,99 @@ return {
|
|||
// 2. The delegated agent's Create hook can read: ctx.space.GetDel("choose_prompt")
|
||||
```
|
||||
|
||||
## Concurrent Operations Storage
|
||||
|
||||
When an Agent makes parallel calls (e.g., multiple MCP tools, multiple sub-agents), messages use `block_id` and `thread_id` for grouping:
|
||||
|
||||
```
|
||||
Main Agent concurrently calls 3 tasks:
|
||||
├── Thread T1: Weather query (MCP)
|
||||
├── Thread T2: News search (MCP)
|
||||
├── Thread T3: Stock query (MCP)
|
||||
└── Wait for all to complete, then summarize
|
||||
```
|
||||
|
||||
**Stored Messages:**
|
||||
|
||||
```json
|
||||
[
|
||||
// All concurrent messages share the same block_id, different thread_id
|
||||
// Messages may arrive in any order due to concurrency
|
||||
|
||||
// Thread T1: Weather result
|
||||
{
|
||||
"message_id": "msg_t1_001",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": { "content": "Weather in SF: 18°C, sunny" },
|
||||
"block_id": "B1",
|
||||
"thread_id": "T1",
|
||||
"assistant_id": "main_assistant",
|
||||
"sequence": 2
|
||||
},
|
||||
|
||||
// Thread T2: News result
|
||||
{
|
||||
"message_id": "msg_t2_001",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": { "content": "Top news: AI breakthrough announced..." },
|
||||
"block_id": "B1",
|
||||
"thread_id": "T2",
|
||||
"assistant_id": "main_assistant",
|
||||
"sequence": 3
|
||||
},
|
||||
|
||||
// Thread T3: Stock result
|
||||
{
|
||||
"message_id": "msg_t3_001",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": { "content": "AAPL: $185.50 (+1.2%)" },
|
||||
"block_id": "B1",
|
||||
"thread_id": "T3",
|
||||
"assistant_id": "main_assistant",
|
||||
"sequence": 4
|
||||
},
|
||||
|
||||
// After all threads complete, main agent summarizes (new block)
|
||||
{
|
||||
"message_id": "msg_summary",
|
||||
"chat_id": "chat_123",
|
||||
"request_id": "req_abc",
|
||||
"role": "assistant",
|
||||
"type": "text",
|
||||
"props": {
|
||||
"content": "Here's your daily briefing: The weather is great at 18°C..."
|
||||
},
|
||||
"block_id": "B2",
|
||||
"thread_id": null,
|
||||
"assistant_id": "main_assistant",
|
||||
"sequence": 5
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
|
||||
| Field | Concurrent Usage |
|
||||
| ----------- | -------------------------------------------------- |
|
||||
| `block_id` | Same for all parallel operations (B1) |
|
||||
| `thread_id` | Different for each concurrent task (T1, T2, T3) |
|
||||
| `sequence` | Reflects actual arrival order (may be interleaved) |
|
||||
|
||||
**Frontend Rendering:**
|
||||
|
||||
- Group messages by `block_id` for visual blocks
|
||||
- Within a block, optionally group by `thread_id` to show parallel results
|
||||
- Use `sequence` for chronological display
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [OpenAPI Request Design](../../openapi/request/REQUEST_DESIGN.md) - Global request tracking, billing, rate limiting
|
||||
|
|
|
|||
325
data/bindata.go
325
data/bindata.go
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
134
yao/models/agent/message.mod.yao
Normal file
134
yao/models/agent/message.mod.yao
Normal 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 }
|
||||
}
|
||||
|
||||
170
yao/models/agent/resume.mod.yao
Normal file
170
yao/models/agent/resume.mod.yao
Normal 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 }
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue