update
This commit is contained in:
+102
-84
@@ -1,11 +1,11 @@
|
||||
module agentCore
|
||||
|
||||
# export prompt
|
||||
export _agent_loop
|
||||
|
||||
using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization,
|
||||
DataFrames, Serde
|
||||
using GeneralUtils
|
||||
using ..type, ..util, ..llmfunction
|
||||
using ..type, ..utils, ..llmfunction
|
||||
|
||||
# ---------------------------------------------- 100 --------------------------------------------- #
|
||||
|
||||
@@ -74,7 +74,6 @@ function _agent_loop(agent::yiemAgent)
|
||||
agent.followUpChannel -> nothing
|
||||
"""
|
||||
|
||||
|
||||
while true
|
||||
result = nothing
|
||||
msg = nothing
|
||||
@@ -106,7 +105,7 @@ function _agent_loop(agent::yiemAgent)
|
||||
break
|
||||
end
|
||||
|
||||
# make active
|
||||
# start _process_message loop
|
||||
if agent._state.activeRun == false
|
||||
# Dispatch message through the processing pipeline
|
||||
processing_task = @spawn _process_message(agent)
|
||||
@@ -122,8 +121,8 @@ function _agent_loop(agent::yiemAgent)
|
||||
put!(agent.inputChannel, followMsg)
|
||||
end
|
||||
end
|
||||
|
||||
continue # continue to process user message in the next loop
|
||||
|
||||
elseif typeof(processing_task) == Task && istaskdone(processing_task) == true
|
||||
# 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.
|
||||
@@ -135,8 +134,8 @@ function _agent_loop(agent::yiemAgent)
|
||||
end
|
||||
result = fetch(processing_task)
|
||||
put!(agent.outputChannel, result)
|
||||
agent._state.activeRun = false
|
||||
processing_task = nothing
|
||||
agent._state.activeRun = false # reset
|
||||
processing_task = nothing # reset
|
||||
end
|
||||
end
|
||||
catch e
|
||||
@@ -163,7 +162,7 @@ should be implemented. Currently a placeholder that echoes back the received mes
|
||||
- Implement the full processing pipeline:
|
||||
1. Add `msg` to `agent._state.messages`
|
||||
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)
|
||||
5. If agent has tools, handle tool calls in a loop
|
||||
6. Build `assistantMessage` and return it
|
||||
@@ -174,93 +173,116 @@ julia> # Currently returns a placeholder echo response
|
||||
```
|
||||
"""
|
||||
function _process_message(agent::yiemAgent)::assistantMessage
|
||||
# WORKING
|
||||
|
||||
# loop until llmCall() response didn't use tool calls
|
||||
while
|
||||
# take every messages from agent.inputChannel, convert them into userMessage
|
||||
# and add them to agent._state.messages
|
||||
final_response = nothing
|
||||
while true
|
||||
# call agent.prepareContext()
|
||||
preparedContext = agent.prepareContext(agent._state)
|
||||
|
||||
# call agent.preprocessContext()
|
||||
|
||||
# Call agent.formatMsgForLLM(agent._state) to format for LLM
|
||||
#WORKING Call agent.formatMsgForLLM(agent._state) to format for LLM
|
||||
formatted_messages = agent.formatMsgForLLM(preparedContext)
|
||||
|
||||
# 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()
|
||||
batch = executeToolCalls(context, response, tool_call_list, config, signal, emit)
|
||||
|
||||
# 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)
|
||||
# break out of while loop
|
||||
|
||||
if batch.terminate
|
||||
# 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
|
||||
|
||||
# Build assistantMessage and return it
|
||||
|
||||
# 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(),
|
||||
)
|
||||
return final_response
|
||||
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
|
||||
|
||||
@@ -1040,10 +1062,6 @@ end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user