Current section

Files

Jump to
genai_core lib gen_ai thread state.ex
Raw

lib/gen_ai/thread/state.ex

defmodule GenAI.Thread.State do
@vsn 1.0
defstruct [
model: [],
settings: %{},
tools: %{},
model_settings: %{},
provider_settings: %{},
messages: [],
vsn: @vsn
]
defimpl GenAI.Thread.StateProtocol do
defp ok(response), do: {:ok, response}
defp effective_value_fetch_success(response, updated_state), do: {:ok, response, updated_state}
@doc """
Add a model selector/constraint
"""
def with_model(state, model)
def with_model(state, model) do
state
|> update_in([Access.key(:model)], & [model | (&1 || []) ])
|> ok()
end
@doc """
Add a setting selector/constraint
"""
def with_setting(state, setting, value)
def with_setting(state, setting, value) do
state
|> update_in([Access.key(:settings), setting], & [value | (&1 || []) ])
|> ok()
end
@doc """
Add multiple setting selectors/constraints
"""
def with_settings(state, settings)
def with_settings(state, settings) when is_list(settings) do
settings
|> Enum.reduce_while({:ok, state},
fn
{setting, value}, {:ok, state} -> {:cont, GenAI.Thread.StateProtocol.with_setting(state, setting, value)}
_, error -> {:halt, error}
end
)
end
@doc """
Add a provider specific setting selector/constraint
"""
def with_provider_setting(state, provider, setting, value)
def with_provider_setting(state, provider, setting, value) do
state
|> update_in(
[Access.key(:provider_settings), provider],
fn
nil -> %{setting => [value]}
x -> update_in(x, [setting], & [value | (&1 || []) ])
end
)
|> ok()
end
@doc """
Add a provider specific settings selector/constraint
"""
def with_provider_settings(state, provider, settings)
def with_provider_settings(state, provider, settings) when is_list(settings) do
settings
|> Enum.reduce_while({:ok, state},
fn
{setting, value}, {:ok, state} -> {:cont, GenAI.Thread.StateProtocol.with_provider_setting(state, provider, setting, value)}
_, error -> {:halt, error}
end
)
end
@doc """
Add a model specific setting selector/constraint
"""
def with_model_setting(state, model, setting, value)
def with_model_setting(state, model, setting, value) do
with {:ok, m} <- GenAI.ModelProtocol.model(model),
{:ok, p} <- GenAI.ModelProtocol.provider(model) do
key = {p, m}
state
|> update_in(
[Access.key(:model_settings), key],
fn
nil -> %{setting => [value]}
x -> update_in(x, [setting], & [value | (&1 || []) ])
end
)
|> ok()
end
end
@doc """
Add a model specific setting selector/constraint
"""
def with_model_settings(state, model, settings)
def with_model_settings(state, model, settings) when is_list(settings) do
settings
|> Enum.reduce_while({:ok, state},
fn
{setting, value}, {:ok, state} -> {:cont, GenAI.Thread.StateProtocol.with_model_setting(state, model, setting, value)}
_, error -> {:halt, error}
end
)
end
@doc """
Add a tool
"""
def with_tool(state, tool)
def with_tool(state, tool) do
with {:ok, name} <- GenAI.ToolProtocol.name(tool) do
state
|> update_in([Access.key(:tools), name], & [tool | (&1 || []) ])
|> ok()
end
end
@doc """
Add a tools
"""
def with_tools(state, tools)
def with_tools(state, nil), do: {:ok, state}
def with_tools(state, tools) when is_list(tools) do
tools
|> Enum.reduce_while({:ok, state},
fn
tool, {:ok, state} -> {:cont, GenAI.Thread.StateProtocol.with_tool(state, tool)}
_, error -> {:halt, error}
end
)
end
@doc """
Add message
"""
def with_message(state, message)
def with_message(state, message) do
state
|> update_in(
[Access.key(:messages)], & [message | (&1 || []) ]
)
|> ok()
end
@doc """
Add messages
"""
def with_messages(state, messages)
def with_messages(state, messages) when is_list(messages) do
state
|> update_in(
[Access.key(:messages)], & Enum.reverse(messages) ++ (&1 || [])
)
|> ok()
end
@doc """
Obtain the effective model as of current state.
@note temporary logic - pending support for context specific dynamic selection
"""
def model(state) do
with %{model: [effective_model|_]} <- state do
effective_value_fetch_success(effective_model, state)
else
_ -> {:error, :not_set}
end
end
@doc """
Obtain the effective settings as of current state.
@note temporary logic - pending support for context specific dynamic selection
"""
def settings(state) do
Enum.map(state.settings,
fn
{{:__multi__, k}, v} ->
Enum.map(v, & {k, &1})
{k, [v|_]} -> {k, v}
end
)
|> List.flatten()
|> effective_value_fetch_success(state)
end
def model_settings(state, model) do
with {:ok, m} <- GenAI.ModelProtocol.model(model),
{:ok, p} <- GenAI.ModelProtocol.provider(model) do
key = {p, m}
with settings = %{} <- state.model_settings[key] do
Enum.map(settings,
fn
{{:__multi__, k}, v} -> {k, v}
{k, [v|_]} -> {k, v}
end
)
|> effective_value_fetch_success(state)
else
_ ->
effective_value_fetch_success([], state)
end
end
end
@doc """
Obtain the effective provider settings as of current state.
@note temporary logic - pending support for context specific dynamic selection
"""
def provider_settings(state, provider) do
with settings = %{} <- state.provider_settings[provider] do
Enum.map(settings,
fn
{{:__multi__, k}, v} -> {k, v}
{k, [v|_]} -> {k, v}
end
)
|> effective_value_fetch_success(state)
else
_ ->
effective_value_fetch_success([], state)
end
end
def messages(state, provider) do
unless state.messages == [] do
{messages, state} =
state.messages
|> Enum.reverse()
|> Enum.map_reduce(state,
fn
message, state ->
case GenAI.ProviderBehaviour.format_message(provider, message, state) do
{:ok, message, state} -> {message, state}
error = {:error, _} -> {error, state}
end
end
)
errors = Enum.filter(messages,
fn
{:error, _} -> true
_ -> false
end)
if errors == [] do
messages |> effective_value_fetch_success(state)
else
{:error, {:format_messages, errors}}
end
else
{:ok, [], state}
end
end
def tools(state, provider) do
unless state.tools == %{} do
{tools, state} = Enum.map_reduce(state.tools, state,
fn
# Temp logic take top
{_,[tool|_]}, state ->
case GenAI.ProviderBehaviour.format_tool(provider, tool, state) do
{:ok, tool, state} -> {tool, state}
error = {:error, _} -> {error, state}
end
end
)
errors = Enum.filter(tools,
fn
{:error, _} -> true
_ -> false
end)
if errors == [] do
tools |> effective_value_fetch_success(state)
else
{:error, {:format_tools, errors}}
end
else
{:ok, nil, state}
end
end
end
end