PtcRunner.SubAgent.LLMResolver (PtcRunner v0.9.0)

Copy Markdown View Source

LLM resolution and invocation for SubAgents.

Handles calling LLMs that can be either functions or atoms, with support for LLM registry lookups for atom-based LLM references (like :haiku or :sonnet).

LLM responses are normalized to a consistent format:

  • Plain string responses become %{content: string, tokens: nil}
  • Map responses with :content key preserve tokens if present

Summary

Types

Normalized LLM response with content, optional token counts, and optional tool calls.

Functions

Normalize an LLM response to a consistent format.

Resolve and invoke an LLM, handling both functions and atom references.

Calculate total tokens from input and output token counts.

Types

normalized_response()

@type normalized_response() ::
  %{content: String.t() | nil, tokens: map() | nil}
  | %{content: String.t() | nil, tokens: map() | nil, tool_calls: [map()]}

Normalized LLM response with content, optional token counts, and optional tool calls.

For tool calling mode, the response may include tool_calls instead of or in addition to content.

Functions

normalize_response(response)

@spec normalize_response(String.t() | map()) :: normalized_response()

Normalize an LLM response to a consistent format.

Examples

iex> PtcRunner.SubAgent.LLMResolver.normalize_response("hello")
%{content: "hello", tokens: nil}

iex> PtcRunner.SubAgent.LLMResolver.normalize_response(%{content: "hello"})
%{content: "hello", tokens: nil}

iex> PtcRunner.SubAgent.LLMResolver.normalize_response(%{content: "hello", tokens: %{input: 10, output: 5}})
%{content: "hello", tokens: %{input: 10, output: 5}}

resolve(llm, input, registry)

@spec resolve(atom() | (map() -> {:ok, term()} | {:error, term()}), map(), map()) ::
  {:ok, normalized_response()} | {:error, term()}

Resolve and invoke an LLM, handling both functions and atom references.

Normalizes the LLM response to always return a map with :content and :tokens keys. This provides a consistent interface for callers regardless of whether the LLM callback returns a plain string or a map with token information.

Parameters

  • llm - Either a function/1 or an atom referencing the registry
  • input - The LLM input map to pass to the callback
  • registry - Map of atom to LLM callback for atom-based LLM references

Returns

  • {:ok, %{content: String.t(), tokens: map() | nil}} - Normalized response on success

  • {:error, reason} - Error tuple with reason on failure

Examples

iex> llm = fn %{messages: [%{content: _}]} -> {:ok, "result"} end
iex> PtcRunner.SubAgent.LLMResolver.resolve(llm, %{messages: [%{content: "test"}]}, %{})
{:ok, %{content: "result", tokens: nil}}

iex> llm = fn _ -> {:ok, %{content: "result", tokens: %{input: 10, output: 5}}} end
iex> PtcRunner.SubAgent.LLMResolver.resolve(llm, %{messages: []}, %{})
{:ok, %{content: "result", tokens: %{input: 10, output: 5}}}

iex> registry = %{haiku: fn %{messages: _} -> {:ok, "response"} end}
iex> PtcRunner.SubAgent.LLMResolver.resolve(:haiku, %{messages: [%{content: "test"}]}, registry)
{:ok, %{content: "response", tokens: nil}}

total_tokens(tokens)

@spec total_tokens(map()) :: non_neg_integer()

Calculate total tokens from input and output token counts.

Examples

iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{input: 10, output: 5})
15

iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{input: 0, output: 0})
0

iex> PtcRunner.SubAgent.LLMResolver.total_tokens(%{})
0