Packages
A Fair Source multi-agent runtime with deterministic agent scoring and replayable run history.
Retired package: Deprecated - superseded — operator console moved to the Syntropy app
Current section
Files
Jump to
Current section
Files
lib/syntropy/llm_client.ex
defmodule Syntropy.LLMClient do
@moduledoc """
Provider boundary for Syntropy agent completions.
"""
alias Syntropy.LLMClient.Config
@max_completion_chars 12_000
@type agent_payload :: %{
required(:id) => String.t(),
required(:name) => String.t(),
required(:perspective) => String.t(),
optional(atom()) => term()
}
@spec complete(agent_payload(), String.t()) :: {:ok, String.t()} | {:error, term()}
def complete(agent, prompt) do
config = config()
with :ok <- validate_config(config) do
module = provider_module(config)
started_at = System.monotonic_time(:millisecond)
result =
config
|> module.complete(agent, prompt)
|> normalize_completion()
duration_ms = System.monotonic_time(:millisecond) - started_at
Syntropy.Telemetry.llm_request_stop(
Config.provider_name(config),
config.model,
result,
duration_ms
)
result
end
end
@spec config() :: Config.t()
def config do
Config.load()
end
@spec validate_config() :: :ok | {:error, [String.t()]}
def validate_config do
config()
|> validate_config()
end
@spec validate_config(Config.t()) :: :ok | {:error, [String.t()]}
def validate_config(config) do
Config.validate(config)
end
@spec provider_name() :: String.t()
def provider_name do
config()
|> Config.provider_name()
end
@spec call_timeout() :: pos_integer()
def call_timeout do
config()
|> Config.call_timeout()
end
defp provider_module(%Config{provider_module: provider_module})
when is_atom(provider_module) and not is_nil(provider_module) do
provider_module
end
defp provider_module(%Config{provider: :mock}), do: Syntropy.LLMClient.Providers.Mock
defp provider_module(%Config{provider: :openai}), do: Syntropy.LLMClient.Providers.OpenAI
defp provider_module(%Config{provider: :ollama}), do: Syntropy.LLMClient.Providers.Ollama
defp provider_module(%Config{}), do: Syntropy.LLMClient.Providers.Mock
defp normalize_completion({:ok, completion}) when is_binary(completion) do
completion =
completion
|> String.trim()
|> String.slice(0, @max_completion_chars)
case completion do
"" -> {:error, :empty_provider_response}
normalized -> {:ok, normalized}
end
end
defp normalize_completion({:ok, other}), do: {:error, {:invalid_provider_response, other}}
defp normalize_completion({:error, _reason} = error), do: error
end