got some tool calling running

This commit is contained in:
2026-03-05 22:06:58 -07:00
parent cacae19f74
commit b89d4e5a28
8 changed files with 495 additions and 151 deletions

View File

@@ -0,0 +1,45 @@
defmodule ElixirAi.ChatUtils do
require Logger
import ElixirAi.AiUtils.StreamLineUtils
def request_ai_response(server, messages, tools) do
Task.start(fn ->
api_url = Application.fetch_env!(:elixir_ai, :ai_endpoint)
api_key = Application.fetch_env!(:elixir_ai, :ai_token)
model = Application.fetch_env!(:elixir_ai, :ai_model)
tool_definition = tools |> Enum.map(fn {_name, definition} -> definition end)
body = %{
model: model,
stream: true,
messages: messages |> Enum.map(&api_message/1),
tools: tool_definition
}
headers = [{"authorization", "Bearer #{api_key}"}]
case Req.post(api_url,
json: body,
headers: headers,
into: fn {:data, data}, acc ->
data
|> String.split("\n")
|> Enum.each(&handle_stream_line(server, &1))
{:cont, acc}
end
) do
{:ok, _} ->
:ok
{:error, reason} ->
IO.warn("AI request failed: #{inspect(reason)}")
end
end)
end
def api_message(%{role: role, content: content}) do
%{role: Atom.to_string(role), content: content}
end
end

View File

@@ -0,0 +1,167 @@
defmodule ElixirAi.AiUtils.StreamLineUtils do
require Logger
def handle_stream_line(_server, "") do
:ok
end
def handle_stream_line(_server, "data: [DONE]") do
# send(server, :ai_stream_done)
:ok
end
def handle_stream_line(server, "data: " <> json) do
case Jason.decode(json) do
{:ok, body} ->
# Logger.debug("Received AI chunk: #{inspect(body)}")
handle_stream_line(server, body)
other ->
Logger.error("Failed to decode AI response chunk: #{inspect(other)}")
:ok
end
end
# first streamed response
def handle_stream_line(server, %{
"choices" => [%{"delta" => %{"content" => nil, "role" => "assistant"}}],
"id" => id
}) do
send(
server,
{:start_new_ai_response, id}
)
end
# last streamed response
def handle_stream_line(server, %{
"choices" => [%{"finish_reason" => "stop"}],
"id" => id
}) do
send(
server,
{:ai_stream_finish, id}
)
end
# streamed in reasoning
def handle_stream_line(server, %{
"choices" => [
%{
"delta" => %{"reasoning_content" => reasoning_content},
"finish_reason" => nil
}
],
"id" => id
}) do
send(
server,
{:ai_reasoning_chunk, id, reasoning_content}
)
end
# streamed in text
def handle_stream_line(server, %{
"choices" => [
%{
"delta" => %{"content" => reasoning_content},
"finish_reason" => nil
}
],
"id" => id
}) do
send(
server,
{:ai_text_chunk, id, reasoning_content}
)
end
# start tool call
def handle_stream_line(server, %{
"choices" => [
%{
"delta" => %{
"tool_calls" => [
%{
"function" => %{
"name" => tool_name,
"arguments" => tool_args_start
}
}
]
},
"finish_reason" => nil,
"index" => tool_index
}
],
"id" => id
}) do
send(
server,
{:ai_tool_call_start, id, {tool_name, tool_args_start, tool_index}}
)
end
# middle tool call
def handle_stream_line(server, %{
"choices" => [
%{
"delta" => %{
"tool_calls" => [
%{
"function" => %{
"arguments" => tool_args_diff
}
}
]
},
"finish_reason" => nil,
"index" => tool_index
}
],
"id" => id
}) do
send(
server,
{:ai_tool_call_middle, id, {tool_args_diff, tool_index}}
)
end
# end tool call
def handle_stream_line(server, %{
"choices" => [
%{
"delta" => %{},
"finish_reason" => "tool_calls",
"index" => tool_index
}
],
"id" => id
}) do
send(
server,
{:ai_tool_call_end, id, tool_index}
)
end
def handle_stream_line(_server, %{"error" => error_info}) do
Logger.error("Received error from AI stream: #{inspect(error_info)}")
:ok
end
def handle_stream_line(server, json) when is_binary(json) do
case Jason.decode(json) do
{:ok, body} ->
handle_stream_line(server, body)
_ ->
Logger.warning("Received unmatched stream line: #{inspect(json)}")
:ok
end
end
def handle_stream_line(_server, unmatched_message) do
Logger.warning("Received unmatched stream line: #{inspect(unmatched_message)}")
:ok
end
end