text message process works
This commit is contained in:
+23
-6
@@ -227,8 +227,11 @@ function _agentLoop(agent::yiemAgent)
|
|||||||
else # _processMessage() done and no followUp message.
|
else # _processMessage() done and no followUp message.
|
||||||
agent.agentEventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))")
|
agent.agentEventSink("_agentLoop 4 agent._state.messages length $(length(agent._state.messages))")
|
||||||
result = agent._state.messages[end]
|
result = agent._state.messages[end]
|
||||||
|
filtered_content = [c for c in result.content if !(c isa reasoningContent)]
|
||||||
put!(agent.outputChannel, result)
|
put!(agent.outputChannel, result)
|
||||||
agent.agentEventSink(result.content[1].text)
|
if !isempty(filtered_content) && filtered_content[1] isa textContent
|
||||||
|
agent.agentEventSink(filtered_content[1].text)
|
||||||
|
end
|
||||||
processingTask = nothing # reset
|
processingTask = nothing # reset
|
||||||
newUserMsg = nothing # reset
|
newUserMsg = nothing # reset
|
||||||
result = nothing # reset
|
result = nothing # reset
|
||||||
@@ -381,7 +384,7 @@ function _processMessage(
|
|||||||
# Extract tool calls from LLM response content blocks
|
# Extract tool calls from LLM response content blocks
|
||||||
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
|
hasToolCalls, toolCallList, assistant_msg = _extractToolCalls(response)
|
||||||
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))")
|
agentEventSink("hasToolCalls: $hasToolCalls\ntoolCallList: $toolCallList _state.messages length $(length(agentMsgHistory))")
|
||||||
agentEventSink(string(assistant_msg))
|
agentEventSink("assistant_msg " * string(assistant_msg))
|
||||||
agentEventSink("_processMessage 11-1")
|
agentEventSink("_processMessage 11-1")
|
||||||
|
|
||||||
# Add assistant message (tool calls or text) to history for next LLM turn
|
# Add assistant message (tool calls or text) to history for next LLM turn
|
||||||
@@ -406,6 +409,7 @@ function _processMessage(
|
|||||||
|
|
||||||
# save toolResults to messages
|
# save toolResults to messages
|
||||||
for toolResult in toolResultBatch.messages
|
for toolResult in toolResultBatch.messages
|
||||||
|
agentEventSink("toolResult " * string(toolResult))
|
||||||
push!(agentMsgHistory, toolResult)
|
push!(agentMsgHistory, toolResult)
|
||||||
end
|
end
|
||||||
agentEventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))")
|
agentEventSink("_processMessage 14 _state.messages length $(length(agentMsgHistory))")
|
||||||
@@ -554,7 +558,9 @@ The message is constructed from:
|
|||||||
`(hasToolCalls, toolCallList, message)` where message is `assistantMessageToolCall`
|
`(hasToolCalls, toolCallList, message)` where message is `assistantMessageToolCall`
|
||||||
when tool calls exist, `assistantMessage` otherwise
|
when tool calls exist, `assistantMessage` otherwise
|
||||||
|
|
||||||
# Example response = JSON.Object{String, Any}("finish_reason" => "tool_calls", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "", "reasoning_content" => "Here's a thinking process:\n\n1. **Identify User Request**: The user is asking for the weather in Bangkok.\n2. **Locate Relevant Tool**: I have a `getWeather` function available.\n3. **Check Function Parameters**:\n - `city` (required): City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\n - `units` (optional, default \"celsius\"): Temperature scale (\"celsius\" or \"fahrenheit\")\n4. **Prepare Parameters**:\n - `city`: \"Bangkok, Thailand\" (adding country for clarity, though just \"Bangkok\" might work, following the example format is safer)\n - `units`: Not specified, so I'll use the default (\"celsius\")\n5. **Execute Tool Call**: Call `getWeather` with `city: \"Bangkok, Thailand\"`\n6. **Anticipate Response**: The function will return current weather and forecast data for Bangkok. I'll then format it nicely for the user.\n - *Self-Correction/Verification during thought*: The prompt says \"city: City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\". I'll use \"Bangkok, Thailand\". The `units` parameter is optional, so I'll omit it to use the default.\n - Proceed with tool call.✅\n", "tool_calls" => Any[JSON.Object{String, Any}("type" => "function", "function" => JSON.Object{String, Any}("name" => "getWeather", "arguments" => "{\"city\":\"Bangkok, Thailand\"}"), "id" => "6fOilR5QPcdppbAAHluhkRyUDu3oWMAL")]))
|
# Example
|
||||||
|
1) llm_useTool = JSON.Object{String, Any}("finish_reason" => "tool_calls", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "", "reasoning_content" => "Here's a thinking process:\n\n1. **Identify User Request**: The user is asking for the weather in Bangkok.\n2. **Locate Relevant Tool**: I have a `getWeather` function available.\n3. **Check Function Parameters**:\n - `city` (required): City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\n - `units` (optional, default \"celsius\"): Temperature scale (\"celsius\" or \"fahrenheit\")\n4. **Prepare Parameters**:\n - `city`: \"Bangkok, Thailand\" (adding country for clarity, though just \"Bangkok\" might work, following the example format is safer)\n - `units`: Not specified, so I'll use the default (\"celsius\")\n5. **Execute Tool Call**: Call `getWeather` with `city: \"Bangkok, Thailand\"`\n6. **Anticipate Response**: The function will return current weather and forecast data for Bangkok. I'll then format it nicely for the user.\n - *Self-Correction/Verification during thought*: The prompt says \"city: City and country, e.g., 'San Francisco, CA' or 'Tokyo, Japan'\". I'll use \"Bangkok, Thailand\". The `units` parameter is optional, so I'll omit it to use the default.\n - Proceed with tool call.✅\n", "tool_calls" => Any[JSON.Object{String, Any}("type" => "function", "function" => JSON.Object{String, Any}("name" => "getWeather", "arguments" => "{\"city\":\"Bangkok, Thailand\"}"), "id" => "6fOilR5QPcdppbAAHluhkRyUDu3oWMAL")]))
|
||||||
|
2) llm_notUseTool = JSON.Object{String, Any}("finish_reason" => "stop", "index" => 0, "message" => JSON.Object{String, Any}("role" => "assistant", "content" => "The weather in London, UK is currently Sunny with a temperature of 22°C.", "reasoning_content" => "The user asked for the weather in London.\nI called the `getWeather` tool for London, UK.\nThe response indicates it's Sunny and 22°C.\nI will convey this information to the user.\n"))
|
||||||
"""
|
"""
|
||||||
function _extractToolCalls(response)
|
function _extractToolCalls(response)
|
||||||
hasToolCalls = false
|
hasToolCalls = false
|
||||||
@@ -642,14 +648,15 @@ function _extractToolCalls(response)
|
|||||||
end
|
end
|
||||||
if reasoning_text isa String
|
if reasoning_text isa String
|
||||||
reasoning_text = reasoning_text
|
reasoning_text = reasoning_text
|
||||||
elseif reasoning_text isa textContent
|
elseif reasoning_text isa reasoningContent
|
||||||
reasoning_text = reasoning_text.text
|
reasoning_text = reasoning_text.text
|
||||||
else
|
else
|
||||||
reasoning_text = ""
|
reasoning_text = ""
|
||||||
end
|
end
|
||||||
reasoning_content = !isempty(reasoning_text) ? [textContent(reasoning_text)] : textContent[]
|
reasoning_content = !isempty(reasoning_text) ? [reasoningContent(reasoning_text)] : reasoningContent[]
|
||||||
|
|
||||||
# Collect content blocks from response.content array (Format 2: OpenAI-style)
|
# Collect content blocks from response.content array (Format 2: OpenAI-style)
|
||||||
|
# or as a plain string (Format 1: LMStudio.jl non-tool-calls response)
|
||||||
content_from_response = get(response, "content", nothing)
|
content_from_response = get(response, "content", nothing)
|
||||||
content_blocks = Vector{messageContent}()
|
content_blocks = Vector{messageContent}()
|
||||||
if content_from_response isa Vector
|
if content_from_response isa Vector
|
||||||
@@ -662,12 +669,22 @@ function _extractToolCalls(response)
|
|||||||
elseif get(block, "type", "") == "tool_calls"
|
elseif get(block, "type", "") == "tool_calls"
|
||||||
# tool_calls blocks — don't add text content for these
|
# tool_calls blocks — don't add text content for these
|
||||||
elseif get(block, "type", "") == "reasoning"
|
elseif get(block, "type", "") == "reasoning"
|
||||||
push!(content_blocks, textContent(get(block, "text", "")))
|
push!(content_blocks, reasoningContent(get(block, "text", "")))
|
||||||
else
|
else
|
||||||
push!(content_blocks, textContent(get(block, "text", "")))
|
push!(content_blocks, textContent(get(block, "text", "")))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elseif content_from_response isa AbstractString && !isempty(content_from_response)
|
||||||
|
push!(content_blocks, textContent(content_from_response))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Format 1 fallback: response["message"]["content"] as plain string
|
||||||
|
if isempty(content_blocks) && msg !== nothing && msg isa AbstractDict
|
||||||
|
msg_content = get(msg, "content", nothing)
|
||||||
|
if msg_content isa AbstractString && !isempty(msg_content)
|
||||||
|
push!(content_blocks, textContent(msg_content))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Combine reasoning_content field + content array blocks, deduplicating reasoning
|
# Combine reasoning_content field + content array blocks, deduplicating reasoning
|
||||||
|
|||||||
+13
-10
@@ -4,8 +4,8 @@
|
|||||||
messageContent, agentMessage, agent,
|
messageContent, agentMessage, agent,
|
||||||
# Model types
|
# Model types
|
||||||
modelCost, llmModel, llmUsage,
|
modelCost, llmModel, llmUsage,
|
||||||
# Message content types
|
# Message content types
|
||||||
textContent, imageContent,
|
textContent, imageContent, reasoningContent,
|
||||||
# Message types
|
# Message types
|
||||||
userMessage, assistantMessageToolCall, assistantMessage, toolResultMessage,
|
userMessage, assistantMessageToolCall, assistantMessage, toolResultMessage,
|
||||||
# Tool types
|
# Tool types
|
||||||
@@ -31,6 +31,13 @@ using GeneralUtils
|
|||||||
|
|
||||||
const Timestamp = DateTime
|
const Timestamp = DateTime
|
||||||
|
|
||||||
|
struct agentToolCall # 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
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------ #
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# LLM model info #
|
# LLM model info #
|
||||||
# ------------------------------------------------------------------------------------------------ #
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
@@ -75,6 +82,10 @@ struct imageContent <: messageContent # Image message content
|
|||||||
mimeType::String # MIME type (e.g., "image/png")
|
mimeType::String # MIME type (e.g., "image/png")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
struct reasoningContent <: messageContent # LLM reasoning/thinking content
|
||||||
|
text::String # The reasoning text
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------------------------ #
|
# ------------------------------------------------------------------------------------------------ #
|
||||||
# Message types #
|
# Message types #
|
||||||
@@ -404,14 +415,6 @@ function agentState(
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
struct agentToolCall # 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.
|
Context for preparing the next conversation turn.
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import YiemAgent.agentCore: _extractToolCalls
|
|||||||
@test assistant_msg.role == "assistant"
|
@test assistant_msg.role == "assistant"
|
||||||
@test assistant_msg.stopReason == "tool_calls"
|
@test assistant_msg.stopReason == "tool_calls"
|
||||||
@test length(assistant_msg.content) == 1
|
@test length(assistant_msg.content) == 1
|
||||||
@test assistant_msg.content[1] isa textContent
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
@test assistant_msg.content[1].text == "Let me check the weather."
|
@test assistant_msg.content[1].text == "Let me check the weather."
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -241,7 +241,9 @@ import YiemAgent.agentCore: _extractToolCalls
|
|||||||
@test has_toolcalls == false
|
@test has_toolcalls == false
|
||||||
@test length(tc_list) == 0
|
@test length(tc_list) == 0
|
||||||
@test length(assistant_msg.content) == 2
|
@test length(assistant_msg.content) == 2
|
||||||
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
@test assistant_msg.content[1].text == "Thinking..."
|
@test assistant_msg.content[1].text == "Thinking..."
|
||||||
|
@test assistant_msg.content[2] isa textContent
|
||||||
@test assistant_msg.content[2].text == "Here's the answer."
|
@test assistant_msg.content[2].text == "Here's the answer."
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -307,7 +309,9 @@ import YiemAgent.agentCore: _extractToolCalls
|
|||||||
)
|
)
|
||||||
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
|
||||||
@test length(assistant_msg.content) == 2
|
@test length(assistant_msg.content) == 2
|
||||||
|
@test assistant_msg.content[1] isa reasoningContent
|
||||||
@test assistant_msg.content[1].text == "internal thought"
|
@test assistant_msg.content[1].text == "internal thought"
|
||||||
|
@test assistant_msg.content[2] isa textContent
|
||||||
@test assistant_msg.content[2].text == "output"
|
@test assistant_msg.content[2].text == "output"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user