Files
YiemAgent/docs/4-specification.md
T
2026-09-11 08:28:49 +07:00

14 KiB

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):

struct textContent <: messageContent
    text::String
end

imageContent (SP-006.2):

struct imageContent <: messageContent
    data::String                   # Base64-encoded image data
    mimeType::String               # MIME type (e.g., "image/png")
end

reasoningContent (SP-006.3):

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):

{
  "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:

{
  "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):

{
  "result": {
    "content": [
      {"type": "text", "text": "Tool output text"}
    ],
    "isError": false
  }
}

Response error:

{
  "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

Dict(
  "role" => "system",
  "content" => [Dict("type" => "text", "text" => ctx.systemPrompt)]
)

Traceability:

  • Requirement: FR-006
  • Solution Design: SD-006

SP-012: userMessage → OpenAI format

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

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

Dict(
  "role" => "assistant",
  "content" => _messageContentToBlocks(msg.content)
)

Traceability:

  • Requirement: FR-006
  • Solution Design: SD-006

SP-015: toolResultMessage → OpenAI format

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

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):

{
  "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:

{
  "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):

{
  "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):

{
  "content": [
    {
      "type": "tool_call",
      "name": "getWeather",
      "arguments": {"city": "Bangkok"},
      "id": "call_1"
    }
  ]
}

Content blocks with text only (SP-018.3):

{
  "content": [
    {"type": "text", "text": "It's 22°C in Bangkok"}
  ]
}

Response dict extras (common across all formats, SP-018.4):

{
  "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

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

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

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

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