diff --git a/src/type.jl b/src/type.jl index e04c124..aaa6c76 100644 --- a/src/type.jl +++ b/src/type.jl @@ -11,37 +11,36 @@ using GeneralUtils # ============================================================================ # Message types # ============================================================================ -abstract type agentMessage end +abstract type agentMessage end # Base type for all agent messages -struct userMessage <: agentMessage - role::String - content::Vector{messageContent} - timestamp::Timestamp +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 -struct assistantMessage <: agentMessage - role::String - content::Vector{messageContent} - api::String - provider::String - model::String - usage::Usage - stop_reason::String - error_message::Union{String, Nothing} - timestamp::Timestamp +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 -struct toolResultMessage <: agentMessage - - role::String - tool_call_id::String - tool_name::String - content::Vector{messageContent} - details::Any - usage::Union{Usage, Nothing} - added_tool_names::Union{Vector{String}, Nothing} - is_error::Bool - timestamp::Timestamp +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 @@ -49,15 +48,15 @@ end # Message content types # ============================================================================ -abstract type messageContent end +abstract type messageContent end # Base type for message content -struct textContent <: messageContent - text::String +struct textContent <: messageContent # Plain text message content + text::String # The text content end -struct imageContent <: messageContent - data::String - mime_type::String +struct imageContent <: messageContent # Image message content + data::String # Base64-encoded image data + mimeType::String # MIME type (e.g., "image/png") end @@ -65,14 +64,14 @@ end # Tool types # ============================================================================ -struct agentTool{TParameters, TDetails} - name::String - label::String - description::String - parameters::TParameters - execute::Function - prepare_arguments::Union{Function, Nothing} - execution_mode::Union{ToolExecutionMode, Nothing} +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 @@ -80,10 +79,10 @@ end # Agent context # ============================================================================ -struct agentContext - system_prompt::String - messages::Vector{agentMessage} - tools::Union{Vector{agentTool}, Nothing} +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 @@ -92,35 +91,35 @@ end # Assistant message event types # ============================================================================ -abstract type assistantMessageEvent end +abstract type assistantMessageEvent end # Base type for assistant message streaming events -struct startEvent <: assistantMessageEvent - partial::assistantMessage +struct startEvent <: assistantMessageEvent # Message generation started + partial::assistantMessage # The partial message at this point end -struct textStartEvent <: assistantMessageEvent - content_index::Int64 - partial::assistantMessage +struct textStartEvent <: assistantMessageEvent # Text content block started + contentIndex::Int64 # Index of the content block + partial::assistantMessage # The partial message at this point end -struct textDeltaEvent <: assistantMessageEvent - content_index::Int64 - delta::String - partial::assistantMessage +struct textDeltaEvent <: assistantMessageEvent # Text content block received a chunk + contentIndex::Int64 # Index of the content block + delta::String # New text chunk + partial::assistantMessage # The partial message at this point end -struct textEndEvent <: assistantMessageEvent - content_index::Int64 - content::String - partial::assistantMessage +struct textEndEvent <: assistantMessageEvent # Text content block completed + contentIndex::Int64 # Index of the content block + content::String # Complete text content + partial::assistantMessage # The partial message at this point end -struct doneEvent <: assistantMessageEvent - reason::String - usage::Usage - message::assistantMessage +struct doneEvent <: assistantMessageEvent # Message generation completed successfully + reason::String # Why generation stopped + usage::Usage # Token usage + message::assistantMessage # The completed message end -struct errorEvent <: assistantMessageEvent - reason::String - error_message::Union{String, Nothing} - usage::Usage - error::assistantMessage +struct errorEvent <: assistantMessageEvent # Message generation encountered an error + reason::String # Error reason + errorMessage::Union{String, Nothing} # Human-readable error + usage::Usage # Token usage (partial) + error::assistantMessage # The error message end @@ -129,48 +128,41 @@ end # Agent state # ============================================================================ -mutable struct agentState - system_prompt::String - model::Model - thinking_level::ThinkingLevel - tools::Vector{agentTool} - messages::Vector{agentMessage} - is_streaming::Bool - streaming_message::Union{agentMessage, Nothing} - pending_tool_calls::Set{String} - error_message::Union{String, Nothing} +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 + errorMessage::Union{String, Nothing} # Last error message +end - function agentState( - system_prompt::String="", - model::Model=Model("", "", "unknown", "unknown", "", false, String[], ModelCost(0.0, 0.0, 0.0, 0.0), 0, 0), - thinking_level::ThinkingLevel=THINKING_OFF, - tools::Vector{agentTool}=agentTool[], - messages::Vector{agentMessage}=agentMessage[], +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, ) - new( - system_prompt, - model, - thinking_level, - copy(tools), - copy(messages), - false, - nothing, - Set{String}(), - nothing, - ) - end end # ============================================================================ # Tool call types # ============================================================================ -struct toolCall - type::String - id::String - name::String - arguments::Dict{String, Any} - partial_json::Union{String, Nothing} +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 + partialJson::Union{String, Nothing} # Raw JSON string during streaming end @@ -178,12 +170,179 @@ end # Next turn context # ============================================================================ -struct nextTurnContext - message::assistantMessage - tool_results::Vector{toolResultMessage} - context::agentContext - new_messages::Vector{agentMessage} +struct nextTurnContext # 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 + +# ============================================================================ +# llmModel types +# ============================================================================ + +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 +# ============================================================================ + +mutable struct yiemAgent # High-level agent wrapper + _state::agentState # Current state (prompt, model, messages, tools, etc.) + conn::NATS.Connection # NATS connection for messaging + followUpQueue::pendingMessageQueue # Messages queued via followUp() when agent would stop + + formatMsgForLLM::Function # Convert agent messages to LLM message format + preprocessMessages ::Union{Function, Nothing} # Preprocess/transform messages before sending + streamFunction::streamFn # Stream function for streaming responses + getApiKey::Union{Function, Nothing} # Callback to retrieve API key + onPayload::Union{Function, Nothing} # Callback when a payload is sent to the API + onResponse::Union{Function, Nothing} # Callback when a full response is received + 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 + activeRun::Union{activeRun, Nothing} # Active run state (promise, abort controller) + sessionId::Union{String, Nothing} # Optional session identifier + thinkingBudgets::Union{Dict{String, Int64}, Nothing} # Per-model thinking token budgets + transport::String # Transport mode ("auto" or explicit) + maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms) + toolExecution::toolExecutionMode # Default: run tool calls sequentially or in parallel +end + +# Outer constructor — clean keyword API +function yiemAgent( + ; systemPrompt::String="", + model::llmModel=llmModel{String}("", "", "unknown", "unknown", "", false, String[], modelCost(0.0, 0.0, 0.0, 0.0), 0, 0), + thinkingLevel::thinkingLevel=THINKING_OFF, + tools::Vector{agentTool}=agentTool[], + messages::Vector{agentMessage}=agentMessage[], + formatMsgForLLM::Function=defaultformatMsgForLLM, + preprocessMessages ::Union{Function, Nothing}=nothing, + streamFunction::streamFn=getDefaultStreamFn(), + getApiKey::Union{Function, Nothing}=nothing, + onPayload::Union{Function, Nothing}=nothing, + onResponse::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, + thinkingBudgets::Union{Dict{String, Int64}, Nothing}=nothing, + transport::String="auto", + maxRetryDelayMs::Union{Int64, Nothing}=nothing, + toolExecution::toolExecutionMode=EXECUTION_PARALLEL, + ) + new( + agentState(systemPrompt, model, tools, messages), + Set{Tuple{Function, Ref{Bool}}}(), + pendingMessageQueue(QUEUE_ONE_AT_A_TIME), + pendingMessageQueue(QUEUE_ONE_AT_A_TIME), + formatMsgForLLM, + preprocessMessages , + streamFunction, + getApiKey, + onPayload, + onResponse, + beforeToolCall, + afterToolCall, + prepareNextTurn, + prepareNextTurnWithContext, + nothing, + sessionId, + thinkingBudgets, + transport, + maxRetryDelayMs, + toolExecution, + ) end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + end # module type