update
This commit is contained in:
+2
-2
@@ -10,8 +10,8 @@ module YiemAgent
|
|||||||
include("type.jl")
|
include("type.jl")
|
||||||
using .type
|
using .type
|
||||||
|
|
||||||
include("util.jl")
|
include("utils.jl")
|
||||||
using .util
|
using .utils
|
||||||
|
|
||||||
include("llmfunction.jl")
|
include("llmfunction.jl")
|
||||||
using .llmfunction
|
using .llmfunction
|
||||||
|
|||||||
+102
-84
@@ -1,11 +1,11 @@
|
|||||||
module agentCore
|
module agentCore
|
||||||
|
|
||||||
# export prompt
|
export _agent_loop
|
||||||
|
|
||||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||||
DataFrames, Serde
|
DataFrames, Serde
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
using ..type, ..util, ..llmfunction
|
using ..type, ..utils, ..llmfunction
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
@@ -74,7 +74,6 @@ function _agent_loop(agent::yiemAgent)
|
|||||||
agent.followUpChannel -> nothing
|
agent.followUpChannel -> nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
while true
|
while true
|
||||||
result = nothing
|
result = nothing
|
||||||
msg = nothing
|
msg = nothing
|
||||||
@@ -106,7 +105,7 @@ function _agent_loop(agent::yiemAgent)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
# make active
|
# start _process_message loop
|
||||||
if agent._state.activeRun == false
|
if agent._state.activeRun == false
|
||||||
# Dispatch message through the processing pipeline
|
# Dispatch message through the processing pipeline
|
||||||
processing_task = @spawn _process_message(agent)
|
processing_task = @spawn _process_message(agent)
|
||||||
@@ -122,8 +121,8 @@ function _agent_loop(agent::yiemAgent)
|
|||||||
put!(agent.inputChannel, followMsg)
|
put!(agent.inputChannel, followMsg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
continue # continue to process user message in the next loop
|
continue # continue to process user message in the next loop
|
||||||
|
|
||||||
elseif typeof(processing_task) == Task && istaskdone(processing_task) == true
|
elseif typeof(processing_task) == Task && istaskdone(processing_task) == true
|
||||||
# if agent runs is done but followUpChannel has messages, discard all message in it.
|
# if agent runs is done but followUpChannel has messages, discard all message in it.
|
||||||
# when agent work is done it should not accept follow up msg.
|
# when agent work is done it should not accept follow up msg.
|
||||||
@@ -135,8 +134,8 @@ function _agent_loop(agent::yiemAgent)
|
|||||||
end
|
end
|
||||||
result = fetch(processing_task)
|
result = fetch(processing_task)
|
||||||
put!(agent.outputChannel, result)
|
put!(agent.outputChannel, result)
|
||||||
agent._state.activeRun = false
|
agent._state.activeRun = false # reset
|
||||||
processing_task = nothing
|
processing_task = nothing # reset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
catch e
|
catch e
|
||||||
@@ -163,7 +162,7 @@ should be implemented. Currently a placeholder that echoes back the received mes
|
|||||||
- Implement the full processing pipeline:
|
- Implement the full processing pipeline:
|
||||||
1. Add `msg` to `agent._state.messages`
|
1. Add `msg` to `agent._state.messages`
|
||||||
2. Call `agent.formatMsgForLLM(agent._state)` 1 to format for LLM
|
2. Call `agent.formatMsgForLLM(agent._state)` 1 to format for LLM
|
||||||
3. If `agent.preprocessContext` is set, call it on the formatted messages
|
3. If `agent.prepareContext` is set, call it on the formatted messages
|
||||||
4. Call the LLM (blocking — the task waits here)
|
4. Call the LLM (blocking — the task waits here)
|
||||||
5. If agent has tools, handle tool calls in a loop
|
5. If agent has tools, handle tool calls in a loop
|
||||||
6. Build `assistantMessage` and return it
|
6. Build `assistantMessage` and return it
|
||||||
@@ -174,93 +173,116 @@ julia> # Currently returns a placeholder echo response
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
function _process_message(agent::yiemAgent)::assistantMessage
|
function _process_message(agent::yiemAgent)::assistantMessage
|
||||||
# WORKING
|
|
||||||
|
|
||||||
# loop until llmCall() response didn't use tool calls
|
# loop until llmCall() response didn't use tool calls
|
||||||
while
|
final_response = nothing
|
||||||
# take every messages from agent.inputChannel, convert them into userMessage
|
while true
|
||||||
# and add them to agent._state.messages
|
# call agent.prepareContext()
|
||||||
|
preparedContext = agent.prepareContext(agent._state)
|
||||||
|
|
||||||
# call agent.preprocessContext()
|
#WORKING Call agent.formatMsgForLLM(agent._state) to format for LLM
|
||||||
|
formatted_messages = agent.formatMsgForLLM(preparedContext)
|
||||||
# Call agent.formatMsgForLLM(agent._state) to format for LLM
|
|
||||||
|
|
||||||
# Call llmCall() (blocking — the task waits here)
|
# Call llmCall() (blocking — the task waits here)
|
||||||
|
response = agent.llmCall(formatted_messages)
|
||||||
|
|
||||||
# if (LLM use tool calls)
|
# Check if LLM used tool calls (inspect content for tool_call blocks)
|
||||||
|
has_tool_calls = false
|
||||||
|
tool_call_list = agentToolCall[]
|
||||||
|
|
||||||
|
for content_block in response.content
|
||||||
|
if content_block isa Dict
|
||||||
|
if get(content_block, :type, "") == "tool_calls"
|
||||||
|
has_tool_calls = true
|
||||||
|
for tc_data in get(content_block, :tool_calls, [])
|
||||||
|
tc = agentToolCall(
|
||||||
|
type="function",
|
||||||
|
id=get(tc_data, :id, string(uuid4())),
|
||||||
|
name=get(tc_data, :function, Dict{String,Any}())[:name],
|
||||||
|
arguments=get(tc_data, :function, Dict{String,Any}())[:arguments],
|
||||||
|
)
|
||||||
|
push!(tool_call_list, tc)
|
||||||
|
end
|
||||||
|
elseif get(content_block, :type, "") == "tool_call"
|
||||||
|
has_tool_calls = true
|
||||||
|
tc_data = content_block
|
||||||
|
tc = agentToolCall(
|
||||||
|
type="function",
|
||||||
|
id=get(tc_data, :id, string(uuid4())),
|
||||||
|
name=get(tc_data, :name, ""),
|
||||||
|
arguments=get(tc_data, :arguments, Dict{String,Any}()),
|
||||||
|
)
|
||||||
|
push!(tool_call_list, tc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_tool_calls && length(tool_call_list) > 0
|
||||||
|
# Build context and config for executeToolCalls
|
||||||
|
context = agentContext(
|
||||||
|
agent._state.systemPrompt,
|
||||||
|
agent._state.messages,
|
||||||
|
agent._state.tools,
|
||||||
|
)
|
||||||
|
|
||||||
|
config = agentLoopConfig(
|
||||||
|
agent._state.tools,
|
||||||
|
agent.beforeToolCall,
|
||||||
|
agent.afterToolCall,
|
||||||
|
agent.parallelToolExecute ? "parallel" : "sequential",
|
||||||
|
)
|
||||||
|
|
||||||
|
signal = nothing
|
||||||
|
emit = agent.agentEventSink
|
||||||
|
|
||||||
# call executeToolCalls()
|
# call executeToolCalls()
|
||||||
|
batch = executeToolCalls(context, response, tool_call_list, config, signal, emit)
|
||||||
|
|
||||||
# save toolResults to agent._state.messages
|
# save toolResults to agent._state.messages
|
||||||
|
for tool_result in batch.messages
|
||||||
|
push!(agent._state.messages, tool_result)
|
||||||
|
end
|
||||||
|
|
||||||
# else (LLM not use tool calls)
|
if batch.terminate
|
||||||
# break out of while loop
|
# If batch requested termination, build a final response
|
||||||
|
final_content = [textContent("Tool execution completed.")]
|
||||||
|
for tool_result in batch.messages
|
||||||
|
for content_block in tool_result.content
|
||||||
|
if content_block isa textContent
|
||||||
|
append!(final_content, [content_block])
|
||||||
|
elseif content_block isa Dict
|
||||||
|
if haskey(content_block, :text)
|
||||||
|
push!(final_content, textContent(content_block[:text]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
final_response = assistantMessage(
|
||||||
|
role="assistant",
|
||||||
|
content=final_content,
|
||||||
|
api=response.api,
|
||||||
|
model=response.model,
|
||||||
|
usage=response.usage,
|
||||||
|
stopReason="tool_use_terminated",
|
||||||
|
errorMessage=if any(x -> x.isError, batch.messages)
|
||||||
|
"One or more tool calls failed"
|
||||||
|
else
|
||||||
|
nothing
|
||||||
|
end,
|
||||||
|
timestamp=now(),
|
||||||
|
)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# LLM did not use tool calls — this is the final response
|
||||||
|
final_response = response
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Build assistantMessage and return it
|
return final_response
|
||||||
|
|
||||||
# Placeholder: echo back the message as a simple response
|
|
||||||
@warn "TODO: implement _process_message"
|
|
||||||
return assistantMessage(
|
|
||||||
role="assistant",
|
|
||||||
content=[textContent("Received: $(msg)")],
|
|
||||||
api="", model="", usage=nothing,
|
|
||||||
stopReason="end_turn",
|
|
||||||
errorMessage=nothing,
|
|
||||||
timestamp=now(),
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
executeToolCalls(context, assistantMsg, toolCalls, config, signal, emit)
|
|
||||||
|
|
||||||
Dispatches to sequential or parallel execution. Uses sequential mode
|
|
||||||
when `config.toolExecution == "sequential"` or when any of the
|
|
||||||
tool calls reference a tool with `executionMode: "sequential"`.
|
|
||||||
Otherwise uses parallel execution. This is the entry point called
|
|
||||||
from `streamAssistantResponse` in the agent loop.
|
|
||||||
|
|
||||||
The sequential mode takes priority over parallel because it is the
|
|
||||||
safe default. If even one tool in a batch is marked sequential, all
|
|
||||||
tools execute sequentially — this prevents a single dependent tool
|
|
||||||
from racing with an otherwise independent one. The per-tool
|
|
||||||
`executionMode` allows fine-grained control (e.g. most tools are
|
|
||||||
parallel but a specific write tool is sequential), while the config-level
|
|
||||||
`toolExecution` provides a global override.
|
|
||||||
"""
|
|
||||||
function executeToolCalls(
|
|
||||||
context::agentContext,
|
|
||||||
assistantMsg::assistantMessage,
|
|
||||||
toolCalls::vector{agentToolCall},
|
|
||||||
config::agentLoopConfig,
|
|
||||||
signal::union{nothing,abortSignal},
|
|
||||||
emit::Function,
|
|
||||||
)::agentToolCallBatch
|
|
||||||
|
|
||||||
hasSequential = false
|
|
||||||
for tc in toolCalls
|
|
||||||
for t in context.tools
|
|
||||||
if t.name == tc.name && get(t.executionMode, "parallel") == "sequential"
|
|
||||||
hasSequential = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if hasSequential
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if config.toolExecution == "sequential" || hasSequential
|
|
||||||
return executeToolCallsSequential(context, assistantMsg, toolCalls, config, signal, emit)
|
|
||||||
else
|
|
||||||
return executeToolCallsParallel(context, assistantMsg, toolCalls, config, signal, emit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
createErrorToolResult(msg::String) -> agentToolResult
|
createErrorToolResult(msg::String) -> agentToolResult
|
||||||
|
|
||||||
@@ -1040,10 +1062,6 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+175
-39
@@ -1,15 +1,11 @@
|
|||||||
module type
|
module type
|
||||||
export agent, sommelier, companion, virtualcustomer, agentContext, yiemAgent,
|
export agentContext, yiemAgent,
|
||||||
run_agent, take_response, follow_up, stop_agent
|
run_agent, take_response, follow_up, stop_agent
|
||||||
|
|
||||||
|
|
||||||
using Dates, UUIDs, DataStructures, JSON, NATS
|
using Dates, UUIDs, DataStructures, JSON, NATS
|
||||||
using GeneralUtils
|
using GeneralUtils
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Simple type aliases / definitions
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
const Timestamp = DateTime
|
const Timestamp = DateTime
|
||||||
|
|
||||||
struct Usage
|
struct Usage
|
||||||
@@ -17,12 +13,10 @@ struct Usage
|
|||||||
outputTokens::Int64
|
outputTokens::Int64
|
||||||
end
|
end
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# ============================================================================
|
# Message types #
|
||||||
# Message types
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# ============================================================================
|
|
||||||
abstract type agentMessage end # Base type for all agent messages
|
abstract type agentMessage end # Base type for all agent messages
|
||||||
|
|
||||||
struct userMessage <: agentMessage # Message from the user
|
struct userMessage <: agentMessage # Message from the user
|
||||||
@@ -135,9 +129,9 @@ function toolResultMessage(; role="tool", toolCallId="", toolName="",
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Message content types
|
# Message content types #
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
abstract type messageContent end # Base type for message content
|
abstract type messageContent end # Base type for message content
|
||||||
|
|
||||||
@@ -190,9 +184,9 @@ function imageContent(; data="", mimeType="")
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Tool types
|
# Tool types #
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A tool available to the agent.
|
A tool available to the agent.
|
||||||
@@ -220,9 +214,9 @@ struct agentTool{TParameters, TDetails} # A tool available to the agent
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Agent context
|
# Agent context #
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Snapshot of the agent's conversation context.
|
Snapshot of the agent's conversation context.
|
||||||
@@ -242,15 +236,18 @@ struct agentContext # Snapshot of the agent's conversa
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Agent state
|
# Agent state #
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
mutable struct agentState # Mutable runtime state of an agent
|
mutable struct agentState # Mutable runtime state of an agent
|
||||||
systemPrompt::String # System prompt text
|
systemPrompt::String # System prompt text
|
||||||
model::llmModel # LLM model to use
|
model::llmModel # LLM model to use
|
||||||
tools::Vector{agentTool} # Available tools
|
tools::Vector{agentTool} # Available tools
|
||||||
messages::Vector{agentMessage} # Conversation messages
|
|
||||||
|
# messages history includes userMessage, assistantMessage, toolResultMessage
|
||||||
|
messages::Vector{agentMessage}
|
||||||
|
|
||||||
pendingToolCalls::Vector{String} # Tool call IDs waiting for results
|
pendingToolCalls::Vector{String} # Tool call IDs waiting for results
|
||||||
activeRun::Bool # is agent processing user message?
|
activeRun::Bool # is agent processing user message?
|
||||||
errorMessage::Union{String, Nothing} # Last error message
|
errorMessage::Union{String, Nothing} # Last error message
|
||||||
@@ -343,9 +340,148 @@ struct llmModel{Api} # LLM model configuration
|
|||||||
maxTokens::Int64 # Maximum output tokens per completion
|
maxTokens::Int64 # Maximum output tokens per completion
|
||||||
end
|
end
|
||||||
|
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Agent struct
|
# Agent loop configuration & tool execution types #
|
||||||
# ============================================================================
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
"""
|
||||||
|
Configuration for the agent tool execution loop.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `tools::Vector{agentTool}`: Available tools
|
||||||
|
- `beforeToolCall::Union{Function, Nothing}`: Callback before tool execution
|
||||||
|
- `afterToolCall::Union{Function, Nothing}`: Callback after tool execution
|
||||||
|
- `toolExecution::String`: Execution mode — "sequential" or "parallel"
|
||||||
|
"""
|
||||||
|
struct agentLoopConfig
|
||||||
|
tools::Vector{agentTool}
|
||||||
|
beforeToolCall::Union{Function, Nothing}
|
||||||
|
afterToolCall::Union{Function, Nothing}
|
||||||
|
toolExecution::String
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Signal for aborting ongoing operations.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `aborted::Bool`: Whether the operation has been aborted
|
||||||
|
"""
|
||||||
|
struct abortSignal
|
||||||
|
aborted::Bool
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Result returned by tool execution before the `afterToolCall` hook.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `content::Vector{messageContent}`: Tool output content
|
||||||
|
- `details::Dict{Any,Any}`: Tool-specific details
|
||||||
|
- `usage::Union{Usage, Nothing}`: Token usage if applicable
|
||||||
|
- `terminate::Bool`: Whether tool requests termination of the agent loop
|
||||||
|
"""
|
||||||
|
struct agentToolResult
|
||||||
|
content::Vector{messageContent}
|
||||||
|
details::Dict{Any,Any}
|
||||||
|
usage::Union{Usage, Nothing}
|
||||||
|
terminate::Bool
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Function type for parallel execution override on a tool.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- Context for parallel execution
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `agentToolCallBatch`: The result batch from parallel execution
|
||||||
|
"""
|
||||||
|
const toolparallelExecute = Function
|
||||||
|
|
||||||
|
"""
|
||||||
|
Context passed to the `beforeToolCall` hook.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `message::assistantMessage`: The assistant message containing the tool call
|
||||||
|
- `toolCall::agentToolCall`: The tool call being prepared
|
||||||
|
- `args::Dict{String,Any}`: Validated tool arguments
|
||||||
|
- `context::agentContext`: Current conversation context
|
||||||
|
"""
|
||||||
|
struct assistantMsgCtx
|
||||||
|
message::assistantMessage
|
||||||
|
toolCall::agentToolCall
|
||||||
|
args::Dict{String,Any}
|
||||||
|
context::agentContext
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Context passed to the `afterToolCall` hook.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `message::assistantMessage`: The assistant message containing the tool call
|
||||||
|
- `toolCall::agentToolCall`: The tool call that was executed
|
||||||
|
- `args::Dict{String,Any}`: Tool arguments
|
||||||
|
- `result::agentToolResult`: The raw tool result
|
||||||
|
- `isError::Bool`: Whether execution resulted in an error
|
||||||
|
- `context::agentContext`: Current conversation context
|
||||||
|
"""
|
||||||
|
struct afterCtx
|
||||||
|
message::assistantMessage
|
||||||
|
toolCall::agentToolCall
|
||||||
|
args::Dict{String,Any}
|
||||||
|
result::agentToolResult
|
||||||
|
isError::Bool
|
||||||
|
context::agentContext
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Event emitted when a tool call execution starts.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `toolCallId::String`: ID of the tool call
|
||||||
|
- `toolName::String`: Name of the tool
|
||||||
|
- `arguments::Dict{String,Any}`: Tool arguments
|
||||||
|
"""
|
||||||
|
struct toolExecStartEvent
|
||||||
|
toolCallId::String
|
||||||
|
toolName::String
|
||||||
|
arguments::Dict{String,Any}
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Event emitted with partial results during tool execution.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `toolCallId::String`: ID of the tool call
|
||||||
|
- `toolName::String`: Name of the tool
|
||||||
|
- `arguments::Dict{String,Any}`: Tool arguments
|
||||||
|
- `partialResult::Any`: The partial result data
|
||||||
|
"""
|
||||||
|
struct toolExecUpdateEvent
|
||||||
|
toolCallId::String
|
||||||
|
toolName::String
|
||||||
|
arguments::Dict{String,Any}
|
||||||
|
partialResult::Any
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Event emitted when a tool call execution ends.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `toolCallId::String`: ID of the tool call
|
||||||
|
- `toolName::String`: Name of the tool
|
||||||
|
- `result::agentToolResult`: The final tool result
|
||||||
|
- `isError::Bool`: Whether execution resulted in an error
|
||||||
|
"""
|
||||||
|
struct toolExecEndEvent
|
||||||
|
toolCallId::String
|
||||||
|
toolName::String
|
||||||
|
result::agentToolResult
|
||||||
|
isError::Bool
|
||||||
|
end
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
# Agent struct #
|
||||||
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
|
|
||||||
abstract type agent end
|
abstract type agent end
|
||||||
|
|
||||||
@@ -367,14 +503,14 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
|
|||||||
# and all followUp messages.
|
# and all followUp messages.
|
||||||
outputChannel::Channel
|
outputChannel::Channel
|
||||||
|
|
||||||
_task::Union{Task, Nothing} # Background task running the agent loop
|
_agent_loop::Union{Task, Nothing} # agent loop running in the background
|
||||||
|
|
||||||
# Preprocess/transform messages and context (modify, filter, prune, inject context from memory,
|
# Preprocess/transform messages and context (modify, filter, prune, inject context from memory,
|
||||||
# reorder, ...) for a single LLM call in _process_message()'s loop.
|
# reorder, ...) for a single LLM call in _process_message()'s loop.
|
||||||
# returns new Vector{agentMessage}
|
# returns new Vector{agentMessage}
|
||||||
preprocessContext ::Union{Function, Nothing}
|
prepareContext ::Union{Function, Nothing}
|
||||||
|
|
||||||
# Convert preprocessContext()'s new Vector{agentMessage} to LLM message format
|
# Convert prepareContext()'s new Vector{agentMessage} to LLM message format
|
||||||
formatMsgForLLM::Function
|
formatMsgForLLM::Function
|
||||||
|
|
||||||
# Actually invoke the LLM to get a completion response. The LLM response comes back as an
|
# Actually invoke the LLM to get a completion response. The LLM response comes back as an
|
||||||
@@ -391,8 +527,8 @@ mutable struct yiemAgent <: agent # High-level agent wrapper
|
|||||||
# Callback invoked after executing a tool call to sanitize tools output so the output is ready
|
# Callback invoked after executing a tool call to sanitize tools output so the output is ready
|
||||||
# to be converted into toolResults message
|
# to be converted into toolResults message
|
||||||
afterToolCall::Union{Function, Nothing}
|
afterToolCall::Union{Function, Nothing}
|
||||||
prepareNextTurn::Union{Function, Nothing} # Callback to prepare the next conversation turn
|
# prepareNextTurn::Union{Function, Nothing} # Callback to prepare the next conversation turn
|
||||||
prepareNextTurnWithContext::Union{Function, Nothing} # Same but receives context
|
# prepareNextTurnWithContext::Union{Function, Nothing} # Same but receives context
|
||||||
sessionId::Union{String, Nothing} # Optional session identifier
|
sessionId::Union{String, Nothing} # Optional session identifier
|
||||||
maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms)
|
maxRetryDelayMs::Union{Int64, Nothing} # Maximum delay between retries (ms)
|
||||||
parallelToolExecute::Bool # Default: false
|
parallelToolExecute::Bool # Default: false
|
||||||
@@ -412,7 +548,7 @@ on `inputChannel` and `followUpChannel` channels concurrently.
|
|||||||
- `messages::Vector{agentMessage}`: Initial conversation messages (default: empty)
|
- `messages::Vector{agentMessage}`: Initial conversation messages (default: empty)
|
||||||
- `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`)
|
- `formatMsgForLLM::Function`: Convert agent messages to LLM message format (default: `defaultformatMsgForLLM`)
|
||||||
- `llmCall::Function`: Function to invoke the LLM (required)
|
- `llmCall::Function`: Function to invoke the LLM (required)
|
||||||
- `preprocessContext::Union{Function, Nothing}`: Preprocess/transform messages before sending to LLM (default: `nothing`)
|
- `prepareContext::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`)
|
- `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`)
|
- `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`)
|
- `prepareNextTurn::Union{Function, Nothing}`: Callback to prepare the next conversation turn (default: `nothing`)
|
||||||
@@ -436,13 +572,13 @@ function yiemAgent(
|
|||||||
model=nothing,
|
model=nothing,
|
||||||
tools::Vector{agentTool}=agentTool[],
|
tools::Vector{agentTool}=agentTool[],
|
||||||
messages::Vector{agentMessage}=agentMessage[],
|
messages::Vector{agentMessage}=agentMessage[],
|
||||||
preprocessContext::Union{Function, Nothing}=nothing,
|
prepareContext::Union{Function, Nothing}=nothing,
|
||||||
formatMsgForLLM::Function=defaultformatMsgForLLM,
|
formatMsgForLLM::Function=defaultformatMsgForLLM,
|
||||||
llmCall::Function,
|
llmCall::Function,
|
||||||
beforeToolCall::Union{Function, Nothing}=nothing,
|
beforeToolCall::Union{Function, Nothing}=nothing,
|
||||||
afterToolCall::Union{Function, Nothing}=nothing,
|
afterToolCall::Union{Function, Nothing}=nothing,
|
||||||
prepareNextTurn::Union{Function, Nothing}=nothing,
|
# prepareNextTurn::Union{Function, Nothing}=nothing,
|
||||||
prepareNextTurnWithContext::Union{Function, Nothing}=nothing,
|
# prepareNextTurnWithContext::Union{Function, Nothing}=nothing,
|
||||||
sessionId::Union{String, Nothing}=nothing,
|
sessionId::Union{String, Nothing}=nothing,
|
||||||
maxRetryDelayMs::Union{Int64, Nothing}=nothing,
|
maxRetryDelayMs::Union{Int64, Nothing}=nothing,
|
||||||
parallelToolExecute::Bool=false,
|
parallelToolExecute::Bool=false,
|
||||||
@@ -460,13 +596,13 @@ function yiemAgent(
|
|||||||
followUp,
|
followUp,
|
||||||
outputChannel,
|
outputChannel,
|
||||||
nothing, # placeholder — replaced below
|
nothing, # placeholder — replaced below
|
||||||
preprocessContext,
|
prepareContext,
|
||||||
formatMsgForLLM,
|
formatMsgForLLM,
|
||||||
llmCall,
|
llmCall,
|
||||||
beforeToolCall,
|
beforeToolCall,
|
||||||
afterToolCall,
|
afterToolCall,
|
||||||
prepareNextTurn,
|
# prepareNextTurn,
|
||||||
prepareNextTurnWithContext,
|
# prepareNextTurnWithContext,
|
||||||
sessionId,
|
sessionId,
|
||||||
maxRetryDelayMs,
|
maxRetryDelayMs,
|
||||||
parallelToolExecute,
|
parallelToolExecute,
|
||||||
@@ -474,7 +610,7 @@ function yiemAgent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Spawn the background loop and attach it
|
# Spawn the background loop and attach it
|
||||||
agent._task = @spawn _agent_loop(agent)
|
agent._agent_loop = @spawn _agent_loop(agent)
|
||||||
|
|
||||||
return agent
|
return agent
|
||||||
end
|
end
|
||||||
|
|||||||
-505
@@ -1,505 +0,0 @@
|
|||||||
module util
|
|
||||||
|
|
||||||
export clearhistory, addNewMessage, chatHistoryToText, eventdict, noises, createTimeline,
|
|
||||||
availableWineToText, createEventsLog, createChatLog, checkAgentResponse_JSON,
|
|
||||||
checkAgentResponse_text
|
|
||||||
|
|
||||||
using UUIDs, Dates, DataStructures, HTTP, JSON
|
|
||||||
using GeneralUtils
|
|
||||||
using ..type
|
|
||||||
|
|
||||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
|
||||||
|
|
||||||
"""
|
|
||||||
Clear agent chat history.
|
|
||||||
|
|
||||||
Empties the conversation history, short-term memory, events log, and chatbox.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `a::T`: An agent instance (subtype of `agent`)
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `nothing`
|
|
||||||
|
|
||||||
# Notes
|
|
||||||
- Does not clear long-term memory; use `[PENDING] clear memory` when implemented.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> YiemAgent.clearhistory(agent)
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function clearhistory(a::T) where {T<:agent}
|
|
||||||
empty!(a.chathistory)
|
|
||||||
empty!(a.memory["shortmem"])
|
|
||||||
empty!(a.memory["events"])
|
|
||||||
a.memory["chatbox"] = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Add a new message to the agent's conversation history.
|
|
||||||
|
|
||||||
Automatically summarizes the oldest messages if the history exceeds `maximumMsg`.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `a::T1`: An agent instance (subtype of `agent`)
|
|
||||||
- `name::String`: Message sender role (e.g. "system", "user", "assistant")
|
|
||||||
- `userinput::T2`: Message dictionary to append (must contain "name" and "text" keys)
|
|
||||||
|
|
||||||
# Keyword Arguments
|
|
||||||
- `maximumMsg::Integer=30`: Maximum number of messages before summarization kicks in
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `nothing`
|
|
||||||
|
|
||||||
# Notes
|
|
||||||
- When history length exceeds `maximumMsg`, the oldest messages are summarized automatically.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> YiemAgent.addNewMessage(agent, "user", Dict("name" => "user", "text" => "hello"))
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function addNewMessage(a::T1, name::String, userinput::T2;
|
|
||||||
maximumMsg::Integer=30) where {T1<:agent, T2<:AbstractDict}
|
|
||||||
# if name ∉ ["system", "user", "assistant"] # guard against typo
|
|
||||||
# error("name is not in agent.availableRole $(@__LINE__)")
|
|
||||||
# end
|
|
||||||
|
|
||||||
#TODO summarize the oldest 10 message
|
|
||||||
if length(a.chathistory) > maximumMsg
|
|
||||||
summarize(a.chathistory)
|
|
||||||
else
|
|
||||||
# userinput["timestamp"] = Dates.now()
|
|
||||||
push!(a.chathistory, userinput)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
""" Converts a vector of dictionaries to a formatted string.
|
|
||||||
This function takes in a vector of dictionaries and outputs a single string where each dictionary's keys are prefixed by their values.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `vecd::Vector`
|
|
||||||
A vector of dictionaries containing chat messages
|
|
||||||
- `withkey::Bool`
|
|
||||||
Whether to include the name as a prefix in the output text. Default is true
|
|
||||||
- `range::Union{Nothing,UnitRange,Int}`
|
|
||||||
Optional range of messages to include. If nothing, includes all messages
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
A formatted string where each line contains either:
|
|
||||||
- If withkey=true: "name> message\n"
|
|
||||||
- If withkey=false: "message\n"
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
julia> using Revise
|
|
||||||
julia> using GeneralUtils
|
|
||||||
julia> vecd = [Dict("name" => "John", "text" => "Hello"), Dict("name" => "Jane", "text" => "Goodbye")]
|
|
||||||
julia> GeneralUtils.vectorOfDictToText(vecd, withkey=true)
|
|
||||||
"John> Hello\nJane> Goodbye\n"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function chatHistoryToText(vecd::Vector; withkey=true, range=nothing)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
text = ""
|
|
||||||
|
|
||||||
# Get the elements within the specified range, or all elements if no range provided
|
|
||||||
elements = isnothing(range) ? vecd : vecd[range]
|
|
||||||
|
|
||||||
# Determine whether to include the key in the output text or not
|
|
||||||
if withkey
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in elements
|
|
||||||
# Extract the 'name' and 'text' keys from the dictionary
|
|
||||||
name = titlecase(d[:name])
|
|
||||||
_text = d[:text]
|
|
||||||
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$name> $_text \n"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for d in elements
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
text *= "$v \n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the final text
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Convert a vector of wine dictionaries to a formatted text string.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `vecd::Vector`: A vector of dictionaries, each representing a wine with key-value pairs
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- A formatted string where each wine is numbered and each key-value pair is comma-separated
|
|
||||||
in the format: `"1) key1:value1,key2:value2 key3:value3 2) ..."`
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> vecd = [Dict("wine_name" => "Chateau A", "price" => "50")]
|
|
||||||
julia> YiemAgent.availableWineToText(vecd)
|
|
||||||
"1) wine_name:Chateau A,price:50 "
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function availableWineToText(vecd::Vector)::String
|
|
||||||
# Initialize an empty string to hold the final text
|
|
||||||
rowtext = ""
|
|
||||||
# Loop through each dictionary in the input vector
|
|
||||||
for (i, d) in enumerate(vecd)
|
|
||||||
# Iterate over all key-value pairs in the dictionary
|
|
||||||
temp = []
|
|
||||||
for (k, v) in d
|
|
||||||
# Append the formatted string to the text variable
|
|
||||||
t = "$k:$v"
|
|
||||||
push!(temp, t)
|
|
||||||
end
|
|
||||||
_rowtext = join(temp, ',')
|
|
||||||
rowtext *= "$i) $_rowtext "
|
|
||||||
end
|
|
||||||
|
|
||||||
return rowtext
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Create a dictionary representing an event with optional details.
|
|
||||||
|
|
||||||
# Keyword Arguments
|
|
||||||
- `event_description::Union{String, Nothing}`: A description of the event
|
|
||||||
- `timestamp::Union{DateTime, Nothing}`: The time when the event occurred
|
|
||||||
- `subject::Union{String, Nothing}`: The subject or entity associated with the event
|
|
||||||
- `thought::Union{AbstractDict, Nothing}`: Any associated thoughts or metadata
|
|
||||||
- `action_name::Union{String, Nothing}`: The name of the action performed (e.g., "CHAT", "CHECKINVENTORY")
|
|
||||||
- `action_input::Union{String, Nothing}`: Input or parameters for the action
|
|
||||||
- `location::Union{String, Nothing}`: Where the event took place
|
|
||||||
- `equipment_used::Union{String, Nothing}`: Equipment involved in the event
|
|
||||||
- `material_used::Union{String, Nothing}`: Materials used during the event
|
|
||||||
- `observation::Union{String, Nothing}`: Observation of the event
|
|
||||||
- `note::Union{String, Nothing}`: Additional notes or comments
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- A `Dict{String, Any}` with event details as string-keyed key-value pairs
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> YiemAgent.eventdict(action_name="CHAT", action_input="hello")
|
|
||||||
Dict{String, Any} with 11 entries: ...
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function eventdict(;
|
|
||||||
event_description::Union{String, Nothing}=nothing,
|
|
||||||
timestamp::Union{DateTime, Nothing}=nothing,
|
|
||||||
subject::Union{String, Nothing}=nothing,
|
|
||||||
thought::Union{AbstractDict, Nothing}=nothing,
|
|
||||||
action_name::Union{String, Nothing}=nothing, # "CHAT", "CHECKINVENTORY", "PRESENT_WINE_GUIDELINE", etc
|
|
||||||
action_input::Union{String, Nothing}=nothing,
|
|
||||||
location::Union{String, Nothing}=nothing,
|
|
||||||
equipment_used::Union{String, Nothing}=nothing,
|
|
||||||
material_used::Union{String, Nothing}=nothing,
|
|
||||||
observation::Union{String, Nothing}=nothing,
|
|
||||||
note::Union{String, Nothing}=nothing,
|
|
||||||
)
|
|
||||||
|
|
||||||
d = Dict{String, Any}(
|
|
||||||
"event_description"=> event_description,
|
|
||||||
"timestamp"=> timestamp,
|
|
||||||
"subject"=> subject,
|
|
||||||
"thought"=> thought,
|
|
||||||
"action_name"=> action_name,
|
|
||||||
"action_input"=> action_input,
|
|
||||||
"location"=> location,
|
|
||||||
"equipment_used"=> equipment_used,
|
|
||||||
"material_used"=> material_used,
|
|
||||||
"observation"=> observation,
|
|
||||||
"note"=> note,
|
|
||||||
)
|
|
||||||
|
|
||||||
return d
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Create a formatted timeline string from a sequence of events.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `events::T1`: Vector of event dictionaries. Each must have `action_name` and `action_input` keys,
|
|
||||||
and optionally `subject` and `observation` keys.
|
|
||||||
|
|
||||||
# Keyword Arguments
|
|
||||||
- `eventindex::Union{UnitRange, Nothing}=nothing`: Optional range of event indices to include.
|
|
||||||
If `nothing`, all events are included.
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `timeline::String`: A formatted string where each event appears on its own line in the format:
|
|
||||||
`"Event_{index} {subject}> action_name: {action_name}, action_input: {action_input}"`
|
|
||||||
If `observation` is present, it is appended.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> events = [
|
|
||||||
Dict("subject" => "User", "action_input" => "Hello", "action_name" => "CHAT", "observation" => nothing),
|
|
||||||
Dict("subject" => "Assistant", "action_input" => "Hi there!", "action_name" => "CHAT", "observation" => "with a smile")
|
|
||||||
];
|
|
||||||
julia> YiemAgent.createTimeline(events)
|
|
||||||
"Event_1 User> action_name: CHAT, action_input: Hello\\nEvent_2 Assistant> action_name: CHAT, action_input: Hi there!\\n"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function createTimeline(events::T1; eventindex::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty timeline string
|
|
||||||
timeline = ""
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if eventindex !== nothing
|
|
||||||
[eventindex...]
|
|
||||||
else
|
|
||||||
1:length(events)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = events[i]
|
|
||||||
# If no outcome exists, format without outcome
|
|
||||||
# if event["action_name"] == "CHAT_BOX"
|
|
||||||
# timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\n"
|
|
||||||
# elseif event["action_name"] == "CHECKINVENTORY" && event["observation"] === nothing
|
|
||||||
# timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: Not done yet.\n"
|
|
||||||
if event["action_name"] == "SEARCH_WINE_DATABASE"
|
|
||||||
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"]), observation: $(event["observation"])\\n"
|
|
||||||
else
|
|
||||||
timeline *= "Event_$i $(event["subject"])> action_name: $(event["action_name"]), action_input: $(event["action_input"])\\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return formatted timeline string
|
|
||||||
return timeline
|
|
||||||
end
|
|
||||||
|
|
||||||
"""
|
|
||||||
Create a formatted event log from a sequence of events.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `events::T1`: Vector of event dictionaries. Each must have `subject`, `action_name`, `action_input`,
|
|
||||||
and optionally `observation` keys.
|
|
||||||
|
|
||||||
# Keyword Arguments
|
|
||||||
- `index::Union{UnitRange, Nothing}=nothing`: Optional range of event indices to include.
|
|
||||||
If `nothing`, all events are included.
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- A `Vector{Dict{String, String}}` where each dictionary has `"name"` (from event subject) and
|
|
||||||
`"text"` (formatted action description) keys.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> events = [Dict("subject" => "User", "action_name" => "CHAT", "action_input" => "hello", "observation" => nothing)];
|
|
||||||
julia> log = YiemAgent.createEventsLog(events);
|
|
||||||
julia> log[1]["name"]
|
|
||||||
"User"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function createEventsLog(events::T1; index::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty log array
|
|
||||||
log = Dict{String, String}[]
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if index !== nothing
|
|
||||||
[index...]
|
|
||||||
else
|
|
||||||
1:length(events)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = events[i]
|
|
||||||
# If no outcome exists, format without outcome
|
|
||||||
if event["observation"] === nothing
|
|
||||||
subject = event["subject"]
|
|
||||||
action_name = event["action_name"]
|
|
||||||
action_input = event["action_input"]
|
|
||||||
str = "action_name: $action_name, action_input: $action_input"
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>str)
|
|
||||||
push!(log, d)
|
|
||||||
else
|
|
||||||
subject = event["subject"]
|
|
||||||
action_name = event["action_name"]
|
|
||||||
action_input = event["action_input"]
|
|
||||||
observation = event["observation"]
|
|
||||||
str = "action_name: $action_name, action_input: $action_input, observation: $observation"
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>str)
|
|
||||||
push!(log, d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return log
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Create a formatted chat log from a sequence of chat entries.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `chatdict::T1`: Vector of chat entry dictionaries. Each must have `"name"` and `"text"` keys.
|
|
||||||
|
|
||||||
# Keyword Arguments
|
|
||||||
- `index::Union{UnitRange, Nothing}=nothing`: Optional range of entry indices to include.
|
|
||||||
If `nothing`, all entries are included.
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- A `Vector{Dict{String, String}}` where each dictionary has `"name"` and `"text"` keys
|
|
||||||
copied from the corresponding input entry.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> chats = [Dict("name" => "user", "text" => "hello"), Dict("name" => "assistant", "text" => "hi")];
|
|
||||||
julia> YiemAgent.createChatLog(chats)[1]["name"]
|
|
||||||
"user"
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function createChatLog(chatdict::T1; index::Union{UnitRange, Nothing}=nothing
|
|
||||||
) where {T1<:AbstractVector}
|
|
||||||
# Initialize empty log array
|
|
||||||
log = Dict{String, String}[]
|
|
||||||
|
|
||||||
# Determine which indices to use - either provided range or full length
|
|
||||||
ind =
|
|
||||||
if index !== nothing
|
|
||||||
[index...]
|
|
||||||
else
|
|
||||||
1:length(chatdict)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterate through events and format each one
|
|
||||||
for i in ind
|
|
||||||
event = chatdict[i]
|
|
||||||
subject = event["name"]
|
|
||||||
text = event["text"]
|
|
||||||
d = Dict{String, String}("name"=>subject, "text"=>text)
|
|
||||||
push!(log, d)
|
|
||||||
end
|
|
||||||
|
|
||||||
return log
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Check if an agent's text response contains all required header keywords.
|
|
||||||
|
|
||||||
Validates that the response includes all required keywords without duplications.
|
|
||||||
|
|
||||||
# Arguments
|
|
||||||
- `response::String`: The agent's text response to validate
|
|
||||||
- `requiredHeader::T`: Array of required keyword strings (subtype of `Array{String}`)
|
|
||||||
|
|
||||||
# Returns
|
|
||||||
- `Tuple{Bool, Union{String, Nothing}}`: A two-element tuple where:
|
|
||||||
- First element: `true` if all required keywords are present and not duplicated, `false` otherwise
|
|
||||||
- Second element: An error description string if validation failed, or `nothing` if passed
|
|
||||||
|
|
||||||
# Notes
|
|
||||||
- Uses `GeneralUtils.detectKeywordVariation` for flexible keyword matching.
|
|
||||||
|
|
||||||
# Examples
|
|
||||||
```jldoctest
|
|
||||||
julia> ispass, err = YiemAgent.checkAgentResponse_text("hello world", ["hello"])
|
|
||||||
(true, nothing)
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
function checkAgentResponse_text(response::String, requiredHeader::T
|
|
||||||
)::Tuple where {T<:Array{String}}
|
|
||||||
detected_kw = GeneralUtils.detectKeywordVariation(requiredHeader, response)
|
|
||||||
missingkeys = [k for (k, v) in detected_kw if v === nothing]
|
|
||||||
ispass = false
|
|
||||||
errormsg = nothing
|
|
||||||
if !isempty(missingkeys)
|
|
||||||
errormsg = "$missingkeys are missing from your previous response"
|
|
||||||
ispass = false
|
|
||||||
elseif sum([length(i) for i in values(detected_kw)]) > length(requiredHeader)
|
|
||||||
errormsg = "Your previous attempt has duplicated points according to the required response format"
|
|
||||||
ispass = false
|
|
||||||
else
|
|
||||||
ispass = true
|
|
||||||
end
|
|
||||||
return (ispass, errormsg)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end # module util
|
|
||||||
+179
@@ -0,0 +1,179 @@
|
|||||||
|
module utils
|
||||||
|
|
||||||
|
export clearhistory, availableWineToText, prepareContext
|
||||||
|
|
||||||
|
using UUIDs, Dates, DataStructures, HTTP, JSON
|
||||||
|
using GeneralUtils
|
||||||
|
using ..type
|
||||||
|
|
||||||
|
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||||
|
|
||||||
|
"""
|
||||||
|
Clear agent chat history.
|
||||||
|
|
||||||
|
Empties the conversation history, short-term memory, events log, and chatbox.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `a::T`: An agent instance (subtype of `agent`)
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `nothing`
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
- Does not clear long-term memory; use `[PENDING] clear memory` when implemented.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> YiemAgent.clearhistory(agent)
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function clearhistory(a::T) where {T<:agent}
|
||||||
|
empty!(a.chathistory)
|
||||||
|
empty!(a.memory["shortmem"])
|
||||||
|
empty!(a.memory["events"])
|
||||||
|
a.memory["chatbox"] = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Convert a vector of wine dictionaries to a formatted text string.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `vecd::Vector`: A vector of dictionaries, each representing a wine with key-value pairs
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- A formatted string where each wine is numbered and each key-value pair is comma-separated
|
||||||
|
in the format: `"1) key1:value1,key2:value2 key3:value3 2) ..."`
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```jldoctest
|
||||||
|
julia> vecd = [Dict("wine_name" => "Chateau A", "price" => "50")]
|
||||||
|
julia> YiemAgent.availableWineToText(vecd)
|
||||||
|
"1) wine_name:Chateau A,price:50 "
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function availableWineToText(vecd::Vector)::String
|
||||||
|
# Initialize an empty string to hold the final text
|
||||||
|
rowtext = ""
|
||||||
|
# Loop through each dictionary in the input vector
|
||||||
|
for (i, d) in enumerate(vecd)
|
||||||
|
# Iterate over all key-value pairs in the dictionary
|
||||||
|
temp = []
|
||||||
|
for (k, v) in d
|
||||||
|
# Append the formatted string to the text variable
|
||||||
|
t = "$k:$v"
|
||||||
|
push!(temp, t)
|
||||||
|
end
|
||||||
|
_rowtext = join(temp, ',')
|
||||||
|
rowtext *= "$i) $_rowtext "
|
||||||
|
end
|
||||||
|
|
||||||
|
return rowtext
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
prepareContext(state::agentState) -> Vector{agentMessage}
|
||||||
|
|
||||||
|
Returns a deep copy of the messages from the given `agentState`, ready
|
||||||
|
to be sent to the LLM. Override this function to inject additional
|
||||||
|
context — such as retrieved documents, current time, user preferences,
|
||||||
|
or any other relevant information — into the message list before
|
||||||
|
formatting and calling the LLM.
|
||||||
|
|
||||||
|
By default, returns an exact copy of `state.messages` without
|
||||||
|
modification.
|
||||||
|
|
||||||
|
# Arguments
|
||||||
|
- `state::agentState`: The current agent state containing conversation history
|
||||||
|
|
||||||
|
# Returns
|
||||||
|
- `Vector{agentMessage}`: A deep copy of the messages to be sent to the LLM
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
```julia
|
||||||
|
# Default: returns a deep copy of messages
|
||||||
|
prepareContext(state) == deepcopy(state.messages)
|
||||||
|
|
||||||
|
# Override to inject system context:
|
||||||
|
# function Base.prepareContext(state::agentState)
|
||||||
|
# msgs = deepcopy(state.messages)
|
||||||
|
# pushfirst!(msgs, textMessage("system", "You are a helpful assistant."))
|
||||||
|
# return msgs
|
||||||
|
# end
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
function prepareContext(state::agentState)::Vector{agentMessage}
|
||||||
|
messages = deepcopy(state.messages) # messages that will be send to LLM
|
||||||
|
|
||||||
|
#TODO adjust/modify and inject additional context into messages
|
||||||
|
|
||||||
|
return messages
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function formatMsgForLLM()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end # module util
|
||||||
Reference in New Issue
Block a user