Skip to content

Conversation

@renatillas
Copy link
Contributor

@renatillas renatillas commented Jan 20, 2026

High-level API for agentic LLM workflows with automatic tool execution.

New Modules

Module Purpose
gai/agent Bundles provider, system prompt, tools
gai/agent/loop Orchestrates LLM calls + tool execution
gai/runtime HTTP transport abstraction

Tool Design

Tools carry their own executor with type-safe args:

let weather_tool = tool.tool(
  name: "get_weather",
  description: "Get weather",
  schema: weather_schema(),
  execute: fn(ctx, args) {
    // args is WeatherArgs - fully typed!
    Ok("Sunny in " <> args.location)
  },
)

Usage

let my_agent = agent.new(provider)
  |> agent.with_system_prompt("You are helpful.")
  |> agent.with_tool(weather_tool)

loop.run(my_agent, ctx, messages, runtime)

Changes

  • tool.tool() - Creates tool with embedded executor
  • tool.ToolSchema - Replaces UntypedTool for request serialization
  • agent.Agent - Minimal config (provider, tools, system_prompt, max_iterations)
  • loop.run() / loop.run_with_config() - Executes agent with tool loop

See AGENT_DESIGN.md for details.

This PR implements the agent/tool loop system for gai:

## New Modules

- \`gai/internal/coerce\` - Existential type pattern using identity function
  (works on both Erlang and JavaScript targets)

- \`gai/tool\` - Extended with new ExecutableTool type that carries its own
  executor. Type safety is preserved at definition time, then erased for
  storage using the coerce pattern.

- \`gai/agent\` - Agent type that bundles provider, system prompt, and tools
  with builder pattern API.

- \`gai/runtime\` - HTTP transport abstraction for target-agnostic requests.

- \`gai/agent/loop\` - Tool loop that orchestrates LLM calls with automatic
  tool execution until completion or max iterations.

## Design Highlights

- Tools carry their own executor: no pattern matching on tool names needed
- Coerce pattern works on both Erlang and JS (uses identity function)
- Backwards compatible: legacy Tool(a) and UntypedTool APIs preserved
- Full test coverage for new functionality

## Example Usage

\`\`\`gleam
let weather_tool = tool.executable(
  name: "get_weather",
  description: "Get weather",
  schema: weather_schema(),
  execute: fn(ctx, args) {  // args is fully typed!
    Ok("Sunny in " <> args.location)
  },
)

let my_agent = agent.new(provider)
  |> agent.with_system_prompt("You are helpful")
  |> agent.with_tool(weather_tool)
  |> agent.with_max_iterations(5)

loop.run(my_agent, ctx, messages, runtime)
\`\`\`

See AGENT_DESIGN.md for full design documentation.
@renatillas renatillas force-pushed the feat/agent-tool-loop branch from d1c5ae8 to f3b78eb Compare January 20, 2026 12:32
- Replace UntypedTool with ToolSchema in request and providers
- Update tool.gleam: remove legacy API, use tool() as constructor
- Update all tests to use new API:
  - tool.tool() with execute parameter
  - tool.to_schema() instead of to_untyped()
  - Direct field access on ToolSchema
- 194 tests passing
@renatillas renatillas force-pushed the feat/agent-tool-loop branch from a880d25 to 692d9fe Compare January 20, 2026 12:53
Proves that tool args can be any type - the test uses Option(Unit)
enum which would fail if args was incorrectly typed as String.

Also restores internal/coerce.gleam for future use.
No need for a separate internal module - coerce is only used here.
@renatillas renatillas force-pushed the feat/agent-tool-loop branch from 943afcd to e09820b Compare January 20, 2026 14:39
@renatillas renatillas changed the title feat: add Agent, Runtime, and Tool Loop feat: Agent & Tool Loop Jan 20, 2026
@renatillas renatillas force-pushed the feat/agent-tool-loop branch from 2a53879 to 2a65bab Compare January 20, 2026 14:50
@renatillas renatillas force-pushed the feat/agent-tool-loop branch from 2a65bab to e9168f7 Compare January 20, 2026 15:11
@renatillas renatillas marked this pull request as ready for review January 20, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant