Refactor TestSubscribeFrom for improved timing accuracy and reliability
- Adjusted sleep durations to 1100ms to enhance test stability in CI environments. - Modified timestamp calculations to ensure millisecond precision, improving event timing accuracy in tests.
This commit is contained in:
parent
edf921324b
commit
63c91d7f28
1 changed files with 937 additions and 0 deletions
937
agent/search/DESIGN.md
Normal file
937
agent/search/DESIGN.md
Normal file
|
|
@ -0,0 +1,937 @@
|
||||||
|
# Search Module Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Search module provides a unified RAG (Retrieval-Augmented Generation) interface for Yao Agent, supporting three search types:
|
||||||
|
|
||||||
|
| Type | Source | Use Case |
|
||||||
|
| ----- | -------------- | ---------------------------------------------------- |
|
||||||
|
| `web` | Internet | Real-time information, news, external knowledge |
|
||||||
|
| `kb` | Knowledge Base | Documents, FAQs, internal knowledge (vector + graph) |
|
||||||
|
| `db` | Database | Structured data from Yao Models (QueryDSL) |
|
||||||
|
|
||||||
|
The module follows the **Handler + Registry** pattern consistent with the `content` module, and exposes JSAPI for flexible usage in Create/Next hooks.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- **Unified JSAPI**: `ctx.search.Web()`, `ctx.search.KB()`, `ctx.search.DB()`, `ctx.search.Parallel()`
|
||||||
|
- **Citation System**: Auto-generate citation IDs (`#ref:xxx`) for LLM reference
|
||||||
|
- **Real-time Output**: Stream search progress to client
|
||||||
|
- **Trace Integration**: Report search operations to user for transparency
|
||||||
|
- **Reranking**: Score, Model, Agent, or MCP-based result reranking
|
||||||
|
- **Graceful Degradation**: Search errors don't block agent flow
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In Create hook (assistants/my-assistant/index.ts)
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
// Simple web search
|
||||||
|
const result = ctx.search.Web(query, { limit: 5 });
|
||||||
|
|
||||||
|
// Or parallel search across all sources
|
||||||
|
const [web, kb, db] = ctx.search.Parallel([
|
||||||
|
{ type: "web", query, limit: 5 },
|
||||||
|
{ type: "kb", query, collections: ["docs"] },
|
||||||
|
{ type: "db", query, models: ["product"] },
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [{ role: "system", content: formatContext(web, kb, db) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. **Unified Interface**: Single API for web, knowledge base, and database search
|
||||||
|
2. **Flexibility**: Support built-in handlers and external tools (MCP/Agent delegation)
|
||||||
|
3. **JSAPI Support**: Enable search calls from Create/Next hooks via JavaScript
|
||||||
|
4. **Parallel Execution**: Support concurrent web + KB + DB searches
|
||||||
|
5. **Graceful Degradation**: Search failures should not block the main agent flow
|
||||||
|
6. **Real-time Feedback**: Stream search progress and results to users via output
|
||||||
|
7. **Traceability**: Report search operations to users for transparency
|
||||||
|
8. **Citation Support**: Enable LLM to reference search results with trackable citations
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Search Flow Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Stream Start] --> B{Options.Search?}
|
||||||
|
B -->|false| C[Skip Search]
|
||||||
|
B -->|true/nil| D{Hook Handled?}
|
||||||
|
D -->|Yes| C
|
||||||
|
D -->|No| E[Auto Search]
|
||||||
|
|
||||||
|
E --> F{Check Assistant Config}
|
||||||
|
F --> G[Web Search]
|
||||||
|
F --> H[KB Search]
|
||||||
|
F --> I[DB Search]
|
||||||
|
|
||||||
|
G --> J[Parallel Execute]
|
||||||
|
H --> J
|
||||||
|
I --> J
|
||||||
|
|
||||||
|
J --> K[Merge Results]
|
||||||
|
K --> L[Rerank]
|
||||||
|
L --> M[Generate Citations]
|
||||||
|
M --> N[Inject to System Prompt]
|
||||||
|
|
||||||
|
C --> O[LLM Call]
|
||||||
|
N --> O
|
||||||
|
O --> P[Output with Citations]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration in Stream()
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client
|
||||||
|
participant Stream
|
||||||
|
participant CreateHook
|
||||||
|
participant Search
|
||||||
|
participant LLM
|
||||||
|
participant Output
|
||||||
|
|
||||||
|
Client->>Stream: Stream(ctx, messages, options)
|
||||||
|
Stream->>Stream: Initialize
|
||||||
|
|
||||||
|
alt Has Create Hook
|
||||||
|
Stream->>CreateHook: Create(ctx, messages, options)
|
||||||
|
CreateHook-->>Stream: response (may include search results)
|
||||||
|
end
|
||||||
|
|
||||||
|
alt Options.Search != false AND not handled by Hook
|
||||||
|
Stream->>Search: AutoSearch(ctx, messages)
|
||||||
|
Search->>Search: Web/KB/DB in parallel
|
||||||
|
Search->>Search: Rerank & Citations
|
||||||
|
Search->>Output: search_start, search_result, search_complete
|
||||||
|
Search-->>Stream: Inject search context to messages
|
||||||
|
end
|
||||||
|
|
||||||
|
Stream->>LLM: Execute with search context
|
||||||
|
LLM->>Output: Stream response with #ref:xxx
|
||||||
|
Stream-->>Client: Complete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
agent/search/
|
||||||
|
├── DESIGN.md # This document
|
||||||
|
├── interfaces.go # Core interfaces (Handler, Searcher)
|
||||||
|
├── types.go # Type definitions (Request, Result, Citation, etc.)
|
||||||
|
├── registry.go # Handler registry
|
||||||
|
├── search.go # Main search logic and utilities
|
||||||
|
├── jsapi.go # JavaScript API bindings for hooks
|
||||||
|
├── trace.go # Trace node creation and management
|
||||||
|
├── output.go # Real-time output/streaming to client
|
||||||
|
├── citation.go # Citation ID generation and tracking
|
||||||
|
├── rerank/ # Result reranking
|
||||||
|
│ ├── interfaces.go # Reranker interface
|
||||||
|
│ ├── score.go # Score-based reranking (default)
|
||||||
|
│ ├── model.go # Model-based reranking (Cohere, etc.)
|
||||||
|
│ ├── agent.go # Agent-based reranking (delegate to another assistant)
|
||||||
|
│ └── mcp.go # MCP-based reranking (call MCP server tool)
|
||||||
|
├── query/ # Query processing
|
||||||
|
│ ├── interfaces.go # Query processor interface
|
||||||
|
│ ├── keyword.go # Keyword extraction for web search
|
||||||
|
│ ├── embedding.go # Embedding generation for KB search
|
||||||
|
│ └── dsl.go # Query DSL generation for DB search
|
||||||
|
├── web/ # Web search implementations
|
||||||
|
│ ├── handler.go # Web search handler
|
||||||
|
│ └── providers/ # Provider implementations
|
||||||
|
│ ├── tavily.go
|
||||||
|
│ └── serper.go
|
||||||
|
├── kb/ # Knowledge base search
|
||||||
|
│ ├── handler.go # KB search handler
|
||||||
|
│ ├── vector.go # Vector similarity search
|
||||||
|
│ └── graph.go # Graph-based association (GraphRAG)
|
||||||
|
└── db/ # Database search (Yao Model/QueryDSL)
|
||||||
|
├── handler.go # DB search handler
|
||||||
|
├── query.go # QueryDSL builder
|
||||||
|
└── schema.go # Model schema introspection
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Interfaces
|
||||||
|
|
||||||
|
### Handler Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Handler defines the interface for search implementations
|
||||||
|
type Handler interface {
|
||||||
|
// Type returns the search type this handler supports
|
||||||
|
Type() SearchType
|
||||||
|
|
||||||
|
// CanHandle checks if this handler can process the given request
|
||||||
|
CanHandle(ctx *context.Context, req *Request) bool
|
||||||
|
|
||||||
|
// Search executes the search and returns results
|
||||||
|
Search(ctx *context.Context, req *Request) (*Result, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Searcher Interface (Public API)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Searcher is the main interface exposed to external callers
|
||||||
|
type Searcher interface {
|
||||||
|
// Search executes a single search request
|
||||||
|
Search(ctx *context.Context, req *Request) (*Result, error)
|
||||||
|
|
||||||
|
// SearchMultiple executes multiple searches (potentially in parallel)
|
||||||
|
SearchMultiple(ctx *context.Context, reqs []*Request) ([]*Result, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### QueryProcessor Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
// QueryProcessor prepares queries for different search types
|
||||||
|
type QueryProcessor interface {
|
||||||
|
// ExtractKeywords extracts search keywords from user message (for web search)
|
||||||
|
ExtractKeywords(ctx *context.Context, content string) ([]string, error)
|
||||||
|
|
||||||
|
// Embed generates vector embedding for query (for KB search)
|
||||||
|
Embed(ctx *context.Context, content string, collection string) ([]float32, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reranker Interface
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Reranker reorders search results by relevance
|
||||||
|
type Reranker interface {
|
||||||
|
// Rerank reorders results based on query relevance
|
||||||
|
Rerank(ctx *context.Context, query string, items []*ResultItem) ([]*ResultItem, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
### SearchType
|
||||||
|
|
||||||
|
```go
|
||||||
|
type SearchType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SearchTypeWeb SearchType = "web" // Web/Internet search
|
||||||
|
SearchTypeKB SearchType = "kb" // Knowledge base vector search
|
||||||
|
SearchTypeDB SearchType = "db" // Database search (Yao Model/QueryDSL)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### RerankerType
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RerankerType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RerankerTypeScore RerankerType = "score" // Simple score-based sorting (default)
|
||||||
|
RerankerTypeModel RerankerType = "model" // Model-based reranking (Cohere, BGE, etc.)
|
||||||
|
RerankerTypeAgent RerankerType = "agent" // Agent-based reranking (delegate to assistant)
|
||||||
|
RerankerTypeMCP RerankerType = "mcp" // MCP-based reranking (call MCP server tool)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Request struct {
|
||||||
|
// Common fields
|
||||||
|
Query string `json:"query"` // Search query (natural language)
|
||||||
|
Type SearchType `json:"type"` // Search type: "web", "kb", or "db"
|
||||||
|
Limit int `json:"limit,omitempty"` // Max results (default: 10)
|
||||||
|
|
||||||
|
// Web search specific
|
||||||
|
Sites []string `json:"sites,omitempty"` // Restrict to specific sites
|
||||||
|
TimeRange string `json:"time_range,omitempty"` // "day", "week", "month", "year"
|
||||||
|
|
||||||
|
// Knowledge base specific
|
||||||
|
Collections []string `json:"collections,omitempty"` // KB collection IDs
|
||||||
|
Threshold float64 `json:"threshold,omitempty"` // Similarity threshold (0-1)
|
||||||
|
Graph bool `json:"graph,omitempty"` // Enable graph association
|
||||||
|
|
||||||
|
// Database search specific
|
||||||
|
Models []string `json:"models,omitempty"` // Model IDs (e.g., "user", "agents.mybot.product")
|
||||||
|
Wheres []QueryWhere `json:"wheres,omitempty"` // Pre-defined filters (optional)
|
||||||
|
Orders []QueryOrder `json:"orders,omitempty"` // Sort orders (optional)
|
||||||
|
Select []string `json:"select,omitempty"` // Fields to return (optional)
|
||||||
|
|
||||||
|
// Reranking
|
||||||
|
Rerank *RerankOptions `json:"rerank,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryWhere represents a filter condition for DB search
|
||||||
|
type QueryWhere struct {
|
||||||
|
Field string `json:"field"` // Field name
|
||||||
|
Op string `json:"op,omitempty"` // Operator: "=", "like", ">", "<", "in", etc. (default: "=")
|
||||||
|
Value interface{} `json:"value"` // Filter value
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryOrder represents a sort order for DB search
|
||||||
|
type QueryOrder struct {
|
||||||
|
Field string `json:"field"` // Field name
|
||||||
|
Order string `json:"order,omitempty"` // "asc" or "desc" (default: "desc")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RerankOptions
|
||||||
|
|
||||||
|
```go
|
||||||
|
// RerankOptions controls result reranking
|
||||||
|
type RerankOptions struct {
|
||||||
|
Type string `json:"type,omitempty"` // "score", "model", "agent", "mcp"
|
||||||
|
Model string `json:"model,omitempty"` // Model ID (for type="model")
|
||||||
|
Agent string `json:"agent,omitempty"` // Agent ID (for type="agent")
|
||||||
|
MCP string `json:"mcp,omitempty"` // MCP server ID (for type="mcp")
|
||||||
|
TopK int `json:"top_k,omitempty"` // Return top K after reranking
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Result
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Result struct {
|
||||||
|
Type SearchType `json:"type"` // Search type
|
||||||
|
Query string `json:"query"` // Original query
|
||||||
|
Items []*ResultItem `json:"items"` // Result items
|
||||||
|
Total int `json:"total"` // Total matches
|
||||||
|
Duration int64 `json:"duration_ms"` // Search duration in ms
|
||||||
|
Error string `json:"error,omitempty"` // Error message if failed
|
||||||
|
|
||||||
|
// Graph associations (KB only, if enabled)
|
||||||
|
GraphNodes []*GraphNode `json:"graph_nodes,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ResultItem
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ResultItem struct {
|
||||||
|
// Citation
|
||||||
|
CitationID string `json:"citation_id"` // Unique ID for LLM reference: "#ref:xxx"
|
||||||
|
|
||||||
|
// Common fields
|
||||||
|
Title string `json:"title,omitempty"` // Title/headline
|
||||||
|
Content string `json:"content"` // Main content/snippet
|
||||||
|
URL string `json:"url,omitempty"` // Source URL
|
||||||
|
Score float64 `json:"score,omitempty"` // Relevance score (0-1)
|
||||||
|
|
||||||
|
// KB specific
|
||||||
|
DocumentID string `json:"document_id,omitempty"` // Source document ID
|
||||||
|
Collection string `json:"collection,omitempty"` // Collection name
|
||||||
|
|
||||||
|
// DB specific
|
||||||
|
Model string `json:"model,omitempty"` // Model ID
|
||||||
|
RecordID interface{} `json:"record_id,omitempty"` // Record primary key
|
||||||
|
Data map[string]interface{} `json:"data,omitempty"` // Full record data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GraphNode
|
||||||
|
|
||||||
|
```go
|
||||||
|
// GraphNode represents a related entity from knowledge graph
|
||||||
|
type GraphNode struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"` // Entity type
|
||||||
|
Name string `json:"name"` // Entity name
|
||||||
|
Description string `json:"description,omitempty"` // Entity description
|
||||||
|
Relation string `json:"relation,omitempty"` // Relationship to query
|
||||||
|
Score float64 `json:"score,omitempty"` // Relevance score
|
||||||
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Citation System
|
||||||
|
|
||||||
|
Each search result has a unique `CitationID` for LLM reference.
|
||||||
|
|
||||||
|
### Citation Config
|
||||||
|
|
||||||
|
```go
|
||||||
|
type CitationConfig struct {
|
||||||
|
Format string `json:"format,omitempty"` // Default: "#ref:{id}"
|
||||||
|
AutoInjectPrompt bool `json:"auto_inject_prompt,omitempty"` // Auto-add instructions to system prompt
|
||||||
|
CustomPrompt string `json:"custom_prompt,omitempty"` // Override default prompt template
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default Citation Prompt
|
||||||
|
|
||||||
|
When `AutoInjectPrompt` is enabled (default), the system prompt includes:
|
||||||
|
|
||||||
|
```
|
||||||
|
When citing search results, use #ref:{id} format inline.
|
||||||
|
Example: "According to studies #ref:a1b2, this is significant."
|
||||||
|
|
||||||
|
Available references:
|
||||||
|
- #ref:a1b2 - Title of source 1
|
||||||
|
- #ref:c3d4 - Title of source 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Prompt in Config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# assistants/my-assistant.yml
|
||||||
|
search:
|
||||||
|
citation:
|
||||||
|
format: "[{id}]"
|
||||||
|
auto_inject_prompt: true
|
||||||
|
custom_prompt: "Cite using [{id}]. Sources: ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trace Integration
|
||||||
|
|
||||||
|
Search operations create trace nodes to report execution details to users, providing transparency about what the agent is doing.
|
||||||
|
|
||||||
|
### Trace Node Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
search (type: "search")
|
||||||
|
├── query // Original query
|
||||||
|
├── search_type // "web", "kb", or "db"
|
||||||
|
├── duration_ms
|
||||||
|
├── status // "success", "failed"
|
||||||
|
├── result_count
|
||||||
|
└── children // Sub-operations
|
||||||
|
├── embedding (kb only)
|
||||||
|
├── vector_search (kb only)
|
||||||
|
├── graph_search (kb, if enabled)
|
||||||
|
├── dsl_build (db only)
|
||||||
|
├── db_query (db only)
|
||||||
|
└── rerank (if enabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-time Output
|
||||||
|
|
||||||
|
Search progress is streamed to the client via the output system.
|
||||||
|
|
||||||
|
### Output Message Types
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
TypeSearchStart = "search_start" // Search initiated
|
||||||
|
TypeSearchResult = "search_result" // Result item (streamed)
|
||||||
|
TypeSearchComplete = "search_complete" // Search completed
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Display Example
|
||||||
|
|
||||||
|
```
|
||||||
|
🔍 Searching "latest AI developments"...
|
||||||
|
|
||||||
|
📄 Found 5 results:
|
||||||
|
1. #ref:a1b2 - OpenAI Announces GPT-5
|
||||||
|
2. #ref:c3d4 - Google's New AI Model
|
||||||
|
...
|
||||||
|
|
||||||
|
✅ Search complete (1.2s)
|
||||||
|
```
|
||||||
|
|
||||||
|
## JSAPI Integration
|
||||||
|
|
||||||
|
The Search module is exposed via `ctx.search` object in hook scripts.
|
||||||
|
|
||||||
|
### API Methods
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In hook scripts (index.ts)
|
||||||
|
|
||||||
|
// Web search
|
||||||
|
ctx.search.Web(query: string, options?: WebOptions): Result
|
||||||
|
|
||||||
|
// Knowledge base search
|
||||||
|
ctx.search.KB(query: string, options?: KBOptions): Result
|
||||||
|
|
||||||
|
// Database search (Yao Model/QueryDSL)
|
||||||
|
ctx.search.DB(query: string, options?: DBOptions): Result
|
||||||
|
|
||||||
|
// Parallel search (multiple types)
|
||||||
|
ctx.search.Parallel(requests: Request[]): Result[]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface WebOptions {
|
||||||
|
limit?: number; // Max results (default: 10)
|
||||||
|
sites?: string[]; // Restrict to sites
|
||||||
|
timeRange?: string; // "day", "week", "month", "year"
|
||||||
|
rerank?: RerankOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KBOptions {
|
||||||
|
collections?: string[]; // Collection IDs
|
||||||
|
threshold?: number; // Similarity threshold (0-1)
|
||||||
|
limit?: number; // Max results
|
||||||
|
graph?: boolean; // Enable graph association
|
||||||
|
rerank?: RerankOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DBOptions {
|
||||||
|
models?: string[]; // Model IDs (default: use assistant's db.models)
|
||||||
|
wheres?: QueryWhere[]; // Pre-defined filters
|
||||||
|
orders?: QueryOrder[]; // Sort orders
|
||||||
|
select?: string[]; // Fields to return
|
||||||
|
limit?: number; // Max results (default: 10)
|
||||||
|
rerank?: RerankOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryWhere {
|
||||||
|
field: string;
|
||||||
|
op?: string; // "=", "like", ">", "<", "in", etc.
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryOrder {
|
||||||
|
field: string;
|
||||||
|
order?: string; // "asc" or "desc"
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RerankOptions {
|
||||||
|
type?: string; // "score", "model", "agent", "mcp"
|
||||||
|
model?: string; // Model ID (for type="model")
|
||||||
|
agent?: string; // Agent ID (for type="agent")
|
||||||
|
mcp?: string; // MCP server ID (for type="mcp")
|
||||||
|
topK?: number; // Return top K
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
#### Example 1: Web Search
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
const result = ctx.search.Web(query, {
|
||||||
|
limit: 5,
|
||||||
|
timeRange: "week",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.items.length > 0) {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: formatSearchContext(result),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: [] };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 2: Knowledge Base Search with Graph
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
const result = ctx.search.KB(query, {
|
||||||
|
collections: ["docs", "faq"],
|
||||||
|
threshold: 0.7,
|
||||||
|
limit: 10,
|
||||||
|
graph: true, // Enable graph association
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.items.length > 0) {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: formatKBContext(result),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: [] };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 3: Database Search
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
// Search in assistant's models (uses db.models from assistant config)
|
||||||
|
const result = ctx.search.DB(query, {
|
||||||
|
models: ["product", "agents.mybot.order"], // Optional: override models
|
||||||
|
wheres: [{ field: "status", value: "active" }], // Pre-filter
|
||||||
|
limit: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.items.length > 0) {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: formatDBContext(result),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: [] };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 4: Parallel Web + KB + DB Search
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
// Execute web, KB, and DB search in parallel
|
||||||
|
const [webResult, kbResult, dbResult] = ctx.search.Parallel([
|
||||||
|
{ type: "web", query: query, limit: 5 },
|
||||||
|
{ type: "kb", query: query, collections: ["docs"], limit: 10 },
|
||||||
|
{ type: "db", query: query, models: ["product"], limit: 10 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Merge results
|
||||||
|
const context = mergeSearchResults(webResult, kbResult, dbResult);
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: context,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 5: Custom Citation Format
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
const result = ctx.search.Web(query, { limit: 5 });
|
||||||
|
|
||||||
|
// Build custom citation prompt
|
||||||
|
const refs = result.items
|
||||||
|
.map((item, i) => `[${i + 1}] ${item.title} - ${item.url}`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: `Use [N] to cite. References:\n${refs}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// Override citation config
|
||||||
|
citation: { autoInjectPrompt: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Assistant Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# assistants/my-assistant.yml
|
||||||
|
assistant_id: my-assistant
|
||||||
|
connector: openai
|
||||||
|
|
||||||
|
search:
|
||||||
|
web_search: true
|
||||||
|
knowledge: true
|
||||||
|
database: true
|
||||||
|
|
||||||
|
web:
|
||||||
|
provider: tavily # "tavily", "serper", "mcp:server-id"
|
||||||
|
max_results: 5
|
||||||
|
|
||||||
|
kb:
|
||||||
|
collections: [docs, faq]
|
||||||
|
threshold: 0.7
|
||||||
|
graph: true
|
||||||
|
|
||||||
|
db:
|
||||||
|
models: [product, order] # Use assistant's db.models if not specified
|
||||||
|
max_results: 20
|
||||||
|
|
||||||
|
rerank:
|
||||||
|
type: score # "score", "model", "agent", "mcp"
|
||||||
|
|
||||||
|
citation:
|
||||||
|
format: "#ref:{id}"
|
||||||
|
auto_inject_prompt: true
|
||||||
|
|
||||||
|
# Knowledge base collections
|
||||||
|
kb:
|
||||||
|
collections: [docs, faq]
|
||||||
|
|
||||||
|
# Database models (also supports assistant-specific models in models/ directory)
|
||||||
|
db:
|
||||||
|
models: [product, order, customer]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Global Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# config/search.yml
|
||||||
|
search:
|
||||||
|
web:
|
||||||
|
provider: tavily
|
||||||
|
api_key_env: TAVILY_API_KEY
|
||||||
|
|
||||||
|
rerank:
|
||||||
|
type: score
|
||||||
|
|
||||||
|
citation:
|
||||||
|
format: "#ref:{id}"
|
||||||
|
auto_inject_prompt: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Flow
|
||||||
|
|
||||||
|
### Search Flow
|
||||||
|
|
||||||
|
## Execution Modes
|
||||||
|
|
||||||
|
### Stream() Execution with Search
|
||||||
|
|
||||||
|
```
|
||||||
|
Stream(ctx, messages, options)
|
||||||
|
│
|
||||||
|
├── 1. Initialize
|
||||||
|
│
|
||||||
|
├── 2. Create Hook (optional)
|
||||||
|
│ └── Can call ctx.search.* and return search results
|
||||||
|
│
|
||||||
|
├── 3. Auto Search Decision
|
||||||
|
│ ├── IF Options.Search == false → SKIP
|
||||||
|
│ ├── IF Create Hook returned search context → SKIP
|
||||||
|
│ └── ELSE → Execute Auto Search
|
||||||
|
│ ├── Read assistant's search config
|
||||||
|
│ ├── Execute web/kb/db in parallel
|
||||||
|
│ ├── Send search_start/search_result/search_complete to output
|
||||||
|
│ ├── Rerank results
|
||||||
|
│ ├── Generate citation IDs
|
||||||
|
│ └── Inject search context + citation prompt to messages
|
||||||
|
│
|
||||||
|
├── 4. LLM Call (with search context if any)
|
||||||
|
│
|
||||||
|
├── 5. Next Hook (optional)
|
||||||
|
│
|
||||||
|
└── 6. Output (response may contain #ref:xxx citations)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control Options
|
||||||
|
|
||||||
|
| Options.Search | Assistant Config | Behavior |
|
||||||
|
| -------------- | ----------------- | ------------------------- |
|
||||||
|
| `true` | any | Force enable auto search |
|
||||||
|
| `false` | any | Force disable auto search |
|
||||||
|
| `nil` | has search config | Enable auto search |
|
||||||
|
| `nil` | no search config | Disable auto search |
|
||||||
|
|
||||||
|
**Go:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Force enable
|
||||||
|
options := &context.Options{Search: boolPtr(true)}
|
||||||
|
|
||||||
|
// Force disable
|
||||||
|
options := &context.Options{Search: boolPtr(false)}
|
||||||
|
|
||||||
|
// Follow assistant config (default)
|
||||||
|
options := &context.Options{Search: nil}
|
||||||
|
```
|
||||||
|
|
||||||
|
**API Request:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"messages": [...],
|
||||||
|
"search": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook-Controlled Search
|
||||||
|
|
||||||
|
When you need custom search logic, handle it in Create Hook:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function Create(ctx, messages, options) {
|
||||||
|
const query = messages[messages.length - 1].content;
|
||||||
|
|
||||||
|
// Custom logic: only search for certain queries
|
||||||
|
if (needsSearch(query)) {
|
||||||
|
const result = ctx.search.Web(query, { limit: 5 });
|
||||||
|
return {
|
||||||
|
messages: [{ role: "system", content: formatContext(result) }],
|
||||||
|
// Returning messages signals: skip auto search
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: [] };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Search Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Request → Trace Start → Query Process → Search → Rerank → Citations → Output → Return
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Processing
|
||||||
|
|
||||||
|
| Type | Process |
|
||||||
|
| ---- | ----------------------------------------------------- |
|
||||||
|
| Web | Extract keywords → Build query |
|
||||||
|
| KB | Get collection's embedding model → Generate embedding |
|
||||||
|
| DB | Parse query → Build QueryDSL → Execute on models |
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
### Web Search
|
||||||
|
|
||||||
|
| Provider | Type | Notes |
|
||||||
|
| -------- | -------- | ------------------------------- |
|
||||||
|
| Tavily | Built-in | Recommended for AI applications |
|
||||||
|
| Serper | Built-in | Google search API |
|
||||||
|
| MCP | External | Any MCP server with search tool |
|
||||||
|
|
||||||
|
### Knowledge Base
|
||||||
|
|
||||||
|
Integrates with Yao's GraphRAG system:
|
||||||
|
|
||||||
|
- Vector search with collection-specific embedding models
|
||||||
|
- Graph-based association (optional)
|
||||||
|
|
||||||
|
### Database Search
|
||||||
|
|
||||||
|
Integrates with Yao's Model/QueryDSL system:
|
||||||
|
|
||||||
|
- Natural language → QueryDSL conversion (via LLM)
|
||||||
|
- Model schema introspection for query building
|
||||||
|
- Support for:
|
||||||
|
- Global models (`models/*.mod.yao`)
|
||||||
|
- Assistant-specific models (`assistants/{id}/models/*.mod.yao` → `agents.{id}.*`)
|
||||||
|
- Permission-aware queries (respects `__yao_*` permission fields)
|
||||||
|
|
||||||
|
### Reranking
|
||||||
|
|
||||||
|
| Type | Notes |
|
||||||
|
| ----- | ---------------------------------------- |
|
||||||
|
| score | Simple score sorting (default) |
|
||||||
|
| model | Cohere, BGE, Jina rerankers |
|
||||||
|
| agent | Delegate to another assistant for rerank |
|
||||||
|
| mcp | Call MCP server rerank tool |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Search errors don't block the agent flow. Errors are returned in `Result.Error`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = ctx.search.Web(query);
|
||||||
|
if (result.error) {
|
||||||
|
// Handle gracefully or fallback
|
||||||
|
console.warn("Search failed:", result.error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Priority
|
||||||
|
|
||||||
|
1. **Request-level**: `Options.Search` in Stream() call (highest)
|
||||||
|
- `true`: Force enable auto search
|
||||||
|
- `false`: Force disable auto search
|
||||||
|
- `nil`: Follow assistant config
|
||||||
|
2. **Hook-level**: Options in `ctx.search.*()` calls
|
||||||
|
3. **Assistant-level**: `search` config in assistant.yml
|
||||||
|
4. **Global-level**: `config/search.yml` defaults
|
||||||
|
|
||||||
|
## DB Search Details
|
||||||
|
|
||||||
|
### Query Processing Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Natural Language Query
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Get Model Schemas │ ← Introspect models from db.models config
|
||||||
|
│ (fields, types, relations) │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ LLM: Generate QueryDSL │ ← Convert NL to Yao QueryDSL
|
||||||
|
│ (select, wheres, orders) │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Execute Query on Each Model │ ← model.Find() with QueryDSL
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Results
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model ID Formats
|
||||||
|
|
||||||
|
| Format | Example | Description |
|
||||||
|
| ------ | -------------------- | --------------------------------------------------------------------- |
|
||||||
|
| Global | `product` | Global model from `models/product.mod.yao` |
|
||||||
|
| System | `__yao.user` | Yao system model |
|
||||||
|
| Agent | `agents.mybot.order` | Assistant-specific model from `assistants/mybot/models/order.mod.yao` |
|
||||||
|
|
||||||
|
### QueryDSL Generation Prompt
|
||||||
|
|
||||||
|
The DB handler uses LLM to convert natural language to QueryDSL:
|
||||||
|
|
||||||
|
```
|
||||||
|
Given the following model schemas:
|
||||||
|
- product: { id, name, price, category, status, created_at }
|
||||||
|
- order: { id, product_id, quantity, total, customer_id, status }
|
||||||
|
|
||||||
|
User query: "find all active products under $100 in electronics category"
|
||||||
|
|
||||||
|
Generate Yao QueryDSL:
|
||||||
|
{
|
||||||
|
"model": "product",
|
||||||
|
"wheres": [
|
||||||
|
{ "field": "status", "op": "=", "value": "active" },
|
||||||
|
{ "field": "price", "op": "<", "value": 100 },
|
||||||
|
{ "field": "category", "op": "=", "value": "electronics" }
|
||||||
|
],
|
||||||
|
"orders": [{ "field": "price", "order": "asc" }],
|
||||||
|
"limit": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `agent/context/jsapi.go` - JSAPI base implementation
|
||||||
|
- `agent/context/types_llm.go` - Uses configuration (Search field)
|
||||||
|
- `agent/assistant/types.go` - SearchOption definition
|
||||||
|
- `agent/store/types/types.go` - KnowledgeBase, Database config
|
||||||
|
- `agent/output/message/types.go` - Output message types
|
||||||
|
- `agent/content/` - Content module (similar Handler + Registry pattern)
|
||||||
|
- `model/model.go` - Yao Model loading (global, system, assistant models)
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- `agent/context/JSAPI.md` - Full JSAPI documentation
|
||||||
|
- `agent/context/RESOURCE_MANAGEMENT.md` - Context lifecycle and resource management
|
||||||
|
- `agent/output/README.md` - Output system documentation
|
||||||
|
- `agent/store/CHAT_STORAGE_DESIGN.md` - Chat storage design
|
||||||
Loading…
Add table
Reference in a new issue