update docs
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# Specification: YiemAgent Agent Framework
|
||||
|
||||
## 1. Message Type System (`src/type.jl`)
|
||||
|
||||
### SP-001: userMessage
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `role::String` | `String` | Yes | Always `"user"` |
|
||||
| `content::Vector{messageContent}` | `Vector{messageContent}` | No | Plain text and/or image content |
|
||||
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was sent |
|
||||
|
||||
**Keyword Constructor**: `userMessage(; role="user", content=[...], timestamp=now())`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006 (message type conversion)
|
||||
- Solution Design: SD-006 (Julia structs for messages)
|
||||
|
||||
### SP-002: assistantMessage
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `role::String` | `String` | Yes | Always `"assistant"` |
|
||||
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Text and/or image content |
|
||||
| `api::String` | `String` | Yes | API name used (e.g., `"openai"`) |
|
||||
| `provider::String` | `String` | Yes | Provider name (e.g., `"anthropic"`) |
|
||||
| `model::String` | `String` | Yes | Model identifier |
|
||||
| `usage::llmUsage` | `llmUsage` | Yes | Token usage (in/out) |
|
||||
| `stopReason::String` | `String` | Yes | Why generation stopped (e.g., `"end_turn"`) |
|
||||
| `errorMessage::Union{String, Nothing}` | `Union{String, Nothing}` | Yes | Error if generation failed |
|
||||
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was received |
|
||||
|
||||
**Keyword Constructor**: `assistantMessage(; role="assistant", content=[...], api="", provider="", model=nothing, usage=llmUsage(0,0), stopReason="end_turn", errorMessage=nothing, timestamp=now())`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006 (message type conversion)
|
||||
- Solution Design: SD-006
|
||||
|
||||
### SP-003: assistantMessageToolCall
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `role::String` | `String` | Yes | Always `"assistant"` |
|
||||
| `toolCalls::Vector{agentToolCall}` | `Vector{agentToolCall}` | Yes | Tool calls to execute |
|
||||
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Reasoning/thinking content blocks |
|
||||
| `api::String` | `String` | Yes | API name used |
|
||||
| `provider::String` | `String` | Yes | Provider name |
|
||||
| `model::String` | `String` | Yes | Model identifier |
|
||||
| `usage::llmUsage` | `llmUsage` | Yes | Token usage |
|
||||
| `stopReason::String` | `String` | Yes | Why generation stopped (e.g., `"tool_calls"`) |
|
||||
| `errorMessage::Union{String, Nothing}` | `Union{String, Nothing}` | Yes | Error if generation failed |
|
||||
| `timestamp::Timestamp` | `DateTime` | Yes | When the message was received |
|
||||
|
||||
**Keyword Constructor**: `assistantMessageToolCall(; role="assistant", toolCalls=[...], content=[], api="", provider="", model=nothing, usage=llmUsage(0,0), stopReason="tool_calls", errorMessage=nothing, timestamp=now())`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-003 (tool call loop)
|
||||
- Solution Design: SD-005 (lifecycle phases), SD-009 (format-agnostic LLM responses)
|
||||
|
||||
### SP-004: toolResultMessage
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `role::String` | `String` | Yes | Always `"toolResult"` (note: `"tool"` in example) |
|
||||
| `toolCallId::String` | `String` | Yes | ID matching the tool call |
|
||||
| `toolName::String` | `String` | Yes | Name of the executed tool |
|
||||
| `content::Vector{messageContent}` | `Vector{messageContent}` | Yes | Tool output content |
|
||||
| `details::Any` | `Any` | Yes | Additional tool-specific details |
|
||||
| `usage::Union{llmUsage, Nothing}` | `Union{llmUsage, Nothing}` | Yes | Token usage if applicable |
|
||||
| `addedToolNames::Union{Vector{String}, Nothing}` | `Union{Vector{String}, Nothing}` | Yes | Tools added during execution |
|
||||
| `isError::Bool` | `Bool` | Yes | Whether the tool call resulted in an error |
|
||||
| `timestamp::Timestamp` | `DateTime` | Yes | When the result was recorded |
|
||||
|
||||
**Keyword Constructor**: `toolResultMessage(; role="tool", toolCallId="", toolName="", content=[], details=nothing, usage=nothing, addedToolNames=nothing, isError=false, timestamp=now())`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-005 (tool lifecycle)
|
||||
- Solution Design: SD-005
|
||||
|
||||
### SP-005: agentToolCall
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `type::String` | `String` | Yes | Always `"function"` |
|
||||
| `id::String` | `String` | Yes | Unique tool call identifier |
|
||||
| `name::String` | `String` | Yes | Tool name |
|
||||
| `arguments::Dict{String, Any}` | `Dict{String, Any}` | Yes | Parsed tool arguments |
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-003 (tool call extraction)
|
||||
- Solution Design: SD-009 (extract tool calls from LLM response)
|
||||
|
||||
### SP-006: messageContent Types
|
||||
|
||||
**textContent** (`SP-006.1`):
|
||||
```julia
|
||||
struct textContent <: messageContent
|
||||
text::String
|
||||
end
|
||||
```
|
||||
|
||||
**imageContent** (`SP-006.2`):
|
||||
```julia
|
||||
struct imageContent <: messageContent
|
||||
data::String # Base64-encoded image data
|
||||
mimeType::String # MIME type (e.g., "image/png")
|
||||
end
|
||||
```
|
||||
|
||||
**reasoningContent** (`SP-006.3`):
|
||||
```julia
|
||||
struct reasoningContent <: messageContent
|
||||
text::String
|
||||
end
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006 (message content conversion)
|
||||
- Solution Design: SD-006
|
||||
|
||||
## 2. LLM Model Configuration (`src/type.jl`)
|
||||
|
||||
### SP-007: llmModel
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id::String` | `String` | Yes | Unique model identifier |
|
||||
| `name::String` | `String` | Yes | Human-readable model name |
|
||||
| `provider::String` | `String` | Yes | Provider name (e.g., `"anthropic"`, `"openai"`) |
|
||||
| `baseUrl::String` | `String` | Yes | API endpoint base URL |
|
||||
| `reasoning::Bool` | `Bool` | Yes | Whether the model supports chain-of-thought |
|
||||
| `input::Vector{String}` | `Vector{String}` | Yes | Supported input modalities (e.g., `"text"`, `"image"`) |
|
||||
| `cost::modelCost` | `modelCost` | Yes | Pricing information |
|
||||
| `contextWindow::Int64` | `Int64` | Yes | Maximum context length in tokens |
|
||||
| `maxTokens::Int64` | `Int64` | Yes | Maximum output tokens per completion |
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-002 (LLM agnosticism)
|
||||
- Solution Design: SD-003
|
||||
|
||||
### SP-008: modelCost
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `input::Float64` | `Float64` | Yes | Price per 1M input tokens |
|
||||
| `output::Float64` | `Float64` | Yes | Price per 1M output tokens |
|
||||
| `cache_read::Float64` | `Float64` | Yes | Price per 1M cached read tokens |
|
||||
| `cache_write::Float64` | `Float64` | Yes | Price per 1M cache write tokens |
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: N/A (internal tracking data)
|
||||
- Solution Design: SD-003
|
||||
|
||||
## 3. MCP Tool Integration (`src/toolRegistry.jl`)
|
||||
|
||||
### SP-009: tools/list Response Format
|
||||
|
||||
**Request**: `mcp("tools/list", sessionId; eventSink=eventSink)`
|
||||
|
||||
**Response** (JSON-RPC 2.0):
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"tools": [
|
||||
{
|
||||
"name": "tool_name",
|
||||
"title"?: "Human-readable title",
|
||||
"label"?: "Fallback title",
|
||||
"description": "What the tool does",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": { "type": "string", "description": "..." }
|
||||
},
|
||||
"required": ["field"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"nextCursor"?: "pagination_cursor_or_empty"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response error**:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Method not found"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-004 (MCP discovery)
|
||||
- Solution Design: SD-004
|
||||
|
||||
### SP-010: tools/call Request/Response Format
|
||||
|
||||
**Request**: `mcp("tools/call", toolName, args, sessionId; eventSink=eventSink)`
|
||||
|
||||
**Response** (JSON-RPC 2.0):
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"content": [
|
||||
{"type": "text", "text": "Tool output text"}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response error**:
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"content": [
|
||||
{"type": "text", "text": "Error message"}
|
||||
],
|
||||
"isError": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-004 (tool execution)
|
||||
- Solution Design: SD-004 (`_wrap_mcp_tool`)
|
||||
|
||||
## 4. OpenAI Message Format (`src/utils.jl`)
|
||||
|
||||
### SP-011: system message format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"role" => "system",
|
||||
"content" => [Dict("type" => "text", "text" => ctx.systemPrompt)]
|
||||
)
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006
|
||||
- Solution Design: SD-006
|
||||
|
||||
### SP-012: userMessage → OpenAI format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"role" => "user",
|
||||
"content" => _messageContentToBlocks(msg.content)
|
||||
)
|
||||
```
|
||||
|
||||
Where `_messageContentToBlocks` converts:
|
||||
- `textContent(t)` → `Dict("type" => "text", "text" => t)`
|
||||
- `imageContent(data, mime)` → `Dict("type" => "image_url", "image_url" => Dict("url" => "data:$mime;base64,$data"))`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006
|
||||
- Solution Design: SD-006
|
||||
|
||||
### SP-013: assistantMessageToolCall → OpenAI format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"role" => "assistant",
|
||||
"content" => nothing,
|
||||
"tool_calls" => [
|
||||
Dict(
|
||||
"id" => tc.id,
|
||||
"type" => tc.type,
|
||||
"function" => Dict(
|
||||
"name" => tc.name,
|
||||
"arguments" => JSON.json(tc.arguments) # serialized as JSON string
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-003
|
||||
- Solution Design: SD-009
|
||||
|
||||
### SP-014: assistantMessage → OpenAI format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"role" => "assistant",
|
||||
"content" => _messageContentToBlocks(msg.content)
|
||||
)
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-006
|
||||
- Solution Design: SD-006
|
||||
|
||||
### SP-015: toolResultMessage → OpenAI format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"role" => "tool",
|
||||
"tool_call_id" => msg.toolCallId,
|
||||
"content" => _messageContentToBlocks(msg.content)
|
||||
)
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-005, FR-006
|
||||
- Solution Design: SD-005
|
||||
|
||||
### SP-016: agentTool → OpenAI tools format
|
||||
|
||||
```julia
|
||||
Dict(
|
||||
"type" => "function",
|
||||
"function" => Dict(
|
||||
"name" => tool.name,
|
||||
"description" => tool.description,
|
||||
"parameters" => tool.inputSchema
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-004
|
||||
- Solution Design: SD-004, SD-006
|
||||
|
||||
## 5. LLM Response Format Agnostic Extraction (`src/agentCore.jl`)
|
||||
|
||||
### SP-017: format 1 — LMStudio.jl / vLLM style (dict with nested `message.tool_calls`)
|
||||
|
||||
**Response dict** (key: `message::Dict`):
|
||||
```json
|
||||
{
|
||||
"finish_reason": "tool_calls",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"reasoning_content": "I need to call getWeather...",
|
||||
"tool_calls": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "getWeather",
|
||||
"arguments": "{\"city\":\"Bangkok\"}" // JSON string
|
||||
},
|
||||
"id": "call_1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**When no tool calls**:
|
||||
```json
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "It's 22°C in Bangkok"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-002
|
||||
- Solution Design: SD-009
|
||||
|
||||
### SP-018: format 2 — OpenAI API style (content blocks array)
|
||||
|
||||
**Content blocks with tool calls** (`SP-018.1`):
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_calls",
|
||||
"tool_calls": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "getWeather",
|
||||
"arguments": {"city": "Bangkok"} // already parsed dict
|
||||
},
|
||||
"id": "call_1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Content blocks with tool_call (already parsed)** (`SP-018.2`):
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "tool_call",
|
||||
"name": "getWeather",
|
||||
"arguments": {"city": "Bangkok"},
|
||||
"id": "call_1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Content blocks with text only** (`SP-018.3`):
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{"type": "text", "text": "It's 22°C in Bangkok"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response dict extras** (common across all formats, `SP-018.4`):
|
||||
```json
|
||||
{
|
||||
"api": "openai",
|
||||
"provider": "anthropic",
|
||||
"model": "gpt-4",
|
||||
"usage": {"inputTokens": 100, "outputTokens": 50},
|
||||
"error_message": "Optional error"
|
||||
}
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-002
|
||||
- Solution Design: SD-009
|
||||
|
||||
## 6. Agent Loop Configuration (`src/type.jl` + `src/agentCore.jl`)
|
||||
|
||||
### SP-019: agentContext
|
||||
|
||||
```julia
|
||||
struct agentContext
|
||||
systemPrompt::String
|
||||
messages::Vector{agentMessage}
|
||||
tools::Union{OrderedDict{String, agentTool}, Nothing} # O(1) lookup by name
|
||||
end
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-003 (context preparation)
|
||||
- Solution Design: SD-003, SD-004
|
||||
|
||||
### SP-020: agentState
|
||||
|
||||
```julia
|
||||
mutable struct agentState
|
||||
systemPrompt::String
|
||||
model::Union{llmModel, Nothing}
|
||||
tools::OrderedDict{String, agentTool} # insertion-ordered
|
||||
messages::Vector{agentMessage} # history (no system prompt)
|
||||
pendingToolCalls::Vector{String} # tool call IDs waiting for results
|
||||
errorMessage::Union{String, Nothing} # last error message
|
||||
end
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-001 (agent state management)
|
||||
- Solution Design: SD-006
|
||||
|
||||
### SP-021: agentLoopConfig
|
||||
|
||||
```julia
|
||||
struct agentLoopConfig
|
||||
beforeToolCall::Union{Function, Nothing}
|
||||
afterToolCall::Union{Function, Nothing}
|
||||
toolExecution::String # "sequential" or "parallel"
|
||||
end
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-005 (lifecycle hooks)
|
||||
- Solution Design: SD-005
|
||||
|
||||
### SP-022: abortSignal
|
||||
|
||||
```julia
|
||||
struct abortSignal
|
||||
aborted::Bool
|
||||
end
|
||||
```
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: NFR-101 (timeout handling)
|
||||
- Solution Design: SD-008
|
||||
|
||||
## 7. Tool Validation Hooks (`src/utils.jl`)
|
||||
|
||||
### SP-023: validateRequiredArgs (default hook)
|
||||
|
||||
**Signature**: `validateRequiredArgs(args::AbstractDict{String, Any}, inputSchema::AbstractDict{String, Any})::Union{Nothing, String}`
|
||||
|
||||
**Behavior**:
|
||||
- Extract `required = get(inputSchema, "required", Any[])`
|
||||
- Check each required field exists in `keys(args)`
|
||||
- Return `nothing` if all present
|
||||
- Return `"Missing required arguments: $(join(missing, ", "))"` if any missing
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: NFR-301 (input validation)
|
||||
- Solution Design: SD-005
|
||||
|
||||
### SP-024: validateToolArguments (wrapper hook)
|
||||
|
||||
**Signature**: `validateToolArguments(tool::agentTool, prepared::agentToolCall)::AbstractDict{String, Any}`
|
||||
|
||||
**Behavior**:
|
||||
- If `tool.validateRequiredArgs !== nothing`: call `tool.validateRequiredArgs(prepared.arguments)`
|
||||
- Else: call `validateRequiredArgs(prepared.arguments, tool.inputSchema)`
|
||||
- If returns `!nothing`: throw `ArgumentError(result)`
|
||||
- Caller (`prepareToolCall`) catches and returns `immediateOutcome`
|
||||
|
||||
**Traceability**:
|
||||
- Requirement: FR-005 (tool validation)
|
||||
- Solution Design: SD-005
|
||||
Reference in New Issue
Block a user