Revise Agent and SUI Documentation for Clarity and Consistency

- Updated the Agent API documentation to reflect a new structure, emphasizing quick start instructions and reorganizing content for better readability.
- Renamed the main documentation title to "Yao Agent" and streamlined sections, including API endpoints and file management.
- Adjusted the SUI documentation to align with the Yao App Engine license, ensuring consistency across project documentation.
This commit is contained in:
Max 2026-01-01 11:08:30 +08:00
parent bd8e91d434
commit bdfe7e83e8
10 changed files with 2281 additions and 584 deletions

View file

@ -1,597 +1,149 @@
# Agent API Documentation
# Yao Agent
Agent is a chat/AI assistant API that provides endpoints for managing conversations, assistants, file uploads, and more.
A powerful AI assistant framework for building intelligent conversational agents with tool integration, knowledge base search, and multi-agent orchestration.
## Base URL
## Quick Start
All endpoints are relative to your base URL + `/api/__yao/agent`
### 1. Create an Assistant
Example: `http://localhost:5099/api/__yao/agent`
```
assistants/
└── my-assistant/
├── package.yao # Configuration
├── prompts.yml # System prompts
└── locales/
└── en-us.yml # Translations
```
## Authentication
**package.yao**
All endpoints require a `token` parameter for authentication. The token can be provided as:
```json
{
"name": "{{ name }}",
"connector": "gpt-4o",
"description": "{{ description }}",
"placeholder": {
"title": "{{ chat.title }}",
"prompts": ["{{ chat.prompts.0 }}"]
}
}
```
- Query parameter: `?token=your_token_here`
- Authorization header: `Authorization: Bearer your_token_here`
**prompts.yml**
## CORS Support
```yaml
- role: system
content: |
You are a helpful assistant.
```
The API supports Cross-Origin Resource Sharing (CORS) and handles preflight OPTIONS requests.
**locales/en-us.yml**
```yaml
name: My Assistant
description: A helpful AI assistant
chat:
title: New Chat
prompts:
- How can I help you today?
```
### 2. Add Hooks (Optional)
Create `src/index.ts` for custom logic:
```typescript
import { agent } from "@yao/runtime";
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Preprocess messages before LLM call
return { messages };
}
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
// Post-process LLM response
return null;
}
```
### 3. Test (Optional)
```bash
yao agent test -i "Hello, how are you?"
```
### 4. Run
```bash
yao start
```
Access via API: `POST /api/__yao/agent`
## Documentation
- [Configuration](docs/configuration.md) - Assistant settings, connectors, options
- [Prompts](docs/prompts.md) - System prompts and prompt presets
- [Hooks](docs/hooks.md) - Create/Next hooks and agent lifecycle
- [Context API](docs/context-api.md) - Messaging, memory, trace, MCP
- [MCP Integration](docs/mcp.md) - Tool servers and resources
- [Search](docs/search.md) - Web, knowledge base, and database search
- [Internationalization](docs/i18n.md) - Multi-language support
- [Testing](docs/testing.md) - Agent testing framework
## Architecture
```mermaid
flowchart LR
subgraph Request
A[User Request]
end
subgraph Create["Create Hook"]
B1[Preprocess Messages]
B2[Configure LLM]
B3[Delegate to Agent]
end
subgraph LLM["LLM Call"]
C1[Load Prompts]
C2[Generate Response]
end
subgraph Tools["Tool Execution"]
D1[MCP Tools]
D2[Search]
D3[Memory]
end
subgraph Next["Next Hook"]
E1[Process Results]
E2[Transform Output]
E3[Delegate to Agent]
end
subgraph Response
F[Stream Response]
end
A --> Create
Create --> LLM
LLM --> Tools
Tools --> Next
Next --> Response
Next -.->|Continue| LLM
```
## API Endpoints
### 1. Chat Endpoints
| Endpoint | Method | Description |
| ------------------------------- | ------ | ------------------- |
| `/api/__yao/agent` | POST | Chat with assistant |
| `/api/__yao/agent/history` | GET | Get chat history |
| `/api/__yao/agent/chats` | GET | List chat sessions |
| `/api/__yao/agent/assistants` | GET | List assistants |
| `/api/__yao/agent/upload/:type` | POST | Upload files |
#### 1.1 Chat with AI
## License
Start or continue a conversation with an AI assistant.
**Endpoints:**
- `GET /` - Chat via query parameters
- `POST /` - Chat via JSON body
**Parameters:**
- `content` (required) - The message content
- `chat_id` (optional) - Chat session ID. If not provided, a new one will be generated
- `context` (optional) - Additional context for the conversation
- `assistant_id` (optional) - Specific assistant to use
- `silent` (optional) - Silent mode: `true` or `1`
- `history_visible` (optional) - Show history: `true` or `1`
- `client_type` (optional) - Client type identifier
**Examples:**
```bash
# GET request
curl -X GET 'http://localhost:5099/api/__yao/agent?content=Hello&chat_id=chat_123&token=xxx'
# POST request
curl -X POST 'http://localhost:5099/api/__yao/agent' \
-H 'Content-Type: application/json' \
-d '{"content": "Hello", "chat_id": "chat_123", "token": "xxx"}'
```
**Response:**
Server-Sent Events (SSE) stream with chat messages.
#### 1.2 Chat History
Get conversation history for a specific chat.
**Endpoint:** `GET /history`
**Parameters:**
- `chat_id` (required) - Chat session ID
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/history?chat_id=chat_123&token=xxx'
```
**Response:**
```json
{
"data": [
{
"role": "user",
"content": "Hello",
"timestamp": "2024-01-01T00:00:00Z"
},
{
"role": "assistant",
"content": "Hi there!",
"timestamp": "2024-01-01T00:00:01Z"
}
]
}
```
### 2. Chat Management
#### 2.1 List Chats
Get a paginated list of chat conversations.
**Endpoint:** `GET /chats`
**Parameters:**
- `page` (optional) - Page number (default: 1)
- `pagesize` (optional) - Items per page (default: 20)
- `keywords` (optional) - Search keywords
- `order` (optional) - Sort order (`asc` or `desc`)
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/chats?page=1&pagesize=20&keywords=search+term&order=desc&token=xxx'
```
**Response:**
```json
{
"data": {
"groups": [
{
"date": "2024-01-01",
"chats": [
{
"chat_id": "chat_123",
"title": "Chat Title",
"updated_at": "2024-01-01T00:00:00Z"
}
]
}
],
"total": 50,
"page": 1,
"pagesize": 20
}
}
```
#### 2.2 Get Latest Chat
Get the most recent chat or create a new one if none exists.
**Endpoint:** `GET /chats/latest`
**Parameters:**
- `assistant_id` (optional) - Preferred assistant ID
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/chats/latest?assistant_id=assistant_123&token=xxx'
```
#### 2.3 Get Chat Details
Get detailed information about a specific chat.
**Endpoint:** `GET /chats/:id`
**Parameters:**
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/chats/chat_123?token=xxx'
```
#### 2.4 Update Chat
Update chat metadata (e.g., title).
**Endpoint:** `POST /chats/:id`
**Body:**
```json
{
"title": "New Title",
"content": "Chat content for title generation"
}
```
**Example:**
```bash
curl -X POST 'http://localhost:5099/api/__yao/agent/chats/chat_123' \
-H 'Content-Type: application/json' \
-d '{"title": "New Title", "content": "Chat content", "token": "xxx"}'
```
#### 2.5 Delete Chat
Delete a specific chat conversation.
**Endpoint:** `DELETE /chats/:id`
**Example:**
```bash
curl -X DELETE 'http://localhost:5099/api/__yao/agent/chats/chat_123?token=xxx'
```
### 3. Assistant Management
#### 3.1 List Assistants
Get a paginated list of available assistants.
**Endpoint:** `GET /assistants`
**Parameters:**
- `page` (optional) - Page number (default: 1)
- `pagesize` (optional) - Items per page (default: 20)
- `tags` (optional) - Comma-separated list of tags
- `keywords` (optional) - Search keywords
- `connector` (optional) - Connector name filter
- `select` (optional) - Comma-separated fields to select
- `built_in` (optional) - Filter built-in assistants (`true`/`false`/`1`/`0`)
- `mentionable` (optional) - Filter mentionable assistants (`true`/`false`/`1`/`0`)
- `automated` (optional) - Filter automated assistants (`true`/`false`/`1`/`0`)
- `assistant_id` (optional) - Specific assistant ID
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/assistants?page=1&pagesize=20&tags=tag1,tag2&token=xxx'
```
#### 3.2 Get Assistant Tags
Get all available assistant tags.
**Endpoint:** `GET /assistants/tags`
**Parameters:**
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/assistants/tags?token=xxx'
```
#### 3.3 Get Assistant Details
Get detailed information about a specific assistant.
**Endpoint:** `GET /assistants/:id`
**Parameters:**
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/assistants/assistant_123?token=xxx'
```
#### 3.4 Execute Assistant API
Call a specific assistant's API functionality.
**Endpoint:** `POST /assistants/:id/call`
**Body:**
```json
{
"name": "Test",
"payload": {
"name": "yao",
"age": 18
}
}
```
**Example:**
```bash
curl -X POST 'http://localhost:5099/api/__yao/agent/assistants/assistant_123/call' \
-H 'Content-Type: application/json' \
-d '{"name": "Test", "payload": {"name": "yao", "age": 18}}'
```
#### 3.5 Create/Update Assistant
Create a new assistant or update an existing one.
**Endpoint:** `POST /assistants`
**Body:**
```json
{
"name": "My Assistant",
"type": "chat",
"tags": ["tag1", "tag2"],
"mentionable": true,
"avatar": "path/to/avatar.png"
}
```
**Example:**
```bash
curl -X POST 'http://localhost:5099/api/__yao/agent/assistants' \
-H 'Content-Type: application/json' \
-d '{"name": "My Assistant", "type": "chat", "tags": ["tag1"], "token": "xxx"}'
```
#### 3.6 Delete Assistant
Delete a specific assistant.
**Endpoint:** `DELETE /assistants/:id`
**Example:**
```bash
curl -X DELETE 'http://localhost:5099/api/__yao/agent/assistants/assistant_123?token=xxx'
```
### 4. File Management
#### 4.1 Upload File
Upload files to different storage types.
**Endpoint:** `POST /upload/:storage`
**Storage Types:**
- `chat` - Chat-related files
- `knowledge` - Knowledge base files
- `assets` - General assets
**Form Data:**
- `file` (required) - The file to upload
- `chat_id` (required for chat storage) - Chat session ID
- `collection_id` (required for knowledge storage) - Knowledge collection ID
- `public` (optional) - Make file public
- `gzip` (optional) - Enable gzip compression
**Example:**
```bash
curl -X POST 'http://localhost:5099/api/__yao/agent/upload/chat?chat_id=chat_123&token=xxx' \
-F 'file=@/path/to/file.txt'
```
**Response:**
```json
{
"data": {
"id": "file_123",
"content_type": "text/plain",
"bytes": 1024,
"status": "uploaded"
}
}
```
#### 4.2 Download File
Download a previously uploaded file.
**Endpoint:** `GET /download`
**Parameters:**
- `file_id` (required) - File ID to download
- `disposition` (optional) - Content disposition (default: `attachment`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/download?file_id=file_123&disposition=attachment&token=xxx' \
-o downloaded_file.txt
```
### 5. Mentions
#### 5.1 Get Mentions
Get mentionable assistants for autocomplete.
**Endpoint:** `GET /mentions`
**Parameters:**
- `keywords` (optional) - Search keywords
- `locale` (optional) - Locale code (default: `en-us`)
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/mentions?keywords=assistant&token=xxx'
```
**Response:**
```json
{
"data": [
{
"id": "assistant_123",
"name": "Assistant Name",
"type": "chat",
"avatar": "avatar_url"
}
]
}
```
### 6. Generation Endpoints
#### 6.1 Generate Title
Generate a title for chat content.
**Endpoints:**
- `GET /generate/title` - Generate via query parameters
- `POST /generate/title` - Generate via JSON body
**Parameters:**
- `content` (required) - Content to generate title for
- `chat_id` (optional) - Associated chat ID
- `context` (optional) - Additional context
**Examples:**
```bash
# GET request
curl -X GET 'http://localhost:5099/api/__yao/agent/generate/title?content=Chat+content&chat_id=chat_123&token=xxx'
# POST request
curl -X POST 'http://localhost:5099/api/__yao/agent/generate/title' \
-H 'Content-Type: application/json' \
-d '{"content": "Chat content", "chat_id": "chat_123", "token": "xxx"}'
```
**Response:** SSE stream with generated title
#### 6.2 Generate Prompts
Generate prompts based on content.
**Endpoints:**
- `GET /generate/prompts` - Generate via query parameters
- `POST /generate/prompts` - Generate via JSON body
**Parameters:**
- `content` (required) - Content to generate prompts for
- `chat_id` (optional) - Associated chat ID
- `context` (optional) - Additional context
**Examples:**
```bash
# GET request
curl -X GET 'http://localhost:5099/api/__yao/agent/generate/prompts?content=Generate+prompts&chat_id=chat_123&token=xxx'
# POST request
curl -X POST 'http://localhost:5099/api/__yao/agent/generate/prompts' \
-H 'Content-Type: application/json' \
-d '{"content": "Generate prompts", "chat_id": "chat_123", "token": "xxx"}'
```
**Response:** SSE stream with generated prompts
### 7. Utility Endpoints
#### 7.1 List Connectors
Get available AI connectors.
**Endpoint:** `GET /utility/connectors`
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/utility/connectors?token=xxx'
```
**Response:**
```json
{
"data": [
{
"label": "OpenAI",
"value": "openai"
},
{
"label": "Custom API",
"value": "custom_api"
}
]
}
```
#### 7.2 Status Check
Check API service status.
**Endpoint:** `GET /status`
**Example:**
```bash
curl -X GET 'http://localhost:5099/api/__yao/agent/status?token=xxx'
```
**Response:** HTTP 200 status code
### 8. Dangerous Operations
#### 8.1 Clear All Chats
Delete all chat conversations for the authenticated user.
**Endpoint:** `DELETE /dangerous/clear_chats`
**Example:**
```bash
curl -X DELETE 'http://localhost:5099/api/__yao/agent/dangerous/clear_chats?token=xxx'
```
**Response:**
```json
{
"message": "ok"
}
```
## Error Responses
All endpoints return JSON error responses in the following format:
```json
{
"message": "Error description",
"code": 400
}
```
Common error codes:
- `400` - Bad Request (missing or invalid parameters)
- `401` - Unauthorized (invalid or missing token)
- `403` - Forbidden (access denied)
- `404` - Not Found (resource not found)
- `500` - Internal Server Error
## Server-Sent Events (SSE)
Chat and generation endpoints return Server-Sent Events for real-time streaming:
**Headers:**
- `Content-Type: text/event-stream;charset=utf-8`
- `Cache-Control: no-cache`
- `Connection: keep-alive`
**Event Format:**
```
data: {"type": "message", "content": "Hello"}
data: {"type": "done"}
```
## Rate Limiting
Rate limiting may be applied based on your authentication token and usage patterns.
## Support
For support and questions, please refer to the Yao App Engine documentation.
This project is part of the Yao App Engine and follows the [Yao Open Source License](../LICENSE).

243
agent/docs/configuration.md Normal file
View file

@ -0,0 +1,243 @@
# Assistant Configuration
## Directory Structure
```
assistants/
└── <assistant-id>/
├── package.yao # Required: Configuration
├── prompts.yml # Optional: Default prompts
├── prompts/ # Optional: Prompt presets
│ ├── chat.yml
│ └── task.yml
├── locales/ # Optional: Translations
│ ├── en-us.yml
│ └── zh-cn.yml
├── src/ # Optional: Hook scripts
│ └── index.ts
└── mcps/ # Optional: MCP servers
└── tools.mcp.yao
```
## package.yao
### Basic Fields
```json
{
"name": "{{ name }}",
"type": "assistant",
"avatar": "/assets/avatar.png",
"description": "{{ description }}",
"connector": "gpt-4o",
"tags": ["Category1", "Category2"],
"sort": 1
}
```
| Field | Type | Description |
| ------------- | -------- | ------------------------------------ |
| `name` | string | Display name (supports i18n `{{ }}`) |
| `type` | string | Type: `assistant` (default) |
| `avatar` | string | Avatar image path |
| `description` | string | Description (supports i18n) |
| `connector` | string | LLM connector ID |
| `tags` | string[] | Categorization tags |
| `sort` | number | Display order |
### Connector Options
```json
{
"connector": "gpt-4o",
"connector_options": {
"optional": true,
"connectors": ["gpt-4o", "gpt-4o-mini", "claude-3"],
"filters": ["tool_calls", "vision"]
}
}
```
| Field | Type | Description |
| ------------ | -------- | -------------------------------------------- |
| `optional` | boolean | Allow user to select connector |
| `connectors` | string[] | Available connectors (empty = all) |
| `filters` | string[] | Required capabilities: `vision`, `audio`, `tool_calls`, `reasoning` |
### Generation Options
```json
{
"options": {
"temperature": 0.7,
"max_tokens": 4096
}
}
```
### Placeholder (UI Hints)
```json
{
"placeholder": {
"title": "{{ chat.title }}",
"description": "{{ chat.description }}",
"prompts": [
"{{ chat.prompts.0 }}",
"{{ chat.prompts.1 }}"
]
}
}
```
### Visibility & Access
```json
{
"public": true,
"share": "team",
"readonly": true,
"built_in": true,
"mentionable": true,
"automated": false
}
```
| Field | Type | Description |
| ------------- | ------- | --------------------------------- |
| `public` | boolean | Visible to all users |
| `share` | string | Sharing scope: `private`, `team` |
| `readonly` | boolean | Prevent user modifications |
| `built_in` | boolean | System-managed assistant |
| `mentionable` | boolean | Can be @mentioned in chat |
| `automated` | boolean | Can be triggered automatically |
### Modes
```json
{
"modes": ["chat", "task"],
"default_mode": "task"
}
```
### MCP Servers
```json
{
"mcp": {
"servers": [
"server-id",
{ "server_id": "tools", "tools": ["tool1", "tool2"] },
{ "server_id": "resources", "resources": ["uri://pattern"] }
]
}
}
```
### Knowledge Base
```json
{
"kb": {
"collections": ["collection-id-1", "collection-id-2"]
}
}
```
### Database Models
```json
{
"db": {
"models": ["model.name", "another.model"]
}
}
```
### Uses (Wrapper Tools)
```json
{
"uses": {
"vision": "vision-agent",
"audio": "audio-agent",
"search": "disabled",
"fetch": "mcp:fetcher"
}
}
```
| Field | Description |
| -------- | -------------------------------------------------- |
| `vision` | Vision processing: `<agent-id>` or `mcp:<server>` |
| `audio` | Audio processing: `<agent-id>` or `mcp:<server>` |
| `search` | Search: `disabled`, `<agent-id>`, or `mcp:<server>`|
| `fetch` | HTTP fetching: `<agent-id>` or `mcp:<server>` |
### Search Configuration
```json
{
"search": {
"web": {
"provider": "tavily",
"max_results": 10
},
"kb": {
"threshold": 0.7,
"graph": true
},
"db": {
"max_results": 20
},
"citation": {
"format": "[{index}]",
"auto_inject_prompt": true
}
}
}
```
## Environment Variables
Use `$ENV.VAR_NAME` for sensitive values:
```json
{
"connector": "$ENV.LLM_CONNECTOR"
}
```
## Complete Example
```json
{
"name": "{{ name }}",
"type": "assistant",
"avatar": "/assets/assistant.png",
"connector": "gpt-4o",
"connector_options": {
"optional": true,
"connectors": ["gpt-4o", "gpt-4o-mini"],
"filters": ["tool_calls"]
},
"mcp": {
"servers": [{ "server_id": "tools", "tools": ["search", "calculate"] }]
},
"description": "{{ description }}",
"options": { "temperature": 0.7 },
"public": true,
"placeholder": {
"title": "{{ chat.title }}",
"description": "{{ chat.description }}",
"prompts": ["{{ chat.prompts.0 }}", "{{ chat.prompts.1 }}"]
},
"tags": ["Productivity"],
"modes": ["chat", "task"],
"default_mode": "chat",
"sort": 1,
"readonly": true,
"mentionable": true
}
```

314
agent/docs/context-api.md Normal file
View file

@ -0,0 +1,314 @@
# Context API
The `ctx` object provides access to messaging, memory, tracing, and MCP operations.
## Properties
```typescript
interface Context {
chat_id: string; // Chat session ID
assistant_id: string; // Assistant ID
locale: string; // User locale (e.g., "en-us")
theme: string; // UI theme
route: string; // Request route
referer: string; // Request source
metadata: Record<string, any>; // Custom metadata
authorized: Record<string, any>; // Auth info
memory: Memory; // Memory namespaces
trace: Trace; // Tracing API
mcp: MCP; // MCP operations
search: Search; // Search API
}
```
## Messaging
### Send Complete Message
```typescript
ctx.Send({ type: "text", props: { content: "Hello!" } });
ctx.Send("Hello!"); // Shorthand for text
```
### Streaming Messages
```typescript
const msgId = ctx.SendStream("Starting...");
ctx.Append(msgId, " processing...");
ctx.Append(msgId, " done!");
ctx.End(msgId);
```
### Update Streaming Message
```typescript
const msgId = ctx.SendStream({ type: "loading", props: { message: "Loading..." } });
// ... do work ...
ctx.Replace(msgId, { type: "text", props: { content: "Complete!" } });
ctx.End(msgId);
```
### Merge Data
```typescript
const msgId = ctx.SendStream({ type: "status", props: { progress: 0 } });
ctx.Merge(msgId, { progress: 50 }, "props");
ctx.Merge(msgId, { progress: 100, status: "done" }, "props");
ctx.End(msgId);
```
### Set Field
```typescript
const msgId = ctx.SendStream({ type: "result", props: {} });
ctx.Set(msgId, "success", "props.status");
ctx.Set(msgId, { count: 10 }, "props.data");
ctx.End(msgId);
```
### Block Grouping
```typescript
const blockId = ctx.BlockID();
ctx.Send("Step 1", blockId);
ctx.Send("Step 2", blockId);
ctx.Send("Step 3", blockId);
ctx.EndBlock(blockId);
```
### ID Generators
```typescript
const msgId = ctx.MessageID(); // "M1", "M2", ...
const blockId = ctx.BlockID(); // "B1", "B2", ...
const threadId = ctx.ThreadID(); // "T1", "T2", ...
```
## Memory
Four-level hierarchical memory system:
| Namespace | Scope | Persistence |
| -------------------- | ------------ | ----------- |
| `ctx.memory.user` | Per user | Persistent |
| `ctx.memory.team` | Per team | Persistent |
| `ctx.memory.chat` | Per chat | Persistent |
| `ctx.memory.context` | Per request | Temporary |
### Basic Operations
```typescript
// Get/Set
ctx.memory.user.Set("theme", "dark");
const theme = ctx.memory.user.Get("theme");
// With TTL (seconds)
ctx.memory.context.Set("temp", data, 300);
// Check/Delete
if (ctx.memory.chat.Has("topic")) {
ctx.memory.chat.Del("topic");
}
// Get and delete atomically
const token = ctx.memory.context.GetDel("one_time_token");
// Collection operations
const keys = ctx.memory.user.Keys();
const count = ctx.memory.chat.Len();
ctx.memory.context.Clear();
```
### Counters
```typescript
const views = ctx.memory.user.Incr("page_views");
const credits = ctx.memory.user.Decr("credits", 5);
```
### Lists
```typescript
ctx.memory.chat.Push("history", [msg1, msg2]);
const last = ctx.memory.chat.Pop("queue");
const items = ctx.memory.chat.Pull("queue", 5);
const all = ctx.memory.chat.PullAll("queue");
```
### Sets
```typescript
ctx.memory.user.AddToSet("visited", ["/home", "/about"]);
```
### Array Access
```typescript
const len = ctx.memory.chat.ArrayLen("messages");
const first = ctx.memory.chat.ArrayGet("messages", 0);
const last = ctx.memory.chat.ArrayGet("messages", -1);
ctx.memory.chat.ArraySet("messages", 0, newMsg);
const slice = ctx.memory.chat.ArraySlice("messages", -10, -1);
const page = ctx.memory.chat.ArrayPage("messages", 1, 20);
const all = ctx.memory.chat.ArrayAll("messages");
```
## Trace
### Create Nodes
```typescript
const node = ctx.trace.Add(
{ query: "input data" },
{
label: "Processing",
type: "process",
icon: "play",
description: "Processing user request"
}
);
```
### Logging
```typescript
ctx.trace.Info("Starting process");
ctx.trace.Debug("Variable: " + value);
ctx.trace.Warn("Deprecated feature");
ctx.trace.Error("Operation failed");
// Or on node
node.Info("Step completed");
```
### Node Lifecycle
```typescript
node.SetOutput({ result: data });
node.SetMetadata("duration", 1500);
node.Complete({ status: "done" });
// or
node.Fail("Error message");
```
### Parallel Nodes
```typescript
const nodes = ctx.trace.Parallel([
{ input: { url: "api1" }, option: { label: "API 1" } },
{ input: { url: "api2" }, option: { label: "API 2" } }
]);
```
### Child Nodes
```typescript
const parent = ctx.trace.Add({}, { label: "Parent" });
const child = parent.Add({}, { label: "Child" });
```
## MCP
### Tools
```typescript
// List tools
const tools = ctx.mcp.ListTools("server-id");
// Call single tool
const result = ctx.mcp.CallTool("server-id", "tool-name", { arg: "value" });
// Call multiple sequentially
const results = ctx.mcp.CallTools("server-id", [
{ name: "tool1", arguments: { a: 1 } },
{ name: "tool2", arguments: { b: 2 } }
]);
// Call multiple in parallel
const results = ctx.mcp.CallToolsParallel("server-id", [
{ name: "tool1", arguments: {} },
{ name: "tool2", arguments: {} }
]);
```
### Resources
```typescript
const resources = ctx.mcp.ListResources("server-id");
const data = ctx.mcp.ReadResource("server-id", "resource://uri");
```
### Prompts
```typescript
const prompts = ctx.mcp.ListPrompts("server-id");
const prompt = ctx.mcp.GetPrompt("server-id", "prompt-name", { arg: "value" });
```
## Search
### Single Search
```typescript
// Web search
const webResult = ctx.search.Web("query", {
limit: 10,
sites: ["example.com"],
time_range: "week"
});
// Knowledge base
const kbResult = ctx.search.KB("query", {
collections: ["docs"],
threshold: 0.7,
graph: true
});
// Database
const dbResult = ctx.search.DB("query", {
models: ["model.name"],
wheres: [{ column: "status", value: "active" }],
limit: 20
});
```
### Parallel Search
```typescript
// Wait for all
const results = ctx.search.All([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic", collections: ["docs"] }
]);
// First success
const results = ctx.search.Any([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
// First complete
const results = ctx.search.Race([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
```
### Result Structure
```typescript
interface SearchResult {
type: "web" | "kb" | "db";
query: string;
source: "hook" | "auto" | "user";
items: {
citation_id: string;
title: string;
url: string;
content: string;
score: number;
}[];
error?: string;
}
```

294
agent/docs/hooks.md Normal file
View file

@ -0,0 +1,294 @@
# Hooks
Hooks allow you to customize agent behavior at key points in the execution lifecycle.
## Lifecycle
```
User Input → Create Hook → LLM Call → Tool Execution → Next Hook → Response
```
## Create Hook
Called before LLM call. Use to preprocess messages, configure request, or delegate.
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Return null for default behavior
return null;
// Or return configuration
return {
messages, // Modified messages
temperature: 0.7, // Override temperature
max_tokens: 2000, // Override max tokens
connector: "gpt-4o-mini", // Override connector
prompt_preset: "task", // Select prompt preset
disable_global_prompts: true,// Skip global prompts
mcp_servers: [ // Add MCP servers
{ server_id: "tools", tools: ["search"] }
],
uses: { // Override wrapper tools
vision: "vision-agent",
search: "disabled"
},
force_uses: true, // Force use wrapper tools
locale: "zh-cn", // Override locale
metadata: { key: "value" }, // Pass data to context
};
}
```
### Delegation (Skip LLM)
Route to another agent immediately:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
if (shouldDelegate(messages)) {
return {
delegate: {
agent_id: "specialist.agent",
messages: messages,
options: { metadata: { source: "main" } }
}
};
}
return { messages };
}
```
## Next Hook
Called after LLM response and tool execution. Use to post-process or delegate.
```typescript
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { messages, completion, tools, error } = payload;
// Handle errors
if (error) {
return { data: { status: "error", message: error } };
}
// Process tool results
if (tools?.length > 0) {
const results = tools.map(t => t.result);
return { data: { status: "success", results } };
}
// Delegate based on response
if (completion?.content?.includes("transfer")) {
return {
delegate: {
agent_id: "transfer.agent",
messages: payload.messages
}
};
}
// Return null for standard response
return null;
}
```
### Payload Structure
```typescript
interface Payload {
messages: Message[]; // Messages sent to LLM
completion?: {
content: string; // LLM text response
tool_calls?: ToolCall[]; // Tool calls from LLM
usage?: UsageInfo; // Token usage
};
tools?: ToolCallResponse[]; // Tool execution results
error?: string; // Error message
}
interface ToolCallResponse {
toolcall_id: string;
server: string; // MCP server ID
tool: string; // Tool name
arguments?: any; // Tool arguments
result?: any; // Tool result
error?: string; // Tool error
}
```
### Return Values
```typescript
interface NextResponse {
delegate?: { // Route to another agent
agent_id: string;
messages: Message[];
options?: Record<string, any>;
};
data?: any; // Custom response data
metadata?: Record<string, any>;// Debug metadata
}
```
## Sending Messages
Use `ctx` to send messages to the client:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Send complete message
ctx.Send({ type: "text", props: { content: "Processing..." } });
// Streaming message
const msgId = ctx.SendStream("Starting...");
ctx.Append(msgId, " step 1...");
ctx.Append(msgId, " step 2...");
ctx.End(msgId);
return { messages };
}
```
## Memory
Share data between hooks:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Store in request-scoped memory
ctx.memory.context.Set("start_time", Date.now());
ctx.memory.context.Set("query", messages[0]?.content);
return { messages };
}
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
// Retrieve data
const startTime = ctx.memory.context.Get("start_time");
const duration = Date.now() - startTime;
return { data: { duration_ms: duration } };
}
```
## Tracing
Add trace nodes for debugging and UI:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const node = ctx.trace.Add(
{ query: messages[0]?.content },
{ label: "Preprocessing", type: "process", icon: "play" }
);
node.Info("Starting analysis");
// ... processing ...
node.Complete({ status: "done" });
return { messages };
}
```
## Error Handling
```typescript
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
try {
if (payload.error) {
ctx.trace.Error(payload.error);
return {
data: { status: "error", message: "Something went wrong" }
};
}
// ... normal processing
} catch (e) {
ctx.trace.Error(e.message);
return { data: { status: "error", message: e.message } };
}
}
```
## Multi-Agent Orchestration
```typescript
// Main agent delegates based on intent
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { tools } = payload;
// Route based on tool result
const intent = tools?.[0]?.result?.intent;
const agentMap = {
"search": "search.agent",
"calculate": "calc.agent",
"translate": "translate.agent"
};
if (intent && agentMap[intent]) {
return {
delegate: {
agent_id: agentMap[intent],
messages: payload.messages
}
};
}
return null;
}
```
## Complete Example
```typescript
import { agent } from "@yao/runtime";
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const query = messages[messages.length - 1]?.content || "";
// Store for Next hook
ctx.memory.context.Set("query", query);
ctx.memory.context.Set("start", Date.now());
// Add trace
ctx.trace.Add({ query }, { label: "Create", type: "hook" });
// Check if needs special handling
if (query.toLowerCase().includes("urgent")) {
return {
messages,
temperature: 0,
prompt_preset: "task"
};
}
return { messages };
}
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { completion, tools, error } = payload;
const start = ctx.memory.context.Get("start");
const duration = Date.now() - start;
ctx.trace.Add(
{ duration },
{ label: "Next", type: "hook" }
).Complete();
if (error) {
return { data: { status: "error", error } };
}
if (tools?.length > 0) {
return {
data: {
status: "success",
response: completion?.content,
tools: tools.map(t => ({ name: t.tool, result: t.result })),
duration_ms: duration
}
};
}
return null;
}
```

199
agent/docs/i18n.md Normal file
View file

@ -0,0 +1,199 @@
# Internationalization (i18n)
## Locale Files
Create `locales/` directory in the assistant:
```
assistants/my-assistant/
└── locales/
├── en-us.yml
├── zh-cn.yml
└── ja.yml
```
## Locale File Format
```yaml
# locales/en-us.yml
name: My Assistant
description: A helpful AI assistant
chat:
title: New Chat
description: How can I help you today?
prompts:
- What can you do?
- Help me with a task
- Tell me about yourself
messages:
welcome: Welcome back!
error: Something went wrong
processing: Processing your request...
```
```yaml
# locales/zh-cn.yml
name: 我的助手
description: 一个有帮助的AI助手
chat:
title: 新对话
description: 今天我能帮您什么?
prompts:
- 你能做什么?
- 帮我完成一个任务
- 介绍一下你自己
messages:
welcome: 欢迎回来!
error: 出了点问题
processing: 正在处理您的请求...
```
## Using Translations
### In package.yao
Use `{{ key }}` syntax:
```json
{
"name": "{{ name }}",
"description": "{{ description }}",
"placeholder": {
"title": "{{ chat.title }}",
"description": "{{ chat.description }}",
"prompts": [
"{{ chat.prompts.0 }}",
"{{ chat.prompts.1 }}",
"{{ chat.prompts.2 }}"
]
}
}
```
### In Prompts
```yaml
- role: system
content: |
You are {{ name }}.
{{ description }}
Respond in the user's language.
```
## Locale Detection
The system detects locale from:
1. Request header `Accept-Language`
2. User preference (stored in memory)
3. Default: `en-us`
### Override in Hook
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Get user preference
const userLocale = ctx.memory.user.Get("preferred_locale");
return {
messages,
locale: userLocale || "en-us"
};
}
```
## Global Translations
Define global translations in `agent/locales/`:
```
agent/
└── locales/
├── en-us.yml
└── zh-cn.yml
```
These are available to all assistants via the `__global__` namespace.
## Accessing Translations in Hooks
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const locale = ctx.locale; // e.g., "en-us"
// Use locale for custom logic
if (locale.startsWith("zh")) {
return {
messages,
prompt_preset: "chinese"
};
}
return { messages };
}
```
## Nested Keys
Access nested values with dot notation:
```yaml
# locales/en-us.yml
errors:
validation:
required: This field is required
invalid: Invalid value
network:
timeout: Connection timed out
```
```json
{
"placeholder": {
"title": "{{ errors.validation.required }}"
}
}
```
## Fallback Behavior
If a translation key is not found:
1. Try the requested locale
2. Fall back to `en-us`
3. Return the key itself if not found
## Dynamic Locale Content
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Add locale-specific system message
const localeGreeting = {
"en-us": "Hello! How can I help you?",
"zh-cn": "你好!有什么可以帮您的?",
"ja": "こんにちは!何かお手伝いできますか?"
};
const greeting = localeGreeting[ctx.locale] || localeGreeting["en-us"];
return {
messages: [
{ role: "system", content: `Greeting: ${greeting}` },
...messages
]
};
}
```
## Best Practices
1. **Keep keys consistent** - Use the same keys across all locale files
2. **Use nested structure** - Organize related translations together
3. **Provide fallbacks** - Always have `en-us` as the base locale
4. **Test all locales** - Verify translations render correctly
5. **Use context variables** - Combine with `$CTX.locale` in prompts

268
agent/docs/mcp.md Normal file
View file

@ -0,0 +1,268 @@
# MCP Integration
Model Context Protocol (MCP) enables tool integration with external services.
## Defining MCP Servers
Create `mcps/tools.mcp.yao` in the assistant directory:
```json
{
"name": "Tools",
"description": "Custom tools for the assistant",
"transport": "process",
"process": {
"command": "node",
"args": ["server.js"]
}
}
```
### Transport Types
**Process (Local)**
```json
{
"transport": "process",
"process": {
"command": "python",
"args": ["mcp_server.py"],
"env": { "API_KEY": "$ENV.API_KEY" }
}
}
```
**HTTP**
```json
{
"transport": "http",
"http": {
"url": "https://mcp.example.com",
"headers": { "Authorization": "Bearer $ENV.TOKEN" }
}
}
```
**SSE (Server-Sent Events)**
```json
{
"transport": "sse",
"sse": {
"url": "https://mcp.example.com/events"
}
}
```
## Configuring in package.yao
### All Tools
```json
{
"mcp": {
"servers": ["tools"]
}
}
```
### Specific Tools
```json
{
"mcp": {
"servers": [
{ "server_id": "tools", "tools": ["search", "calculate"] }
]
}
}
```
### With Resources
```json
{
"mcp": {
"servers": [
{
"server_id": "data",
"tools": ["query"],
"resources": ["data://users/*"]
}
]
}
}
```
## Dynamic Configuration in Hooks
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
mcp_servers: [
{ server_id: "tools", tools: ["search"] },
{ server_id: "data", resources: ["data://reports"] }
]
};
}
```
## Using MCP in Hooks
### List Available Tools
```typescript
const tools = ctx.mcp.ListTools("server-id");
// { tools: [{ name: "search", description: "...", inputSchema: {...} }] }
```
### Call Tool
```typescript
const result = ctx.mcp.CallTool("server-id", "search", {
query: "example",
limit: 10
});
// { content: [{ type: "text", text: "..." }] }
```
### Batch Tool Calls
```typescript
// Sequential
const results = ctx.mcp.CallTools("server-id", [
{ name: "step1", arguments: { input: "a" } },
{ name: "step2", arguments: { input: "b" } }
]);
// Parallel
const results = ctx.mcp.CallToolsParallel("server-id", [
{ name: "api1", arguments: {} },
{ name: "api2", arguments: {} }
]);
```
### Read Resources
```typescript
const resources = ctx.mcp.ListResources("server-id");
const data = ctx.mcp.ReadResource("server-id", "data://users/123");
```
### Get Prompts
```typescript
const prompts = ctx.mcp.ListPrompts("server-id");
const prompt = ctx.mcp.GetPrompt("server-id", "system", { role: "helper" });
```
## Tool Mapping
Map MCP tools to Yao processes:
```
mcps/
└── mapping/
└── tools/
├── search.yao
└── calculate.yao
```
**mapping/tools/search.yao**
```json
{
"process": "scripts.search.Execute",
"args": ["{{input}}"]
}
```
## Error Handling
```typescript
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { tools } = payload;
if (tools) {
for (const tool of tools) {
if (tool.error) {
ctx.trace.Error(`Tool ${tool.tool} failed: ${tool.error}`);
// Handle error
} else {
// Process result
console.log(tool.result);
}
}
}
return null;
}
```
## Complete Example
**mcps/calculator.mcp.yao**
```json
{
"name": "Calculator",
"description": "Math operations",
"transport": "process",
"process": {
"command": "node",
"args": ["calc-server.js"]
}
}
```
**package.yao**
```json
{
"name": "Math Assistant",
"connector": "gpt-4o",
"mcp": {
"servers": [
{ "server_id": "agents.assistant.calculator", "tools": ["add", "multiply"] }
]
}
}
```
**src/index.ts**
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
// Check if calculation is needed
const query = messages[messages.length - 1]?.content || "";
if (/\d+\s*[\+\-\*\/]\s*\d+/.test(query)) {
// Enable calculator
return {
messages,
mcp_servers: [{ server_id: "agents.assistant.calculator" }]
};
}
return { messages };
}
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { tools } = payload;
if (tools?.length > 0) {
const calcResult = tools.find(t => t.server.includes("calculator"));
if (calcResult?.result) {
return {
data: {
answer: calcResult.result,
expression: calcResult.arguments
}
};
}
}
return null;
}
```

177
agent/docs/prompts.md Normal file
View file

@ -0,0 +1,177 @@
# Prompts
## Default Prompts
Create `prompts.yml` in the assistant directory:
```yaml
- role: system
content: |
You are a helpful assistant.
## Guidelines
- Be concise and accurate
- Ask clarifying questions when needed
- role: system
name: context
content: |
Current date: {{ $CTX.date }}
User locale: {{ $CTX.locale }}
```
### Prompt Structure
```yaml
- role: system | user | assistant
content: string
name: string (optional)
```
### Context Variables
Use `$CTX.*` for runtime context:
| Variable | Description |
| ---------------- | -------------------------- |
| `$CTX.date` | Current date |
| `$CTX.time` | Current time |
| `$CTX.locale` | User locale (e.g., en-us) |
| `$CTX.timezone` | User timezone |
| `$CTX.user_id` | Current user ID |
| `$CTX.team_id` | Current team ID |
| `$CTX.chat_id` | Current chat session ID |
## Prompt Presets
Create presets in `prompts/` directory for different scenarios:
```
prompts/
├── chat.yml # Casual conversation
├── task.yml # Task-oriented
└── analysis.yml # Data analysis
```
**prompts/chat.yml**
```yaml
- role: system
content: |
You are a friendly conversational assistant.
Be warm and engaging.
```
**prompts/task.yml**
```yaml
- role: system
content: |
You are a task-focused assistant.
Be precise and efficient.
```
### Using Presets
Select preset in Create hook:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
prompt_preset: "task", // Use prompts/task.yml
};
}
```
Or via mode configuration in `package.yao`:
```json
{
"modes": ["chat", "task"],
"default_mode": "task"
}
```
## Global Prompts
Define global prompts in `agent/prompts.yml` (applies to all assistants):
```yaml
- role: system
content: |
# Global Guidelines
- Always be helpful and respectful
- Follow company policies
```
### Disabling Global Prompts
Per assistant:
```json
{
"disable_global_prompts": true
}
```
Per request (in Create hook):
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
disable_global_prompts: true,
};
}
```
## Multi-line Content
Use YAML block scalars for long content:
```yaml
- role: system
content: |
# Assistant Role
You are an expert in data analysis.
## Capabilities
- Statistical analysis
- Data visualization
- Report generation
## Guidelines
1. Always validate input data
2. Explain your methodology
3. Provide actionable insights
```
## Dynamic Prompts
Inject dynamic content in Create hook:
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const userPrefs = ctx.memory.user.Get("preferences");
// Add dynamic system message
const dynamicPrompt = {
role: "system",
content: `User preferences: ${JSON.stringify(userPrefs)}`,
};
return {
messages: [dynamicPrompt, ...messages],
};
}
```
## Prompt Best Practices
1. **Be specific** - Clear instructions produce better results
2. **Use structure** - Headers, lists, and sections improve readability
3. **Set boundaries** - Define what the assistant should and shouldn't do
4. **Include examples** - Show expected input/output formats
5. **Layer prompts** - Use global + assistant + dynamic prompts together

283
agent/docs/search.md Normal file
View file

@ -0,0 +1,283 @@
# Search
The agent search system provides automatic search across web, knowledge base (KB), and database (DB).
## Auto Search Flow
1. **Intent Detection** - `__yao.needsearch` agent analyzes user message
2. **Search Execution** - Executes web/kb/db searches based on intent
3. **Context Injection** - Results injected as system message
4. **Citation** - LLM can cite results using `[1]`, `[2]` format
## Configuration
### Global (agent/search.yml)
```yaml
web:
provider: tavily # tavily, serper, serpapi
api_key_env: TAVILY_API_KEY
max_results: 10
kb:
threshold: 0.7
graph: true
db:
max_results: 20
keyword:
max_keywords: 5
language: en
citation:
format: "[{index}]"
auto_inject_prompt: true
```
### Per Assistant (package.yao)
```json
{
"search": {
"web": { "max_results": 5 },
"kb": { "threshold": 0.8 },
"citation": { "format": "[{index}]" }
},
"kb": {
"collections": ["docs", "faq"]
},
"db": {
"models": ["articles", "products"]
}
}
```
## Controlling Search in Hooks
### Disable Search
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
search: false
};
}
```
### Enable Specific Types
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
search: {
need_search: true,
search_types: ["kb", "db"], // Only KB and DB
confidence: 1.0,
reason: "controlled by hook"
}
};
}
```
### Disable via Uses
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
return {
messages,
uses: { search: "disabled" }
};
}
```
## Search API (ctx.search)
### Web Search
```typescript
const result = ctx.search.Web("query", {
limit: 10,
sites: ["example.com", "docs.example.com"],
time_range: "week", // day, week, month, year
rerank: { top_n: 5 }
});
```
### Knowledge Base Search
```typescript
const result = ctx.search.KB("query", {
collections: ["docs", "faq"],
threshold: 0.7,
limit: 10,
graph: true,
rerank: { top_n: 5 }
});
```
### Database Search
```typescript
const result = ctx.search.DB("query", {
models: ["articles"],
wheres: [{ column: "status", value: "published" }],
orders: [{ column: "created_at", option: "desc" }],
select: ["id", "title", "content"],
limit: 20,
rerank: { top_n: 10 }
});
```
### Parallel Search
```typescript
// Wait for all
const results = ctx.search.All([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic", collections: ["docs"] },
{ type: "db", query: "topic", models: ["articles"] }
]);
// First success with results
const results = ctx.search.Any([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
// First to complete
const results = ctx.search.Race([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
```
## Result Structure
```typescript
interface SearchResult {
type: "web" | "kb" | "db";
query: string;
source: "hook" | "auto" | "user";
items: SearchItem[];
error?: string;
}
interface SearchItem {
citation_id: string; // "1", "2", etc.
title: string;
url: string;
content: string;
score: number;
}
```
## Custom Search in Hooks
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const query = messages[messages.length - 1]?.content || "";
// Custom KB search
const kbResult = ctx.search.KB(query, {
collections: ["internal_docs"],
threshold: 0.8
});
if (kbResult.items?.length > 0) {
// Format results as context
const context = kbResult.items
.map((item, i) => `[${i + 1}] ${item.title}\n${item.content}`)
.join("\n\n");
// Inject as system message
const contextMsg = {
role: "system",
content: `Reference information:\n${context}`
};
return {
messages: [contextMsg, ...messages],
search: false // Skip auto search
};
}
return { messages };
}
```
## Authorization
### KB Collections
Collections are filtered by user authorization:
```typescript
// Only collections user has access to are searched
const result = ctx.search.KB("query", {
collections: ["public", "internal", "secret"]
// User without "secret" access won't search that collection
});
```
### DB Models
Database queries include permission filters:
```typescript
// Auth filters are automatically added
// e.g., { column: "__yao_created_by", value: user_id }
const result = ctx.search.DB("query", {
models: ["user_documents"]
});
```
## Web Search Providers
### Tavily
```yaml
web:
provider: tavily
api_key_env: TAVILY_API_KEY
```
### Serper
```yaml
web:
provider: serper
api_key_env: SERPER_API_KEY
```
### SerpAPI
```yaml
web:
provider: serpapi
api_key_env: SERPAPI_API_KEY
```
## Citation
LLM responses can include citations:
```
Based on the documentation [1], the feature works by... [2]
References:
[1] Getting Started Guide - https://docs.example.com/start
[2] API Reference - https://docs.example.com/api
```
### Citation Format
```yaml
citation:
format: "[{index}]" # or "({index})" or "[^{index}]"
auto_inject_prompt: true
custom_prompt: |
When citing sources, use the format [N] where N is the reference number.
```

367
agent/docs/testing.md Normal file
View file

@ -0,0 +1,367 @@
# Agent Testing
A comprehensive testing framework for Yao AI agents with support for standard testing, dynamic (simulator-driven) testing, agent-driven assertions, and CI integration.
## Quick Start
```bash
# Test with direct message (auto-detect agent from current directory)
cd assistants/my-assistant
yao agent test -i "Hello, how are you?"
# Test with JSONL file
yao agent test -i tests/inputs.jsonl
# Generate HTML report
yao agent test -i tests/inputs.jsonl -o report.html
# Stability analysis (run each test 5 times)
yao agent test -i tests/inputs.jsonl --runs 5
```
## Input Modes
The `-i` flag supports multiple input modes:
| Mode | Example | Description |
|------|---------|-------------|
| Direct message | `-i "Hello"` | Single message test |
| JSONL file | `-i tests/inputs.jsonl` | Multiple test cases |
| Agent-driven | `-i "agents:tests.generator?count=10"` | Generate tests with agent |
| Script test | `-i scripts.expense.setup` | Test handler scripts |
| Script-generated | `-i "scripts:tests.gen.Generate"` | Generate tests from script |
## Test Case Format (JSONL)
### Basic Test
```jsonl
{"id": "greeting", "input": "Hello", "assert": {"type": "contains", "value": "Hi"}}
```
### With Conversation History
```jsonl
{
"id": "multi-turn",
"input": [
{"role": "user", "content": "What's 2+2?"},
{"role": "assistant", "content": "4"},
{"role": "user", "content": "Multiply by 3"}
],
"assert": {"type": "contains", "value": "12"}
}
```
### With File Attachments
```jsonl
{
"id": "image-test",
"input": {
"role": "user",
"content": [
{"type": "text", "text": "Describe this image"},
{"type": "image", "source": "file://fixtures/test.jpg"}
]
}
}
```
## Assertions
### Static Assertions
| Type | Description | Example |
|------|-------------|---------|
| `equals` | Exact match | `{"type": "equals", "value": {"key": "val"}}` |
| `contains` | Output contains value | `{"type": "contains", "value": "keyword"}` |
| `not_contains` | Output does not contain | `{"type": "not_contains", "value": "error"}` |
| `regex` | Match regex pattern | `{"type": "regex", "value": "\\d+"}` |
| `json_path` | Extract and compare | `{"type": "json_path", "path": "$.field", "value": true}` |
| `type` | Check output type | `{"type": "type", "value": "object"}` |
| `tool_called` | Check tool was called | `{"type": "tool_called", "value": "setup"}` |
| `tool_result` | Check tool result | `{"type": "tool_result", "value": {"tool": "setup", "result": {"success": true}}}` |
### Agent-Driven Assertions
Use LLM to validate response semantics:
```jsonl
{
"id": "helpful-response",
"input": "How do I reset my password?",
"assert": {
"type": "agent",
"use": "agents:tests.validator-agent",
"value": "Response should provide clear step-by-step instructions"
}
}
```
### Multiple Assertions
All assertions must pass:
```jsonl
{
"id": "complete-check",
"input": "Submit expense",
"assert": [
{"type": "contains", "value": "expense"},
{"type": "not_contains", "value": "error"},
{"type": "regex", "value": "(?i)(submitted|created)"}
]
}
```
## Dynamic Mode (Simulator)
For testing complex conversation flows with a user simulator:
```jsonl
{
"id": "order-flow",
"input": "I want to order coffee",
"simulator": {
"use": "tests.simulator-agent",
"options": {
"metadata": {
"persona": "Customer",
"goal": "Order a medium latte"
}
}
},
"checkpoints": [
{
"id": "greeting",
"assert": {"type": "regex", "value": "(?i)(hello|hi)"}
},
{
"id": "ask-size",
"after": ["greeting"],
"assert": {"type": "regex", "value": "(?i)size"}
},
{
"id": "confirm",
"after": ["ask-size"],
"assert": {"type": "regex", "value": "(?i)confirm"}
}
],
"max_turns": 10
}
```
Run with:
```bash
yao agent test -i tests/dynamic.jsonl --simulator tests.simulator-agent -v
```
## Script Testing
Test agent handler scripts with the `t.assert` API:
```typescript
// assistants/my-assistant/src/setup_test.ts
import { SystemReady } from "./setup";
export function TestSystemReady(t: TestingT, ctx: Context) {
const result = SystemReady(ctx);
t.assert.True(result.success, "Should succeed");
t.assert.Equal(result.status, "ready", "Status should be ready");
t.assert.NotNil(result.data, "Data should not be nil");
}
export function TestWithAgentAssertion(t: TestingT, ctx: Context) {
const response = Process("agents.my-assistant.Stream", ctx, messages);
// Static assertion
t.assert.Contains(response.content, "confirm");
// Agent-driven assertion
t.assert.Agent(response.content, "tests.validator-agent", {
criteria: "Response should ask for confirmation"
});
}
```
Run with:
```bash
yao agent test -i scripts.my-assistant.setup -v
```
### Available Assertions
| Method | Description |
|--------|-------------|
| `t.assert.True(value, msg)` | Assert value is true |
| `t.assert.False(value, msg)` | Assert value is false |
| `t.assert.Equal(a, b, msg)` | Assert a equals b |
| `t.assert.NotEqual(a, b, msg)` | Assert a not equals b |
| `t.assert.Nil(value, msg)` | Assert value is null/undefined |
| `t.assert.NotNil(value, msg)` | Assert value is not nil |
| `t.assert.Contains(s, sub, msg)` | Assert string contains substr |
| `t.assert.Len(arr, n, msg)` | Assert array/string length |
| `t.assert.Agent(resp, id, opts)` | Agent-driven assertion |
## Before/After Hooks
### Per-Test Hooks
```jsonl
{
"id": "with-setup",
"input": "Show my data",
"before": "env_test.Before",
"after": "env_test.After"
}
```
### Global Hooks
```bash
yao agent test -i tests/inputs.jsonl --before env_test.BeforeAll --after env_test.AfterAll
```
### Hook Implementation
```typescript
// assistants/my-assistant/src/env_test.ts
export function Before(ctx: Context, testCase: TestCase): any {
const userId = Process("models.user.Create", { name: "Test User" });
return { userId }; // Passed to After
}
export function After(ctx: Context, testCase: TestCase, result: TestResult, beforeData: any) {
if (beforeData?.userId) {
Process("models.user.Delete", beforeData.userId);
}
}
export function BeforeAll(ctx: Context, testCases: TestCase[]): any {
Process("models.migrate");
return { initialized: true };
}
export function AfterAll(ctx: Context, results: TestResult[], beforeData: any) {
const passed = results.filter(r => r.status === "passed").length;
console.log(`Tests completed: ${passed}/${results.length} passed`);
}
```
## Custom Context
Create a JSON file for custom authorization:
```json
{
"chat_id": "test-chat-001",
"authorized": {
"user_id": "test-user-123",
"team_id": "test-team-456",
"constraints": {
"owner_only": true,
"extra": { "department": "engineering" }
}
}
}
```
Use with `--ctx`:
```bash
yao agent test -i scripts.my-assistant.setup --ctx tests/context.json -v
```
## Command Line Options
| Flag | Description | Default |
|------|-------------|---------|
| `-i` | Input: JSONL file, message, `agents:xxx`, or `scripts:xxx` | (required) |
| `-o` | Output file path | `output-{timestamp}.jsonl` |
| `-n` | Agent ID (optional, auto-detected) | auto-detect |
| `-a` | Application directory | auto-detect |
| `-e` | Environment file | - |
| `-c` | Override connector | agent default |
| `-u` | Test user ID | `test-user` |
| `-t` | Test team ID | `test-team` |
| `-r` | Reporter agent ID | built-in |
| `-v` | Verbose output | false |
| `--ctx` | Path to context JSON file | - |
| `--simulator` | Default simulator agent ID | - |
| `--before` | Global BeforeAll hook | - |
| `--after` | Global AfterAll hook | - |
| `--runs` | Runs per test (stability analysis) | 1 |
| `--run` | Regex pattern to filter tests | - |
| `--timeout` | Timeout per test | 2m |
| `--parallel` | Parallel test cases | 1 |
| `--fail-fast` | Stop on first failure | false |
| `--dry-run` | Generate tests without running | false |
## Output Formats
Determined by `-o` file extension:
| Extension | Format | Description |
|-----------|--------|-------------|
| `.jsonl` | JSONL | Streaming (default) |
| `.json` | JSON | Complete structured |
| `.md` | Markdown | Human-readable |
| `.html` | HTML | Interactive web report |
## Stability Analysis
Run each test multiple times to measure consistency:
```bash
yao agent test -i tests/inputs.jsonl --runs 5 -o stability.json
```
| Pass Rate | Classification |
|-----------|----------------|
| 100% | Stable |
| 80-99% | Mostly Stable |
| 50-79% | Unstable |
| < 50% | Highly Unstable |
## CI Integration
```bash
# Exit code: 0 = all passed, 1 = failures
yao agent test -i tests/inputs.jsonl --fail-fast
# Run with parallel execution
yao agent test -i tests/inputs.jsonl --parallel 4
```
### GitHub Actions Example
```yaml
- name: Run Agent Tests
run: |
yao agent test -i assistants/my-assistant/tests/inputs.jsonl \
-u ci-user -t ci-team \
--runs 3 \
-o report.json
- name: Run Dynamic Tests
run: |
yao agent test -i assistants/my-assistant/tests/dynamic.jsonl \
--simulator tests.simulator-agent \
-v
- name: Run Script Tests
run: |
yao agent test -i scripts.my-assistant.setup -v
```
## Exit Codes
| Code | Description |
|------|-------------|
| 0 | All tests passed |
| 1 | Tests failed, configuration error, or runtime error |

View file

@ -100,4 +100,4 @@ See [Agent SUI Documentation](docs/agent-sui.md) for details.
## License
Apache-2.0
This project is part of the Yao App Engine and follows the [Yao Open Source License](../LICENSE).