Files
YiemAgent/src/type.jl
T
2026-08-04 14:51:53 +07:00

520 lines
18 KiB
Julia

module type
export agent, sommelier, companion, virtualcustomer, agentContext, yiemAgent,
run_agent, take_response, follow_up, stop_agent
using Dates, UUIDs, DataStructures, JSON, NATS
using GeneralUtils
# ============================================================================
# Simple type aliases / definitions
# ============================================================================
const Timestamp = DateTime
struct Usage
inputTokens::Int64
outputTokens::Int64
end
# ---------------------------------------------- 100 --------------------------------------------- #
# ============================================================================
# Message types
# ============================================================================
abstract type agentMessage end # Base type for all agent messages
struct userMessage <: agentMessage # Message from the user
role::String # Always "user"
content::Vector{messageContent} # Text and/or image content
timestamp::Timestamp # When the message was sent
end
"""
Create a new user message.
# Arguments
- `role::String`: Always "user"
- `content::Vector{messageContent}`: Text and/or image content
- `timestamp::Timestamp`: When the message was sent
# Returns
- A new `userMessage` instance
# Examples
```julia
julia> msg = userMessage(content=[textContent("Hello")])
userMessage("user", [textContent("Hello")], DateTime(...))
```
"""
function userMessage(; role="user", content=Vector{messageContent}(), timestamp=now())
return userMessage(role, content, timestamp)
end
struct assistantMessage <: agentMessage # Message from the AI assistant
role::String # Always "assistant"
content::Vector{messageContent} # Text and/or image content
api::String # API name used (e.g., "openai")
provider::String # Provider name (e.g., "anthropic")
model::String # Model identifier
usage::Usage # Token usage for this message
stopReason::String # Why generation stopped (e.g., "end_turn")
errorMessage::Union{String, Nothing} # Error if generation failed
timestamp::Timestamp # When the message was received
end
"""
Create a new assistant message.
# Arguments
- `role::String`: Always "assistant"
- `content::Vector{messageContent}`: Text and/or image content
- `api::String`: API name used (e.g., "openai")
- `provider::String`: Provider name (e.g., "anthropic")
- `model::String`: Model identifier
- `usage::Usage`: Token usage for this message
- `stopReason::String`: Why generation stopped (e.g., "end_turn")
- `errorMessage::Union{String, Nothing}`: Error if generation failed
- `timestamp::Timestamp`: When the message was received
# Returns
- A new `assistantMessage` instance
# Examples
```julia
julia> msg = assistantMessage(content=[textContent("Hello!")], model="gpt-4")
assistantMessage("assistant", [textContent("Hello!")], "", "", "gpt-4", ..., "end_turn", nothing, DateTime(...))
```
"""
function assistantMessage(; role="assistant", content=Vector{messageContent}(),
api="", provider="", model="", usage=Usage(0, 0), stopReason="end_turn",
errorMessage=nothing, timestamp=now())
return assistantMessage(role, content, api, provider, model, usage, stopReason, errorMessage, timestamp)
end
struct toolResultMessage <: agentMessage # Result returned from a tool execution
role::String # Always "tool"
toolCallId::String # ID matching the tool call
toolName::String # Name of the executed tool
content::Vector{messageContent} # Tool output content
details::Any # Additional tool-specific details
usage::Union{Usage, Nothing} # Token usage if applicable
addedToolNames::Union{Vector{String}, Nothing} # Tools added during execution
isError::Bool # Whether the tool call resulted in an error
timestamp::Timestamp # When the result was recorded
end
"""
Create a new tool result message.
# Arguments
- `role::String`: Always "tool"
- `toolCallId::String`: ID matching the tool call
- `toolName::String`: Name of the executed tool
- `content::Vector{messageContent}`: Tool output content
- `details::Any`: Additional tool-specific details
- `usage::Union{Usage, Nothing}`: Token usage if applicable
- `addedToolNames::Union{Vector{String}, Nothing}`: Tools added during execution
- `isError::Bool`: Whether the tool call resulted in an error
- `timestamp::Timestamp`: When the result was recorded
# Returns
- A new `toolResultMessage` instance
# Examples
```julia
julia> msg = toolResultMessage(toolCallId="call_123", toolName="search", content=[textContent("results")])
toolResultMessage("tool", "call_123", "search", [textContent("results")], nothing, nothing, nothing, false, DateTime(...))
```
"""
function toolResultMessage(; role="tool", toolCallId="", toolName="",
content=Vector{messageContent}(), details=nothing, usage=nothing,
addedToolNames=nothing, isError=false, timestamp=now())
return toolResultMessage(role, toolCallId, toolName, content, details, usage, addedToolNames, isError, timestamp)
end
# ============================================================================
# Message content types
# ============================================================================
abstract type messageContent end # Base type for message content
struct textContent <: messageContent # Plain text message content
text::String # The text content
end
"""
Create plain text message content.
# Arguments
- `text::String`: The text content
# Returns
- A new `textContent` instance
# Examples
```julia
julia> content = textContent("Hello, world!")
textContent("Hello, world!")
```
"""
function textContent(; text="")
return textContent(text)
end
struct imageContent <: messageContent # Image message content
data::String # Base64-encoded image data
mimeType::String # MIME type (e.g., "image/png")
end
"""
Create image message content.
# Arguments
- `data::String`: Base64-encoded image data
- `mimeType::String`: MIME type (e.g., "image/png")
# Returns
- A new `imageContent` instance
# Examples
```julia
julia> img = imageContent(data="base64data...", mimeType="image/png")
imageContent("base64data...", "image/png")
```
"""
function imageContent(; data="", mimeType="")
return imageContent(data, mimeType)
end
# ============================================================================
# Tool types
# ============================================================================
"""
A tool available to the agent.
# Arguments
- `name::String`: Tool identifier
- `label::String`: Human-readable tool name
- `description::String`: What the tool does
- `parameters::TParameters`: Tool parameters schema (JSON schema)
- `execute::Function`: Tool execution function
- `prepareArguments::Union{Function, Nothing}`: Optional argument preparation callback
- `executionMode::Union{toolExecutionMode, Nothing}`: Override: run tool calls sequentially or in parallel
# Returns
- A new `agentTool` instance
"""
struct agentTool{TParameters, TDetails} # A tool available to the agent
name::String # Tool identifier
label::String # Human-readable tool name
description::String # What the tool does
parameters::TParameters # Tool parameters schema (JSON schema)
execute::Function # Tool execution function
prepareArguments::Union{Function, Nothing} # Optional argument preparation callback
executionMode::Union{toolExecutionMode, Nothing} # Override: run tool calls sequentially or in parallel
end
# ============================================================================
# Agent context
# ============================================================================
"""
Snapshot of the agent's conversation context.
# Arguments
- `systemPrompt::String`: System prompt for the agent
- `messages::Vector{agentMessage}`: Conversation messages
- `tools::Union{Vector{agentTool}, Nothing}`: Available tools
# Returns
- A new `agentContext` instance
"""
struct agentContext # Snapshot of the agent's conversation context
systemPrompt::String # System prompt for the agent
messages::Vector{agentMessage} # Conversation messages
tools::Union{Vector{agentTool}, Nothing} # Available tools
end
# ============================================================================
# Agent state
# ============================================================================
mutable struct agentState # Mutable runtime state of an agent
systemPrompt::String # System prompt text
model::llmModel # LLM model to use
tools::Vector{agentTool} # Available tools
messages::Vector{agentMessage} # Conversation messages
pendingToolCalls::Vector{String} # Tool call IDs waiting for results
activeRun::Bool # is agent processing user message?
errorMessage::Union{String, Nothing} # Last error message
end
"""
Create a new mutable agent state.
Creates a deep copy of the provided tools and messages to isolate the
new state from external references.
# Arguments
- `systemPrompt::String`: System prompt text
- `model::llmModel`: LLM model to use (defaults to an unknown model)
- `tools::Vector{agentTool}`: Available tools (deep copied)
- `messages::Vector{agentMessage}`: Conversation messages (deep copied)
# Returns
- A new `agentState` instance with an empty pending tool calls list and no error
# Examples
```julia
julia> state = agentState(systemPrompt="You are a helpful assistant")
agentState("You are a helpful assistant", ..., agentTool[], agentMessage[], String[], nothing)
```
"""
function agentState(
systemPrompt::String="",
model::llmModel=llmModel{String}("", "", "unknown", "unknown", "", false, String[], modelCost(0.0, 0.0, 0.0, 0.0), 0, 0),
tools::Vector{agentTool}=agentTool[],
messages::Vector{agentMessage}=agentMessage[],
)
agentState(
systemPrompt,
model,
deepcopy(tools),
deepcopy(messages),
Vector{String}(),
nothing,
)
end
struct toolCall # A tool invocation from the LLM
type::String # Always "function"
id::String # Unique tool call identifier
name::String # Tool name
arguments::Dict{String, Any} # Parsed tool arguments
end
"""
Context for preparing the next conversation turn.
# Arguments
- `message::assistantMessage`: The assistant's message that just completed
- `toolResults::Vector{toolResultMessage}`: Tool results from this turn
- `context::agentContext`: Current conversation context
- `newMessages::Vector{agentMessage}`: Messages to append to the context
# Returns
- A new `prepareNextTurnContext` instance
"""
struct prepareNextTurnContext # Context for preparing the next conversation turn
message::assistantMessage # The assistant's message that just completed
toolResults::Vector{toolResultMessage} # Tool results from this turn
context::agentContext # Current conversation context
newMessages::Vector{agentMessage} # Messages to append to the context
end
struct modelCost # Model pricing per 1M tokens
input::Float64 # Price per 1M input tokens
output::Float64 # Price per 1M output tokens
cache_read::Float64 # Price per 1M cached read tokens
cache_write::Float64 # Price per 1M cache write tokens
end
struct llmModel{Api} # LLM model configuration
id::String # Unique model identifier
name::String # Human-readable model name
api::Api # API type (parametric type)
provider::String # Provider name (e.g., "anthropic", "openai")
baseUrl::String # API endpoint base URL
reasoning::Bool # Whether the model supports chain-of-thought
input::Vector{String} # Supported input modalities (e.g., "text", "image")
cost::modelCost # Pricing information
contextWindow::Int64 # Maximum context length in tokens
maxTokens::Int64 # Maximum output tokens per completion
end
# ============================================================================
# Agent struct
# ============================================================================
abstract type agent end
"""
docstring
"""
mutable struct yiemAgent <: agent # High-level agent wrapper
_state::agentState # Current state (prompt, model, messages, tools, etc.)
inputChannel::Channel # user sends prompt message to agent.
# if agent is idle, it process user message right away.
# if agent is running, it process user message after
# the current tool call finished.
followUpChannel::Channel # Buffers messages the user sends while the agent is busy.
# Processed after all inputChannel messages are handled
# and the agent is idle (not using a tool call).
outputChannel::Channel # agent sends response message to user after processing
# all user messages in inputChannel and all followUp messages.
_task::Union{Task, Nothing} # Background task running the agent loop
formatMsgForLLM::Function # Convert agent messages to LLM message format
preprocessMessages ::Union{Function, Nothing} # Preprocess/transform messages before sending to LLM
beforeToolCall::Union{Function, Nothing} # Callback invoked before executing a tool call
afterToolCall::Union{Function, Nothing} # Callback invoked after executing a tool call
prepareNextTurn::Union{Function, Nothing} # Callback to prepare the next conversation turn
prepareNextTurnWithContext::Union{Function, Nothing} # Same but receives context
sessionId::Union{String, Nothing} # Optional session identifier
maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms)
toolExecution::toolExecutionMode # Default: run tool calls sequentially or in parallel
end
"""
Create a new yiemAgent instance with a background loop task.
Spawns a background `@spawn` task that runs the agent loop, listening
on `inputChannel` and `followUpChannel` channels concurrently.
# Keyword Arguments
- `systemPrompt::String`: System prompt for the agent
- `model`: LLM model to use
- `tools::Vector{agentTool}`: Available tools (default: empty)
- `messages::Vector{agentMessage}`: Initial conversation messages (default: empty)
- `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`)
- `preprocessMessages::Union{Function, Nothing}`: Preprocess/transform messages before sending to LLM (default: `nothing`)
- `beforeToolCall::Union{Function, Nothing}`: Callback invoked before executing a tool call (default: `nothing`)
- `afterToolCall::Union{Function, Nothing}`: Callback invoked after executing a tool call (default: `nothing`)
- `prepareNextTurn::Union{Function, Nothing}`: Callback to prepare the next conversation turn (default: `nothing`)
- `prepareNextTurnWithContext::Union{Function, Nothing}`: Same but receives context (default: `nothing`)
- `sessionId::Union{String, Nothing}`: Optional session identifier (default: `nothing`)
- `maxRetryDelayMs::Union{Int64, Nothing}`: Maximum delay between retries in milliseconds (default: `nothing`)
- `toolExecution`: Default tool execution mode (sequential or parallel) (default: `nothing`)
# Returns
- A new `yiemAgent` instance with an active background task
# Examples
```julia
julia> agent = yiemAgent(systemPrompt="You are a helpful assistant", model=my_model)
yiemAgent(agentState(...), Channel(...), Channel(...), Channel(...), ..., ...)
```
"""
function yiemAgent(
; systemPrompt::String="",
model=nothing,
tools::Vector{agentTool}=agentTool[],
messages::Vector{agentMessage}=agentMessage[],
formatMsgForLLM::Function=defaultformatMsgForLLM,
preprocessMessages::Union{Function, Nothing}=nothing,
beforeToolCall::Union{Function, Nothing}=nothing,
afterToolCall::Union{Function, Nothing}=nothing,
prepareNextTurn::Union{Function, Nothing}=nothing,
prepareNextTurnWithContext::Union{Function, Nothing}=nothing,
sessionId::Union{String, Nothing}=nothing,
maxRetryDelayMs::Union{Int64, Nothing}=nothing,
toolExecution=nothing,
)
# Create channels: input (user -> agent), followUp (async queue), output (agent -> user)
inputChannel = Channel(16)
followUp = Channel(32)
outputChannel = Channel(16)
# Create struct with a placeholder task, then spawn and replace it
agent = yiemAgent(
agentState(systemPrompt, model, tools, messages),
inputChannel,
followUp,
outputChannel,
nothing, # placeholder — replaced below
formatMsgForLLM,
preprocessMessages,
beforeToolCall,
afterToolCall,
prepareNextTurn,
prepareNextTurnWithContext,
sessionId,
maxRetryDelayMs,
toolExecution,
)
# Spawn the background loop and attach it
agent._task = @spawn _agent_loop(agent)
return agent
end
end # module type