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
|
|
@ -8,17 +8,18 @@ Defined in `types.go`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
const (
|
const (
|
||||||
TypeUserInput = "user_input" // User input message (frontend display only)
|
TypeUserInput = "user_input" // User input message (frontend display only)
|
||||||
TypeText = "text" // Plain text or Markdown content
|
TypeText = "text" // Plain text or Markdown content
|
||||||
TypeThinking = "thinking" // Reasoning/thinking process
|
TypeThinking = "thinking" // Reasoning/thinking process
|
||||||
TypeLoading = "loading" // Loading/processing indicator
|
TypeLoading = "loading" // Loading/processing indicator
|
||||||
TypeToolCall = "tool_call" // LLM tool/function call
|
TypeToolCall = "tool_call" // LLM tool/function call
|
||||||
TypeError = "error" // Error message
|
TypeRetrieval = "retrieval" // KB/Web search results (for feedback & analytics)
|
||||||
TypeImage = "image" // Image content
|
TypeError = "error" // Error message
|
||||||
TypeAudio = "audio" // Audio content
|
TypeImage = "image" // Image content
|
||||||
TypeVideo = "video" // Video content
|
TypeAudio = "audio" // Audio content
|
||||||
TypeAction = "action" // System action (silent in standard clients)
|
TypeVideo = "video" // Video content
|
||||||
TypeEvent = "event" // Lifecycle event (silent in standard clients)
|
TypeAction = "action" // System action (silent in standard clients)
|
||||||
|
TypeEvent = "event" // Lifecycle event (silent in standard clients)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -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
|
**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)
|
**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.)
|
**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
|
**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
|
**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
|
**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) |
|
| `thinking` | `delta.reasoning_content` | `props.content` | Reasoning content (o1 models) |
|
||||||
| `loading` | `delta.reasoning_content` | `props.message` | Shows as thinking in OpenAI clients |
|
| `loading` | `delta.reasoning_content` | `props.message` | Shows as thinking in OpenAI clients |
|
||||||
| `tool_call` | `delta.tool_calls` | `props.{id, name, arguments}` | |
|
| `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}` | |
|
| `error` | `error` | `props.{message, code}` | |
|
||||||
| `image` | `delta.content` | `props.{url, alt}` | Markdown: `` - displays inline |
|
| `image` | `delta.content` | `props.{url, alt}` | Markdown: `` - displays inline |
|
||||||
| `audio` | `delta.content` | `props.url` | Markdown link (can't display 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 |
|
| `assistant_id` | string(200) | No | Yes | Associated assistant ID |
|
||||||
| `mode` | string(50) | No | - | Chat mode (default: "chat") |
|
| `mode` | string(50) | No | - | Chat mode (default: "chat") |
|
||||||
| `status` | enum | No | Yes | Status: `active`, `archived` |
|
| `status` | enum | No | Yes | Status: `active`, `archived` |
|
||||||
| `preset` | boolean | No | - | Whether this is a preset chat |
|
|
||||||
| `public` | boolean | No | - | Whether shared across all teams |
|
| `public` | boolean | No | - | Whether shared across all teams |
|
||||||
| `share` | enum | No | Yes | Sharing scope: `private`, `team` |
|
| `share` | enum | No | Yes | Sharing scope: `private`, `team` |
|
||||||
| `sort` | integer | No | - | Sort order for display |
|
| `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 |
|
| `block_id` | string(64) | Yes | Yes | Block grouping ID |
|
||||||
| `thread_id` | string(64) | Yes | Yes | Thread grouping ID |
|
| `thread_id` | string(64) | Yes | Yes | Thread grouping ID |
|
||||||
| `assistant_id` | string(200) | Yes | Yes | Assistant ID (join to get name/avatar) |
|
| `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 |
|
| `metadata` | json | Yes | - | Additional metadata |
|
||||||
| `created_at` | timestamp | No | Yes | Creation timestamp |
|
| `created_at` | timestamp | No | Yes | Creation timestamp |
|
||||||
| `updated_at` | timestamp | No | - | Last update 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_chat_seq` | `chat_id`, `sequence` | index |
|
||||||
| `idx_msg_request` | `request_id` | index |
|
| `idx_msg_request` | `request_id` | index |
|
||||||
|
| `idx_msg_role` | `role` | index |
|
||||||
| `idx_msg_block` | `block_id` | index |
|
| `idx_msg_block` | `block_id` | index |
|
||||||
|
| `idx_msg_thread` | `thread_id` | index |
|
||||||
| `idx_msg_assistant` | `assistant_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? |
|
| Type | Description | Props Example | Stored? |
|
||||||
| ------------ | -------------------------------- | ------------------------------------------------------------------------------------------- | ----------- |
|
| ------------ | -------------------------------- | ------------------------------------------------------------------------------------------- | ------- |
|
||||||
| `user_input` | User input (frontend display) | `{"content": "Hello", "role": "user", "name": "John"}` | ✅ Yes |
|
| `user_input` | User input (frontend display) | `{"content": "Hello", "role": "user", "name": "John"}` | ✅ Yes |
|
||||||
| `text` | Text/Markdown content | `{"content": "Hello **world**!"}` | ✅ Yes |
|
| `text` | Text/Markdown content | `{"content": "Hello **world**!"}` | ✅ Yes |
|
||||||
| `thinking` | Reasoning process (o1, DeepSeek) | `{"content": "Let me analyze..."}` | ✅ Yes |
|
| `thinking` | Reasoning process (o1, DeepSeek) | `{"content": "Let me analyze..."}` | ✅ Yes |
|
||||||
| `loading` | Loading/processing indicator | `{"message": "Searching knowledge base..."}` | ✅ 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 |
|
| `tool_call` | LLM tool/function call | `{"id": "call_abc123", "name": "get_weather", "arguments": "{\"location\":\"SF\"}"}` | ✅ Yes |
|
||||||
| `error` | Error message | `{"message": "Connection timeout", "code": "TIMEOUT", "details": "..."}` | ✅ Yes |
|
| `retrieval` | KB/Web search results | `{"query": "...", "sources": [...], "total_results": 10}` | ✅ Yes |
|
||||||
| `image` | Image content | `{"url": "...", "alt": "...", "width": 200, "height": 200, "detail": "auto"}` | ✅ Yes |
|
| `error` | Error message | `{"message": "Connection timeout", "code": "TIMEOUT", "details": "..."}` | ✅ Yes |
|
||||||
| `audio` | Audio content | `{"url": "...", "format": "mp3", "duration": 120.5, "transcript": "...", "controls": true}` | ✅ Yes |
|
| `image` | Image content | `{"url": "...", "alt": "...", "width": 200, "height": 200, "detail": "auto"}` | ✅ Yes |
|
||||||
| `video` | Video content | `{"url": "...", "format": "mp4", "thumbnail": "...", "width": 640, "height": 360}` | ✅ Yes |
|
| `audio` | Audio content | `{"url": "...", "format": "mp3", "duration": 120.5, "transcript": "...", "controls": true}` | ✅ Yes |
|
||||||
| `action` | System action (CUI only) | `{"name": "open_panel", "payload": {"panel_id": "user_profile"}}` | ✅ Yes |
|
| `video` | Video content | `{"url": "...", "format": "mp4", "thumbnail": "...", "width": 640, "height": 360}` | ✅ Yes |
|
||||||
| `event` | Lifecycle event (CUI only) | `{"event": "stream_start", "message": "...", "data": {...}}` | ⚠️ Optional |
|
| `action` | System action (CUI only) | `{"name": "open_panel", "payload": {"panel_id": "user_profile"}}` | ✅ Yes |
|
||||||
|
| `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:**
|
**Tool Call Storage:**
|
||||||
|
|
||||||
|
|
@ -272,32 +277,183 @@ 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
|
### 3. Resume Table
|
||||||
|
|
||||||
Stores execution state for resume/retry functionality. **Only written when request is interrupted or failed.**
|
Stores execution state for resume/retry functionality. **Only written when request is interrupted or failed.**
|
||||||
|
|
||||||
**Table Name:** `agent_resume`
|
**Table Name:** `agent_resume`
|
||||||
|
|
||||||
| Column | Type | Nullable | Index | Description |
|
| Column | Type | Nullable | Index | Description |
|
||||||
| ----------------- | ----------- | -------- | ------ | -------------------------------- |
|
| ----------------- | ----------- | -------- | ------ | ---------------------------------------- |
|
||||||
| `id` | ID | No | PK | Auto-increment primary key |
|
| `id` | ID | No | PK | Auto-increment primary key |
|
||||||
| `resume_id` | string(64) | No | Unique | Unique resume record identifier |
|
| `resume_id` | string(64) | No | Unique | Unique resume record identifier |
|
||||||
| `chat_id` | string(64) | No | Yes | Parent chat ID |
|
| `chat_id` | string(64) | No | Yes | Parent chat ID |
|
||||||
| `request_id` | string(64) | No | Yes | Request ID |
|
| `request_id` | string(64) | No | Yes | Request ID |
|
||||||
| `assistant_id` | string(200) | No | Yes | Assistant executing this step |
|
| `assistant_id` | string(200) | No | Yes | Assistant executing this step |
|
||||||
| `stack_id` | string(64) | No | Yes | Stack node ID for this execution |
|
| `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_parent_id` | string(64) | Yes | Yes | Parent stack ID (for A2A calls) |
|
||||||
| `stack_depth` | integer | No | - | Call depth (0=root, 1+=nested) |
|
| `stack_depth` | integer | No | - | Call depth (0=root, 1+=nested) |
|
||||||
| `type` | enum | No | Yes | Step type |
|
| `type` | enum | No | Yes | Step type |
|
||||||
| `status` | enum | No | Yes | Status: `interrupted`, `failed` |
|
| `status` | enum | No | Yes | Status: `interrupted`, `failed` |
|
||||||
| `input` | json | Yes | - | Step input data |
|
| `input` | json | Yes | - | Step input data |
|
||||||
| `output` | json | Yes | - | Step output data (partial) |
|
| `output` | json | Yes | - | Step output data (partial) |
|
||||||
| `space_snapshot` | json | Yes | - | Space data snapshot for recovery |
|
| `space_snapshot` | json | Yes | - | Space data snapshot for recovery |
|
||||||
| `error` | text | Yes | - | Error message if failed |
|
| `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 |
|
| `metadata` | json | Yes | - | Additional metadata |
|
||||||
| `created_at` | timestamp | No | Yes | Creation timestamp |
|
| `created_at` | timestamp | No | Yes | Creation timestamp |
|
||||||
| `updated_at` | timestamp | No | - | Last update timestamp |
|
| `updated_at` | timestamp | No | - | Last update timestamp |
|
||||||
|
|
||||||
**Space Snapshot:**
|
**Space Snapshot:**
|
||||||
|
|
||||||
|
|
@ -344,6 +500,7 @@ If interrupted during delegate, the `space_snapshot` allows restoring `ctx.Space
|
||||||
| ---------------------- | ------------------------ | ----- |
|
| ---------------------- | ------------------------ | ----- |
|
||||||
| `idx_resume_chat` | `chat_id` | index |
|
| `idx_resume_chat` | `chat_id` | index |
|
||||||
| `idx_resume_request` | `request_id`, `sequence` | index |
|
| `idx_resume_request` | `request_id`, `sequence` | index |
|
||||||
|
| `idx_resume_type` | `type` | index |
|
||||||
| `idx_resume_status` | `status` | index |
|
| `idx_resume_status` | `status` | index |
|
||||||
| `idx_resume_stack` | `stack_id` | index |
|
| `idx_resume_stack` | `stack_id` | index |
|
||||||
| `idx_resume_parent` | `stack_parent_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
|
## Write Strategy
|
||||||
|
|
||||||
### Two-Write Strategy
|
### Single-Write Strategy
|
||||||
|
|
||||||
All data is buffered in memory during execution and written to database only **twice**:
|
All data is buffered in memory during execution and written to database **only once** when `Stream()` exits:
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
**Note**: Request tracking (status, tokens, duration) is handled by [OpenAPI Request Middleware](../../openapi/request/REQUEST_DESIGN.md).
|
**Note**: Request tracking (status, tokens, duration) is handled by [OpenAPI Request Middleware](../../openapi/request/REQUEST_DESIGN.md).
|
||||||
|
|
||||||
```
|
```
|
||||||
Stream() Entry
|
Stream() Entry
|
||||||
│
|
│
|
||||||
├── 【Write 1】Save user input
|
├── Buffer user input message (role=user)
|
||||||
│ - User message (role=user)
|
|
||||||
│
|
│
|
||||||
├── Execution (all in memory)
|
├── Execution (all in memory)
|
||||||
│ - ctx.Send() → messageBuffer
|
│ - ctx.Send() → messageBuffer
|
||||||
|
|
@ -372,10 +525,10 @@ Stream() Entry
|
||||||
│ - ctx.Replace() → update messageBuffer
|
│ - ctx.Replace() → update messageBuffer
|
||||||
│ - Each step → stepBuffer
|
│ - Each step → stepBuffer
|
||||||
│
|
│
|
||||||
└── 【Write 2】Save final state (via defer)
|
└── 【Single Write】Save final state (via defer)
|
||||||
│
|
│
|
||||||
├── Always:
|
├── Always:
|
||||||
│ - Batch write all assistant messages
|
│ - Batch write all messages (user input + assistant responses)
|
||||||
│ - Update token usage in openapi_request (via request_id)
|
│ - Update token usage in openapi_request (via request_id)
|
||||||
│
|
│
|
||||||
└── Only on error/interrupt:
|
└── Only on error/interrupt:
|
||||||
|
|
@ -384,59 +537,59 @@ Stream() Entry
|
||||||
|
|
||||||
### Write Points
|
### Write Points
|
||||||
|
|
||||||
| Event | Message Table | Step Table | Token Usage |
|
| 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 | - |
|
| During execution | Buffer in memory | Buffer in memory | - |
|
||||||
| **Completed** | **Batch write all** | **❌ Skip (no need to resume)** | ✅ Update |
|
| **Completed** | **Batch write all (user + assistant)** | **❌ Skip (no need to resume)** | ✅ Update |
|
||||||
| On interrupt | Batch write buffered | ✅ Batch write (status=interrupted) | ✅ Update |
|
| On interrupt | Batch write all buffered | ✅ Batch write (status=interrupted) | ✅ Update |
|
||||||
| On error | Batch write buffered | ✅ Batch write (status=failed) | ✅ Update |
|
| On error | Batch write all buffered | ✅ Batch write (status=failed) | ✅ Update |
|
||||||
|
|
||||||
**Why skip Steps on success?**
|
**Why skip Steps on success?**
|
||||||
|
|
||||||
- Steps are only needed for resume/retry operations
|
- Steps are only needed for resume/retry operations
|
||||||
- If completed successfully, there's nothing to resume
|
- 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? |
|
| Scenario | What Happens | Data Safe? |
|
||||||
| ------------------ | ----------------------------------- | ---------- |
|
| ------------------ | --------------------------------- | ---------- |
|
||||||
| Normal completion | `defer` triggers → Write 2 executes | ✅ |
|
| Normal completion | `defer` triggers → Write executes | ✅ |
|
||||||
| User clicks stop | `defer` triggers → Write 2 executes | ✅ |
|
| User clicks stop | `defer` triggers → Write executes | ✅ |
|
||||||
| LLM timeout | `defer` triggers → Write 2 executes | ✅ |
|
| LLM timeout | `defer` triggers → Write executes | ✅ |
|
||||||
| Tool failure | `defer` triggers → Write 2 executes | ✅ |
|
| Tool failure | `defer` triggers → Write executes | ✅ |
|
||||||
| Network disconnect | `defer` triggers → Write 2 executes | ✅ |
|
| Network disconnect | `defer` triggers → Write executes | ✅ |
|
||||||
| Process crash | Service is down, user must retry | N/A |
|
| Process crash | Service is down, user must retry | N/A |
|
||||||
|
|
||||||
**Note**: Process crash is a catastrophic failure handled at infrastructure level, not application level.
|
**Note**: Process crash is a catastrophic failure handled at infrastructure level, not application level.
|
||||||
|
|
||||||
### Write Count Comparison
|
### 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 |
|
| Strategy | Database Writes | Notes |
|
||||||
| ---------------------- | --------------- | ------------------ |
|
| ------------------------- | --------------- | --------------------- |
|
||||||
| Write per operation | 1 + 5 + 5 = 11 | One write per step |
|
| 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
|
### Implementation
|
||||||
|
|
||||||
````go
|
````go
|
||||||
func (ast *Assistant) Stream(ctx, inputMessages, options) {
|
func (ast *Assistant) Stream(ctx, inputMessages, options) {
|
||||||
// ========== Write 1: Entry ==========
|
|
||||||
userMsg := createUserMessage(ctx, inputMessages)
|
|
||||||
chatStore.SaveMessages(ctx.ChatID, []*Message{userMsg})
|
|
||||||
|
|
||||||
// ========== Memory Buffers ==========
|
// ========== Memory Buffers ==========
|
||||||
messageBuffer := NewMessageBuffer()
|
messageBuffer := NewMessageBuffer()
|
||||||
stepBuffer := NewStepBuffer()
|
stepBuffer := NewStepBuffer()
|
||||||
|
|
||||||
|
// Buffer user input message (not written yet)
|
||||||
|
userMsg := createUserMessage(ctx, inputMessages)
|
||||||
|
messageBuffer.Add(userMsg)
|
||||||
|
|
||||||
// Track current step for error handling
|
// Track current step for error handling
|
||||||
var currentStep *Step
|
var currentStep *Step
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// ========== Write 2: Exit (always executes) ==========
|
// ========== Single Write: Exit (always executes) ==========
|
||||||
// Determine final status for incomplete steps
|
// Determine final status for incomplete steps
|
||||||
finalStatus := "completed"
|
finalStatus := "completed"
|
||||||
if ctx.IsInterrupted() {
|
if ctx.IsInterrupted() {
|
||||||
|
|
@ -451,9 +604,13 @@ func (ast *Assistant) Stream(ctx, inputMessages, options) {
|
||||||
currentStep.Status = finalStatus
|
currentStep.Status = finalStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch write all buffered data
|
// Batch write all buffered messages (user input + assistant responses)
|
||||||
chatStore.SaveMessages(ctx.ChatID, messageBuffer.GetAll())
|
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
|
// Update token usage in OpenAPI request record
|
||||||
if ctx.RequestID != "" && completionResponse != nil {
|
if ctx.RequestID != "" && completionResponse != nil {
|
||||||
|
|
@ -580,7 +737,6 @@ type Chat struct {
|
||||||
AssistantID string `json:"assistant_id"`
|
AssistantID string `json:"assistant_id"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Preset bool `json:"preset"`
|
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
Share string `json:"share"` // "private" or "team"
|
Share string `json:"share"` // "private" or "team"
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
|
|
@ -639,6 +795,20 @@ type ChatFilter struct {
|
||||||
AssistantID string `json:"assistant_id,omitempty"`
|
AssistantID string `json:"assistant_id,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
Keywords string `json:"keywords,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"`
|
Page int `json:"page,omitempty"`
|
||||||
PageSize int `json:"pagesize,omitempty"`
|
PageSize int `json:"pagesize,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
@ -648,17 +818,28 @@ type MessageFilter struct {
|
||||||
RequestID string `json:"request_id,omitempty"`
|
RequestID string `json:"request_id,omitempty"`
|
||||||
Role string `json:"role,omitempty"`
|
Role string `json:"role,omitempty"`
|
||||||
BlockID string `json:"block_id,omitempty"`
|
BlockID string `json:"block_id,omitempty"`
|
||||||
|
ThreadID string `json:"thread_id,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
Limit int `json:"limit,omitempty"`
|
Limit int `json:"limit,omitempty"`
|
||||||
Offset int `json:"offset,omitempty"`
|
Offset int `json:"offset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatList paginated response
|
// ChatList paginated response with time-based grouping
|
||||||
type ChatList struct {
|
type ChatList struct {
|
||||||
Data []*Chat `json:"data"`
|
Data []*Chat `json:"data"`
|
||||||
Page int `json:"page"`
|
Groups []*ChatGroup `json:"groups,omitempty"` // Time-based groups for UI display
|
||||||
PageSize int `json:"pagesize"`
|
Page int `json:"page"`
|
||||||
PageCount int `json:"pagecount"`
|
PageSize int `json:"pagesize"`
|
||||||
Total int `json:"total"`
|
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
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -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."
|
User: "What's the weather in SF? Also show me a chart."
|
||||||
|
|
||||||
Timeline:
|
Timeline (user input → hook_create → llm → tool → hook_next):
|
||||||
1. User sends multimodal input
|
1. User sends input
|
||||||
2. Hook shows loading state
|
2. Create hook shows loading state
|
||||||
3. LLM thinks and calls tool
|
3. LLM thinks and calls tool
|
||||||
4. Tool returns result
|
4. Tool executes and returns result
|
||||||
5. LLM generates text response
|
5. Next hook generates text response and image chart
|
||||||
6. Hook sends image chart
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Stored Messages:**
|
**Stored Messages:**
|
||||||
|
|
@ -745,7 +925,7 @@ Timeline:
|
||||||
"sequence": 4
|
"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",
|
"message_id": "msg_005",
|
||||||
"chat_id": "chat_123",
|
"chat_id": "chat_123",
|
||||||
|
|
@ -753,39 +933,23 @@ Timeline:
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"props": {
|
"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": {
|
"metadata": {
|
||||||
"tool_call_id": "call_weather_001",
|
"tool_call_id": "call_weather_001",
|
||||||
"tool_name": "get_weather",
|
"tool_name": "get_weather"
|
||||||
"is_tool_result": true
|
|
||||||
},
|
},
|
||||||
"assistant_id": "weather_assistant",
|
"assistant_id": "weather_assistant",
|
||||||
"sequence": 5
|
"sequence": 5
|
||||||
},
|
},
|
||||||
|
|
||||||
// 6. LLM text response (role=assistant, type=text)
|
// 6. Chart image from Next hook (role=assistant, type=image)
|
||||||
{
|
{
|
||||||
"message_id": "msg_006",
|
"message_id": "msg_006",
|
||||||
"chat_id": "chat_123",
|
"chat_id": "chat_123",
|
||||||
"request_id": "req_abc",
|
"request_id": "req_abc",
|
||||||
"role": "assistant",
|
"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",
|
"type": "image",
|
||||||
"props": {
|
"props": {
|
||||||
"url": "https://charts.example.com/weather_sf.png",
|
"url": "https://charts.example.com/weather_sf.png",
|
||||||
|
|
@ -795,7 +959,7 @@ Timeline:
|
||||||
},
|
},
|
||||||
"block_id": "B3",
|
"block_id": "B3",
|
||||||
"assistant_id": "weather_assistant",
|
"assistant_id": "weather_assistant",
|
||||||
"sequence": 7
|
"sequence": 6
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
@ -904,13 +1068,54 @@ Multimedia content storage:
|
||||||
### 5. Load Chat History
|
### 5. Load Chat History
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Get chat list
|
// Example 1: Flat list (default)
|
||||||
chats, _ := chatStore.ListChats(ChatFilter{
|
chats, _ := chatStore.ListChats(ChatFilter{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
Status: "active",
|
Status: "active",
|
||||||
|
OrderBy: "last_message_at",
|
||||||
|
Order: "desc",
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PageSize: 20,
|
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
|
// Get messages for a chat
|
||||||
messages, _ := chatStore.GetMessages("chat_123", MessageFilter{
|
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")
|
// 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
|
## Related Documents
|
||||||
|
|
||||||
- [OpenAPI Request Design](../../openapi/request/REQUEST_DESIGN.md) - Global request tracking, billing, rate limiting
|
- [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{
|
var systemModels = map[string]string{
|
||||||
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
|
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
|
||||||
"__yao.agent.chat": "yao/models/agent/chat.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.attachment": "yao/models/attachment.mod.yao",
|
||||||
"__yao.audit": "yao/models/audit.mod.yao",
|
"__yao.audit": "yao/models/audit.mod.yao",
|
||||||
"__yao.config": "yao/models/config.mod.yao",
|
"__yao.config": "yao/models/config.mod.yao",
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,8 @@ var testServer *http.Server = nil
|
||||||
var testSystemModels = map[string]string{
|
var testSystemModels = map[string]string{
|
||||||
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
|
"__yao.agent.assistant": "yao/models/agent/assistant.mod.yao",
|
||||||
"__yao.agent.chat": "yao/models/agent/chat.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.attachment": "yao/models/attachment.mod.yao",
|
||||||
"__yao.audit": "yao/models/audit.mod.yao",
|
"__yao.audit": "yao/models/audit.mod.yao",
|
||||||
"__yao.config": "yao/models/config.mod.yao",
|
"__yao.config": "yao/models/config.mod.yao",
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,103 @@
|
||||||
{
|
{
|
||||||
"name": "Chat",
|
"name": "Chat",
|
||||||
"label": "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"],
|
"tags": ["agent", "system"],
|
||||||
"builtin": true,
|
"builtin": true,
|
||||||
"readonly": true,
|
"readonly": true,
|
||||||
"sort": 9999,
|
"sort": 9999,
|
||||||
"table": { "name": "agent_chat", "comment": "Agent chat table" },
|
"table": { "name": "agent_chat", "comment": "Agent chat session table" },
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"type": "ID",
|
"type": "ID",
|
||||||
"label": "Chat ID",
|
"label": "ID",
|
||||||
"comment": "Unique chat identifier"
|
"comment": "Auto-increment primary key"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "chat_id",
|
"name": "chat_id",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "Chat ID",
|
"label": "Chat ID",
|
||||||
"comment": "Chat identifier",
|
"comment": "Unique chat identifier",
|
||||||
"length": 200,
|
"length": 64,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"unique": true,
|
"unique": true
|
||||||
"index": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"comment": "Chat title",
|
"comment": "Chat title",
|
||||||
"length": 200,
|
"length": 500,
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "assistant_id",
|
"name": "assistant_id",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "Assistant ID",
|
"label": "Assistant ID",
|
||||||
"comment": "Assistant identifier",
|
"comment": "Associated assistant ID",
|
||||||
"length": 200,
|
"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,
|
"nullable": true,
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "silent",
|
"name": "metadata",
|
||||||
"type": "boolean",
|
"type": "json",
|
||||||
"label": "Silent Mode",
|
"label": "Metadata",
|
||||||
"comment": "Whether this is a silent chat",
|
"comment": "Additional metadata",
|
||||||
"default": false,
|
"nullable": true
|
||||||
"index": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
@ -57,20 +107,19 @@
|
||||||
"key": "assistant_id",
|
"key": "assistant_id",
|
||||||
"foreign": "assistant_id"
|
"foreign": "assistant_id"
|
||||||
},
|
},
|
||||||
"history": {
|
"messages": {
|
||||||
"type": "hasMany",
|
"type": "hasMany",
|
||||||
"model": "__yao.agent.history",
|
"model": "__yao.agent.message",
|
||||||
"key": "chat_id",
|
"key": "chat_id",
|
||||||
"foreign": "cid"
|
"foreign": "chat_id"
|
||||||
|
},
|
||||||
|
"resumes": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "__yao.agent.resume",
|
||||||
|
"key": "chat_id",
|
||||||
|
"foreign": "chat_id"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"indexes": [
|
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
|
||||||
{
|
|
||||||
"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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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