DeutschGitHub
prototypepackages/ai-function-caller/src

AI Function Caller

@ralphschuler/ai-function-caller

Runs a bounded, validated function-call conversation with an injectable provider.

tool callingconversationOpenAI

01 · Problem

What is it for?

A model can request work, but application code must validate arguments, resolve a registered function and return its result.

02 · Mental model

How should you think about it?

Treat the model as a planner whose output is untrusted input. A function schema describes a contract; a handler performs the work.

In this repository

Runs a bounded, validated function-call conversation with an injectable provider. Provider calls are mocked in CI; flat schemas are supported. Real provider compatibility and application authorization remain integration concerns.

03 · Control flow

What happens in which order?

  1. Append the user message to conversation history.
  2. Send public function schemas to the model.
  3. Resolve requested names through a registry and validate arguments.
  4. Execute the handler and append a function result.
  5. Stop at a final answer or a strict step limit.

04 · Building blocks

The important contracts

query(message, signal?)Execute a bounded conversation and validated local handlers.
AIFunctionCallerOptionsInject the provider, model and step limit.

05 · Build it yourself

Implement a small core

Start with this small independent implementation, then use the steps below to make its contract explicit.

  1. Append the user message to conversation history.
  2. Send public function schemas to the model.
  3. Resolve requested names through a registry and validate arguments.
  4. Execute the handler and append a function result.
  5. Stop at a final answer or a strict step limit.
Concept sketch · may reference omitted helpers
type Tool = { schema: object; run(input: unknown): Promise<unknown> };

async function runAgent(input: string, signal: AbortSignal) {
  const messages = [{ role: "user", content: input }];
  for (let step = 0; step < 6; step++) {
    const reply = await model.complete(messages, signal);
    messages.push(reply.message);
    if (!reply.toolCalls.length) return reply.message.content;
    for (const call of reply.toolCalls) {
      const tool = tools.get(call.name);
      if (!tool) throw new Error("Unknown tool: " + call.name);
      const args = validate(tool.schema, JSON.parse(call.arguments));
      const value = await tool.run(args);
      messages.push({ role: "tool", content: JSON.stringify(value) });
    }
  }
  throw new Error("Tool-call limit reached");
}

06 · Verify

What should you test?

  • Reject unknown function names without executing application code.
  • Reject malformed JSON and invalid arguments.
  • Test an endless sequence of tool requests against a step limit.

Try it yourself

Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.

Reject unknown function names without executing application code.

Compare your approach

Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.

07 · Limits

Trade-offs and failure modes

  • Keep provider secrets on the server.
  • Bound conversation size and execution time.
  • Provider SDKs evolve; isolate their message formats in an adapter.
Important

Provider calls are mocked in CI; flat schemas are supported. Real provider compatibility and application authorization remain integration concerns.

08 · Keep exploring

Source code and related ideas

yarn tsx packages/ai-function-caller/examples/basic.ts