PtcRunner.SubAgent.Signature (PtcRunner v0.12.0)

Copy Markdown View Source

Signature parsing and validation for SubAgents.

Signatures define the contract between agents and tools:

  • Input parameters - What the caller must provide
  • Output type - What the callee will return

Signature Format

Full format: (params) -> output Shorthand: output (equivalent to () -> output)

Types

  • Primitives: :string, :int, :float, :bool, :keyword, :any, :datetime
  • Collections: [:type] (list), {field :type} (map), :map (untyped map)
  • Optional: :type? (nullable field or parameter)

Examples

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("(name :string) -> {greeting :string}")
iex> sig
{:signature, [{"name", :string}], {:map, [{"greeting", :string}]}}

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("{count :int}")
iex> sig
{:signature, [], {:map, [{"count", :int}]}}

Summary

Functions

Convert a JSON Schema subset map to a PTC signature return type.

Parse a signature string into internal format.

Format a signature back to string representation.

Check if signature returns a list type.

Convert a signature to JSON Schema format.

Validate data against a signature's return type.

Validate input parameters against a signature.

Types

field()

@type field() :: {String.t(), type()}

param()

@type param() :: {String.t(), type()}

return_type()

@type return_type() :: type()

signature()

@type signature() :: {:signature, [param()], return_type()}

type()

@type type() ::
  :string
  | :int
  | :float
  | :bool
  | :keyword
  | :any
  | :map
  | :datetime
  | {:optional, type()}
  | {:list, type()}
  | {:map, [field()]}
  | {:closed_map, [field()]}

validation_error()

@type validation_error() :: %{
  path: [String.t() | non_neg_integer()],
  message: String.t()
}

Functions

from_json_schema(s)

@spec from_json_schema(map()) :: {:ok, type()} | {:error, String.t()}

Convert a JSON Schema subset map to a PTC signature return type.

Inverse of type_to_json_schema/1. Supports scalar types (string, integer, number, boolean), array with items, and object with properties/required/additionalProperties. Unsupported JSON Schema features (combinators, $ref, enum, pattern, etc.) return {:error, reason}.

additionalProperties: false produces a closed map ({:closed_map, ...}) whose validation rejects undeclared fields. additionalProperties: true (or its absence) produces an open {:map, ...} that tolerates extras. Every required entry must be a string naming a declared property, otherwise an {:error, reason} is returned.

Examples

iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "integer"})
{:ok, :int}

iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "array", "items" => %{"type" => "string"}})
{:ok, {:list, :string}}

iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"count" => %{"type" => "integer"}}, "required" => ["count"]})
{:ok, {:map, [{"count", :int}]}}

iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"name" => %{"type" => "string"}}})
{:ok, {:map, [{"name", {:optional, :string}}]}}

iex> PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{"x" => %{"type" => "integer"}}, "required" => ["x"], "additionalProperties" => false})
{:ok, {:closed_map, [{"x", :int}]}}

iex> match?({:error, _}, PtcRunner.SubAgent.Signature.from_json_schema(%{"type" => "object", "properties" => %{}, "required" => ["missing"]}))
true

parse(input)

@spec parse(String.t()) :: {:ok, signature()} | {:error, String.t()}

Parse a signature string into internal format.

Returns {:ok, signature()} or {:error, reason}.

Examples

iex> PtcRunner.SubAgent.Signature.parse("(id :int) -> {name :string}")
{:ok, {:signature, [{"id", :int}], {:map, [{"name", :string}]}}}

iex> PtcRunner.SubAgent.Signature.parse("() -> :string")
{:ok, {:signature, [], :string}}

iex> PtcRunner.SubAgent.Signature.parse("{count :int}")
{:ok, {:signature, [], {:map, [{"count", :int}]}}}

iex> match?({:error, _}, PtcRunner.SubAgent.Signature.parse("invalid"))
true

render(signature)

@spec render(signature()) :: String.t()

Format a signature back to string representation.

Used for rendering in prompts or debugging.

returns_list?(arg1)

@spec returns_list?(signature()) :: boolean()

Check if signature returns a list type.

Used to determine if text mode response needs unwrapping.

to_json_schema(arg)

@spec to_json_schema(signature()) :: map()

Convert a signature to JSON Schema format.

Extracts the return type and converts it to a JSON Schema that can be passed to LLM providers for structured output.

Note: Array return types are wrapped in an object with an "items" property because most LLM providers require an object at the root level. Use returns_list?/1 to check if unwrapping is needed.

Examples

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> {sentiment :string, score :float}")
iex> PtcRunner.SubAgent.Signature.to_json_schema(sig)
%{
  "type" => "object",
  "properties" => %{
    "sentiment" => %{"type" => "string"},
    "score" => %{"type" => "number"}
  },
  "required" => ["sentiment", "score"],
  "additionalProperties" => false
}

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> [:int]")
iex> PtcRunner.SubAgent.Signature.to_json_schema(sig)
%{
  "type" => "object",
  "properties" => %{
    "items" => %{"type" => "array", "items" => %{"type" => "integer"}}
  },
  "required" => ["items"],
  "additionalProperties" => false
}

validate(signature, data)

@spec validate(signature(), term()) :: :ok | {:error, [validation_error()]}

Validate data against a signature's return type.

Returns :ok or {:error, [validation_error()]}.

Examples

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> {count :int, items [:string]}")
iex> PtcRunner.SubAgent.Signature.validate(sig, %{count: 5, items: ["a", "b"]})
:ok

iex> {:ok, sig} = PtcRunner.SubAgent.Signature.parse("() -> :int")
iex> PtcRunner.SubAgent.Signature.validate(sig, "not an int")
{:error, [%{path: [], message: "expected int, got string"}]}

validate_input(signature, input)

@spec validate_input(signature(), map()) :: :ok | {:error, [validation_error()]}

Validate input parameters against a signature.

Returns :ok or {:error, [validation_error()]}.