Commit c4507e7
Changed files (1)
lib
net
llm
lib/net/llm/claude.rb
@@ -28,12 +28,13 @@ module Net
end
def fetch(messages, tools = [], &block)
+ system_message, user_messages = extract_system_message(messages)
anthropic_tools = tools.empty? ? nil : tools.map { |t| normalize_tool_for_anthropic(t) }
if block_given?
- fetch_streaming(messages, anthropic_tools, &block)
+ fetch_streaming(user_messages, anthropic_tools, system: system_message, &block)
else
- fetch_non_streaming(messages, anthropic_tools)
+ fetch_non_streaming(user_messages, anthropic_tools, system: system_message)
end
end
@@ -62,7 +63,7 @@ module Net
def stream_request(payload, &block)
http.post(endpoint, headers: headers, body: payload) do |response|
- raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
+ raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
buffer = ""
response.read_body do |chunk|
@@ -102,8 +103,52 @@ module Net
event
end
- def fetch_non_streaming(messages, tools)
- result = self.messages(messages, tools: tools)
+ def extract_system_message(messages)
+ system_msg = messages.find { |m| m[:role] == "system" || m["role"] == "system" }
+ system_content = system_msg ? (system_msg[:content] || system_msg["content"]) : nil
+ other_messages = messages.reject { |m| m[:role] == "system" || m["role"] == "system" }
+ normalized_messages = normalize_messages_for_claude(other_messages)
+ [system_content, normalized_messages]
+ end
+
+ def normalize_messages_for_claude(messages)
+ messages.map do |msg|
+ role = msg[:role] || msg["role"]
+ tool_calls = msg[:tool_calls] || msg["tool_calls"]
+
+ if role == "tool"
+ {
+ role: "user",
+ content: [{
+ type: "tool_result",
+ tool_use_id: msg[:tool_call_id] || msg["tool_call_id"],
+ content: msg[:content] || msg["content"]
+ }]
+ }
+ elsif role == "assistant" && tool_calls&.any?
+ content = []
+ text = msg[:content] || msg["content"]
+ content << { type: "text", text: text } if text && !text.empty?
+ tool_calls.each do |tc|
+ func = tc[:function] || tc["function"] || {}
+ args = func[:arguments] || func["arguments"]
+ input = args.is_a?(String) ? (JSON.parse(args) rescue {}) : (args || {})
+ content << {
+ type: "tool_use",
+ id: tc[:id] || tc["id"],
+ name: func[:name] || func["name"] || tc[:name] || tc["name"],
+ input: input
+ }
+ end
+ { role: "assistant", content: content }
+ else
+ msg
+ end
+ end
+ end
+
+ def fetch_non_streaming(messages, tools, system: nil)
+ result = self.messages(messages, system: system, tools: tools)
return result if result["code"]
{
@@ -115,13 +160,13 @@ module Net
}
end
- def fetch_streaming(messages, tools, &block)
+ def fetch_streaming(messages, tools, system: nil, &block)
content = ""
thinking = ""
tool_calls = []
stop_reason = :end_turn
- self.messages(messages, tools: tools) do |event|
+ self.messages(messages, system: system, tools: tools) do |event|
case event["type"]
when "content_block_start"
if event.dig("content_block", "type") == "tool_use"