Packages

A Gleam framework for building AI agents — type-safe, multi-provider, with tool calling, streaming, and simulation testing

Current section

Files

Jump to
glean src glean@run.erl
Raw

src/glean@run.erl

-module(glean@run).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glean/run.gleam").
-export([stream/4, generate/3, generate_messages/3, continue/4]).
-export_type([run_result/0, step_result/0, run_state/1, pending_tool_call/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type run_result() :: {run_result,
binary(),
list(glean@message:message()),
list(glean@provider:generated_content()),
glean@stream:usage(),
glean@error:finish_reason(),
integer(),
list(step_result())}.
-type step_result() :: {step_result,
integer(),
list(glean@provider:generated_content()),
glean@error:finish_reason(),
glean@stream:usage()}.
-type run_state(LGE) :: {run_state,
glean@agent:agent(LGE),
LGE,
list(glean@message:message()),
integer(),
glean@stream:usage(),
list(step_result())}.
-type pending_tool_call() :: {pending_tool_call, binary(), binary(), binary()}.
-file("src/glean/run.gleam", 134).
-spec build_initial_messages(glean@agent:agent(any()), binary()) -> list(glean@message:message()).
build_initial_messages(Agent, Prompt) ->
System_msgs = begin
_pipe = erlang:element(3, Agent),
gleam@list:map(_pipe, fun glean@message:system/1)
end,
lists:append(System_msgs, [glean@message:user(Prompt)]).
-file("src/glean/run.gleam", 142).
-spec prepend_system_prompts(
glean@agent:agent(any()),
list(glean@message:message())
) -> list(glean@message:message()).
prepend_system_prompts(Agent, Messages) ->
System_msgs = begin
_pipe = erlang:element(3, Agent),
gleam@list:map(_pipe, fun glean@message:system/1)
end,
lists:append(System_msgs, Messages).
-file("src/glean/run.gleam", 385).
-spec extract_tool_calls(list(glean@provider:generated_content())) -> list(pending_tool_call()).
extract_tool_calls(Content) ->
gleam@list:filter_map(Content, fun(C) -> case C of
{generated_tool_call, Id, Name, Args} ->
{ok, {pending_tool_call, Id, Name, Args}};
_ ->
{error, nil}
end end).
-file("src/glean/run.gleam", 399).
-spec extract_text(list(glean@provider:generated_content())) -> binary().
extract_text(Content) ->
_pipe = Content,
_pipe@1 = gleam@list:filter_map(_pipe, fun(C) -> case C of
{generated_text, Text} ->
{ok, Text};
_ ->
{error, nil}
end end),
gleam@list:fold(
_pipe@1,
<<""/utf8>>,
fun(Acc, Text@1) -> <<Acc/binary, Text@1/binary>> end
).
-file("src/glean/run.gleam", 548).
-spec tool_error_to_string(glean@error:glean_error()) -> binary().
tool_error_to_string(Err) ->
case Err of
{tool_execution_error, _, _, Reason} ->
Reason;
{tool_input_validation_error, _, _, Errors} ->
_pipe = Errors,
_pipe@1 = gleam@list:map(_pipe, fun(E) -> erlang:element(3, E) end),
gleam@list:fold(
_pipe@1,
<<"Validation errors: "/utf8>>,
fun(Acc, Msg) ->
<<<<Acc/binary, Msg/binary>>/binary, "; "/utf8>>
end
);
_ ->
<<"Tool execution failed"/utf8>>
end.
-file("src/glean/run.gleam", 492).
-spec execute_tool_with_retry(
run_state(LJA),
pending_tool_call(),
glean@tool:tool(LJA),
integer()
) -> {ok, glean@message:tool_result()} | {error, glean@error:glean_error()}.
execute_tool_with_retry(State, Call, Found_tool, Attempt) ->
Ctx = {tool_context,
erlang:element(3, State),
erlang:element(2, Call),
erlang:element(3, Call),
Attempt,
erlang:element(6, Found_tool),
erlang:element(5, State)},
case (erlang:element(5, Found_tool))(Ctx, erlang:element(4, Call)) of
{ok, Output} ->
{ok,
{tool_result_ok,
erlang:element(2, Call),
erlang:element(3, Call),
Output}};
{error, Tool_err} ->
case Attempt < erlang:element(6, Found_tool) of
true ->
case Tool_err of
{tool_not_found, _} ->
{ok,
{tool_result_error,
erlang:element(2, Call),
erlang:element(3, Call),
tool_error_to_string(Tool_err)}};
_ ->
execute_tool_with_retry(
State,
Call,
Found_tool,
Attempt + 1
)
end;
false ->
{ok,
{tool_result_error,
erlang:element(2, Call),
erlang:element(3, Call),
tool_error_to_string(Tool_err)}}
end
end.
-file("src/glean/run.gleam", 481).
-spec execute_single_tool(run_state(any()), pending_tool_call()) -> {ok,
glean@message:tool_result()} |
{error, glean@error:glean_error()}.
execute_single_tool(State, Call) ->
case glean@tool:find(
erlang:element(4, erlang:element(2, State)),
erlang:element(3, Call)
) of
{error, Err} ->
{error, Err};
{ok, Found_tool} ->
execute_tool_with_retry(State, Call, Found_tool, 0)
end.
-file("src/glean/run.gleam", 417).
-spec execute_tool_calls_loop(
run_state(any()),
list(pending_tool_call()),
list(glean@message:tool_result())
) -> {ok, list(glean@message:tool_result())} |
{error, glean@error:glean_error()}.
execute_tool_calls_loop(State, Calls, Results) ->
case Calls of
[] ->
{ok, lists:reverse(Results)};
[Call | Rest] ->
case execute_single_tool(State, Call) of
{error, Err} ->
{error, Err};
{ok, Result} ->
execute_tool_calls_loop(State, Rest, [Result | Results])
end
end.
-file("src/glean/run.gleam", 410).
-spec execute_tool_calls(run_state(any()), list(pending_tool_call())) -> {ok,
list(glean@message:tool_result())} |
{error, glean@error:glean_error()}.
execute_tool_calls(State, Calls) ->
execute_tool_calls_loop(State, Calls, []).
-file("src/glean/run.gleam", 183).
-spec process_generate_result(
run_state(any()),
glean@provider:generate_result()
) -> {ok, run_result()} | {error, glean@error:glean_error()}.
process_generate_result(State, Result) ->
Step_result = {step_result,
erlang:element(5, State),
erlang:element(2, Result),
erlang:element(3, Result),
erlang:element(4, Result)},
New_usage = glean@stream:add_usage(
erlang:element(6, State),
erlang:element(4, Result)
),
New_step_results = lists:append(erlang:element(7, State), [Step_result]),
Tool_calls = extract_tool_calls(erlang:element(2, Result)),
case Tool_calls of
[] ->
Text = extract_text(erlang:element(2, Result)),
{ok,
{run_result,
Text,
erlang:element(4, State),
erlang:element(2, Result),
New_usage,
erlang:element(3, Result),
erlang:element(5, State) + 1,
New_step_results}};
_ ->
Assistant_content = begin
_pipe = erlang:element(2, Result),
gleam@list:map(_pipe, fun(C) -> case C of
{generated_tool_call, Id, Name, Args} ->
{assistant_tool_call, Id, Name, Args};
{generated_text, Text@1} ->
{assistant_text, Text@1};
{generated_reasoning, Text@2} ->
{assistant_reasoning, Text@2}
end end)
end,
Messages_with_assistant = lists:append(
erlang:element(4, State),
[{assistant_message, Assistant_content}]
),
case execute_tool_calls(State, Tool_calls) of
{error, Err} ->
{error, Err};
{ok, Tool_results} ->
Messages_with_results = lists:append(
Messages_with_assistant,
[{tool_result_message, Tool_results}]
),
run_loop(
{run_state,
erlang:element(2, State),
erlang:element(3, State),
Messages_with_results,
erlang:element(5, State) + 1,
New_usage,
New_step_results}
)
end
end.
-file("src/glean/run.gleam", 155).
-spec run_loop(run_state(any())) -> {ok, run_result()} |
{error, glean@error:glean_error()}.
run_loop(State) ->
case erlang:element(5, State) >= erlang:element(7, erlang:element(2, State)) of
true ->
{error,
{max_steps_exceeded,
erlang:element(7, erlang:element(2, State)),
erlang:element(5, State)}};
false ->
Request = {generate_request,
erlang:element(4, State),
erlang:element(5, erlang:element(2, State)),
glean@tool:to_specs(erlang:element(4, erlang:element(2, State))),
erlang:element(6, erlang:element(2, State)),
text_format},
case (erlang:element(4, erlang:element(2, erlang:element(2, State))))(
Request
) of
{error, Err} ->
{error, Err};
{ok, Result} ->
process_generate_result(State, Result)
end
end.
-file("src/glean/run.gleam", 263).
-spec run_stream_loop(
run_state(any()),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, run_result()} | {error, glean@error:glean_error()}.
run_stream_loop(State, On_event) ->
case erlang:element(5, State) >= erlang:element(7, erlang:element(2, State)) of
true ->
{error,
{max_steps_exceeded,
erlang:element(7, erlang:element(2, State)),
erlang:element(5, State)}};
false ->
On_event({step_start, erlang:element(5, State)}),
Request = {generate_request,
erlang:element(4, State),
erlang:element(5, erlang:element(2, State)),
glean@tool:to_specs(erlang:element(4, erlang:element(2, State))),
erlang:element(6, erlang:element(2, State)),
text_format},
case (erlang:element(5, erlang:element(2, erlang:element(2, State))))(
Request,
On_event
) of
{error, Err} ->
{error, Err};
{ok, Result} ->
On_event({step_end, erlang:element(5, State)}),
process_stream_result(State, Result, On_event)
end
end.
-file("src/glean/run.gleam", 296).
-spec process_stream_result(
run_state(any()),
glean@provider:generate_result(),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, run_result()} | {error, glean@error:glean_error()}.
process_stream_result(State, Result, On_event) ->
Step_result = {step_result,
erlang:element(5, State),
erlang:element(2, Result),
erlang:element(3, Result),
erlang:element(4, Result)},
New_usage = glean@stream:add_usage(
erlang:element(6, State),
erlang:element(4, Result)
),
New_step_results = lists:append(erlang:element(7, State), [Step_result]),
Tool_calls = extract_tool_calls(erlang:element(2, Result)),
case Tool_calls of
[] ->
Text = extract_text(erlang:element(2, Result)),
On_event({finish, erlang:element(3, Result), New_usage}),
{ok,
{run_result,
Text,
erlang:element(4, State),
erlang:element(2, Result),
New_usage,
erlang:element(3, Result),
erlang:element(5, State) + 1,
New_step_results}};
_ ->
Assistant_content = begin
_pipe = erlang:element(2, Result),
gleam@list:map(_pipe, fun(C) -> case C of
{generated_tool_call, Id, Name, Args} ->
{assistant_tool_call, Id, Name, Args};
{generated_text, Text@1} ->
{assistant_text, Text@1};
{generated_reasoning, Text@2} ->
{assistant_reasoning, Text@2}
end end)
end,
Messages_with_assistant = lists:append(
erlang:element(4, State),
[{assistant_message, Assistant_content}]
),
case execute_tool_calls_with_events(State, Tool_calls, On_event) of
{error, Err} ->
{error, Err};
{ok, Tool_results} ->
Messages_with_results = lists:append(
Messages_with_assistant,
[{tool_result_message, Tool_results}]
),
run_stream_loop(
{run_state,
erlang:element(2, State),
erlang:element(3, State),
Messages_with_results,
erlang:element(5, State) + 1,
New_usage,
New_step_results},
On_event
)
end
end.
-file("src/glean/run.gleam", 434).
-spec execute_tool_calls_with_events(
run_state(any()),
list(pending_tool_call()),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, list(glean@message:tool_result())} |
{error, glean@error:glean_error()}.
execute_tool_calls_with_events(State, Calls, On_event) ->
execute_tool_calls_with_events_loop(State, Calls, [], On_event).
-file("src/glean/run.gleam", 442).
-spec execute_tool_calls_with_events_loop(
run_state(any()),
list(pending_tool_call()),
list(glean@message:tool_result()),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, list(glean@message:tool_result())} |
{error, glean@error:glean_error()}.
execute_tool_calls_with_events_loop(State, Calls, Results, On_event) ->
case Calls of
[] ->
{ok, lists:reverse(Results)};
[Call | Rest] ->
case execute_single_tool(State, Call) of
{error, Err} ->
{error, Err};
{ok, Result} ->
case Result of
{tool_result_ok, Tool_call_id, Tool_name, Output} ->
On_event(
{tool_result_event,
Tool_call_id,
Tool_name,
Output}
);
{tool_result_error,
Tool_call_id@1,
Tool_name@1,
Error_message} ->
On_event(
{tool_error_event,
Tool_call_id@1,
Tool_name@1,
Error_message}
)
end,
execute_tool_calls_with_events_loop(
State,
Rest,
[Result | Results],
On_event
)
end
end.
-file("src/glean/run.gleam", 94).
?DOC(" Run the agent with streaming. Calls on_event for each StreamEvent.\n").
-spec stream(
glean@agent:agent(LGO),
LGO,
binary(),
fun((glean@stream:stream_event()) -> nil)
) -> {ok, run_result()} | {error, glean@error:glean_error()}.
stream(Agent, Deps, Prompt, On_event) ->
Messages = build_initial_messages(Agent, Prompt),
run_stream_loop(
{run_state, Agent, Deps, Messages, 0, glean@stream:zero_usage(), []},
On_event
).
-file("src/glean/run.gleam", 60).
?DOC(" Run the agent with a simple text prompt.\n").
-spec generate(glean@agent:agent(LGF), LGF, binary()) -> {ok, run_result()} |
{error, glean@error:glean_error()}.
generate(Agent, Deps, Prompt) ->
Messages = build_initial_messages(Agent, Prompt),
run_loop(
{run_state, Agent, Deps, Messages, 0, glean@stream:zero_usage(), []}
).
-file("src/glean/run.gleam", 77).
?DOC(" Run the agent with pre-built messages (for multi-turn conversations).\n").
-spec generate_messages(
glean@agent:agent(LGJ),
LGJ,
list(glean@message:message())
) -> {ok, run_result()} | {error, glean@error:glean_error()}.
generate_messages(Agent, Deps, Messages) ->
Messages@1 = prepend_system_prompts(Agent, Messages),
run_loop(
{run_state, Agent, Deps, Messages@1, 0, glean@stream:zero_usage(), []}
).
-file("src/glean/run.gleam", 115).
?DOC(" Continue a previous run with additional user input.\n").
-spec continue(glean@agent:agent(LGS), LGS, run_result(), binary()) -> {ok,
run_result()} |
{error, glean@error:glean_error()}.
continue(Agent, Deps, Previous, Prompt) ->
Messages = lists:append(
erlang:element(3, Previous),
[glean@message:user(Prompt)]
),
run_loop(
{run_state, Agent, Deps, Messages, 0, glean@stream:zero_usage(), []}
).