# Function Call Utility Operational Flow in LemonAI: How AI-Generated Functions Execute

> Explore the LemonAI function call utility operational flow learn how AI-generated functions are discovered converted and executed through a three-stage pipeline in src tools and src routers agent proxy js

- Repository: [hexdocom/lemonai](https://github.com/hexdocom/lemonai)
- Tags: how-to-guide
- Published: 2026-03-03

---

**The LemonAI function call utility executes AI-generated functions through a three-stage pipeline: discovering server-side tools in `src/tools/`, converting them to OpenAI-compatible schemas via `resolveFunctionCall()`, and parsing LLM-generated `tool_calls` in [`src/routers/agent/proxy.js`](https://github.com/hexdocom/lemonai/blob/main/src/routers/agent/proxy.js) for actual execution.**

The hexdocom/lemonai repository provides a robust framework for LLM agents to invoke server-side JavaScript capabilities. Understanding the operational flow of the function call utility reveals how the system bridges declarative tool schemas with concrete execution logic. This architecture keeps the LLM client agnostic to tool implementations while enabling safe, observable, multi-step interactions.

## Tool Discovery and Schema Generation

The operational flow begins with automatic discovery of available capabilities. The utility scans the filesystem to build a registry of executable tools, then transforms these definitions into a format compatible with major LLM providers.

### Dynamic Tool Loading

In [`src/tools/index.js`](https://github.com/hexdocom/lemonai/blob/main/src/tools/index.js), the system reads every `.js` file from the `src/tools/` directory to populate the tools map. The loader specifically skips the `browser_use` entry while constructing a plain object where `tools[toolName] = require(path…)`. This dynamic require pattern ensures that adding a new tool module automatically makes it available to the function call utility without manual registration.

Each tool module exports three critical metadata fields: `name` (the function identifier), `description` (for the LLM to understand capabilities), and `params` (a JSON-Schema-like object defining required and optional arguments). The discovery phase concludes with a populated `tools` object that maps function names to their implementations.

### OpenAI Schema Conversion

The [`src/utils/function.call.js`](https://github.com/hexdocom/lemonai/blob/main/src/utils/function.call.js) file contains the core transformation logic. The `convertTool()` function (lines 6-16) wraps each tool definition into the standard OpenAI function-calling format:

```javascript
{
  type: 'function',
  function: {
    name: tool.name,
    description: tool.description,
    parameters: tool.params
  }
}

```

The exported `resolveFunctionCall()` function (lines 18-24) iterates through the tools map and returns an array of these converted schemas. This array becomes the value for the `options.tools` field in LLM requests, allowing models like GPT-4o or Gemini to recognize available server-side capabilities as native functions they can invoke.

## LLM Request Composition

When the agent creates a completion request in [`src/completion/llm.base.js`](https://github.com/hexdocom/lemonai/blob/main/src/completion/llm.base.js) (lines 94-100), it attaches the tool schemas to the request payload:

```javascript
async function buildCompletionOptions() {
  const tools = await resolveFunctionCall();
  return {
    model: "gpt-4o",
    stream: true,
    tools,  // LLM receives schema and may emit tool_calls
    temperature: 0.2
  };
}

```

The LLM receives this schema via its `tools` parameter and can decide to call one of the functions based on the conversation context. When the model chooses to execute a tool, it generates a `tool_calls` entry in the streaming response delta rather than returning plain text content.

## Tool Call Parsing and Execution

Once the LLM emits a function call intention, the operational flow shifts to parsing, routing, and execution. This stage involves three coordinated components that transform the LLM's decision into actual JavaScript execution.

### Streaming Response Handling

The `LLM.messageToValue()` method in [`src/completion/llm.base.js`](https://github.com/hexdocom/lemonai/blob/main/src/completion/llm.base.js) (lines 242-247) inspects every streamed delta from the provider. When it detects `choice.delta.tool_calls`, it extracts the function name and arguments, storing them on `this.tools` for further processing. This parsing layer abstracts provider-specific response formats (OpenAI, Azure, Gemini) into a unified internal representation.

### Proxy-Level Forwarding

The HTTP proxy in [`src/routers/agent/proxy.js`](https://github.com/hexdocom/lemonai/blob/main/src/routers/agent/proxy.js) (lines 93-106) intercepts the SSE stream before it reaches the client. When it identifies `tool_calls` in the delta, it extracts readable metadata:

```javascript
if (parsed.choices?.[0]?.delta?.tool_calls) {
  const toolCalls = parsed.choices[0].delta.tool_calls;
  for (const toolCall of toolCalls) {
    if (toolCall.function?.name) {
      fullContent += `Function: ${toolCall.function.name}\n`;
    }
    if (toolCall.function?.arguments) {
      fullContent += `Arguments: ${toolCall.function.arguments}\n`;
    }
  }
}

```

This proxy layer logs the function invocation details and forwards them to the client stream, providing observability into which AI-generated functions the model selected and with what parameters.

### Function Execution and Result Handling

The actual execution occurs in higher-level orchestration code that looks up the function name in the `tools` map populated during the discovery phase. The system invokes the matching implementation (typically containing `resolveMemory` logic or direct action handlers) and feeds the result back into the conversational loop as a new message.

This design maintains a strict separation of concerns: the LLM client remains agnostic to concrete tool implementations, while the routing layer handles the imperative execution logic. The result persistence (via `resolveMemory` in individual tool modules) ensures that subsequent conversation turns can reference previous tool execution outputs.

## Implementation Examples

Creating custom tools and handling their execution requires understanding the contract between the schema generator and the runtime environment.

### Creating a Custom Tool

New tools follow a standard export pattern that the discovery system recognizes automatically. For example, a calculator tool in [`src/tools/calc.js`](https://github.com/hexdocom/lemonai/blob/main/src/tools/calc.js) would implement:

```javascript
module.exports = {
  name: "calc",
  description: "Evaluate a basic arithmetic expression.",
  params: {
    type: "object",
    properties: {
      expression: { 
        type: "string", 
        description: "Arithmetic expression, e.g. 2+3*4" 
      }
    },
    required: ["expression"]
  },
  async execute({ expression }) {
    return { result: eval(expression) };
  }
};

```

Because [`src/tools/index.js`](https://github.com/hexdocom/lemonai/blob/main/src/tools/index.js) automatically loads every `.js` file, this `calc` tool becomes instantly available to `resolveFunctionCall()` without additional configuration.

### Generating Tool Schemas

To expose available functions to the LLM, import the utility and await the schema generation:

```javascript
const resolveFunctionCall = require("@src/utils/function.call");

async function prepareAgentRequest() {
  const toolDefinitions = await resolveFunctionCall();
  // Returns: [{type:'function', function:{name, description, parameters}}, ...]
  return {
    model: "gpt-4o",
    tools: toolDefinitions,
    stream: true
  };
}

```

### Handling Tool Calls in Stream

A simplified handler demonstrating the execution flow:

```javascript
LLM.messageToValue = async function(message) {
  const val = originalParser(message);
  if (val.type === "tool_calls") {
    const { name, arguments: args } = val.tool_calls[0].function;
    const tool = require("@src/tools")[name];
    const result = await tool.execute(JSON.parse(args));
    
    // Return result to LLM as tool response message
    return {
      role: "tool",
      tool_call_id: val.tool_calls[0].id,
      content: JSON.stringify(result)
    };
  }
  return val;
};

```

## Summary

- **Tool Discovery**: [`src/tools/index.js`](https://github.com/hexdocom/lemonai/blob/main/src/tools/index.js) dynamically loads all tool modules (excluding `browser_use`) into a registry map used throughout the system.
- **Schema Generation**: [`src/utils/function.call.js`](https://github.com/hexdocom/lemonai/blob/main/src/utils/function.call.js) converts tool metadata into OpenAI-compatible function schemas via `resolveFunctionCall()`.
- **LLM Integration**: [`src/completion/llm.base.js`](https://github.com/hexdocom/lemonai/blob/main/src/completion/llm.base.js) attaches schemas to requests and parses `tool_calls` deltas using `messageToValue()`.
- **Routing & Execution**: [`src/routers/agent/proxy.js`](https://github.com/hexdocom/lemonai/blob/main/src/routers/agent/proxy.js) forwards tool calls to the client, while higher-level orchestration executes the matched JavaScript functions and returns results to the conversation.
- **Extensibility**: Adding new capabilities requires only creating a new file in `src/tools/` with the standard `name`, `description`, `params`, and execution exports.

## Frequently Asked Questions

### How does LemonAI discover new tools automatically?

The system uses [`src/tools/index.js`](https://github.com/hexdocom/lemonai/blob/main/src/tools/index.js) to perform a filesystem scan of the `src/tools/` directory at startup. It requires every `.js` file (skipping `browser_use`) and populates a `tools` object where keys are tool names and values are the module exports. This dynamic require pattern means any new file following the standard export contract (`name`, `description`, `params`) becomes available immediately without registry updates.

### What format does `resolveFunctionCall()` return for LLM compatibility?

`resolveFunctionCall()` in [`src/utils/function.call.js`](https://github.com/hexdocom/lemonai/blob/main/src/utils/function.call.js) returns an array of objects conforming to the OpenAI function-calling specification. Each element contains `type: 'function'` and a nested `function` object with `name`, `description`, and `parameters` (the JSON Schema from the tool's `params` field). This format works with OpenAI, Azure OpenAI, and other compatible providers.

### Where does the actual function execution happen if not in the LLM class?

While [`src/completion/llm.base.js`](https://github.com/hexdocom/lemonai/blob/main/src/completion/llm.base.js) parses the `tool_calls` from the stream, the imperative execution occurs in higher-level agent orchestration code (typically invoked through [`src/routers/agent/proxy.js`](https://github.com/hexdocom/lemonai/blob/main/src/routers/agent/proxy.js) or the agent controller). This code looks up the function name in the tools map and invokes the module's `execute()` or `resolveMemory()` method, maintaining separation between the LLM client and business logic.

### How does the system handle tool results after execution?

After the orchestration layer executes the matched function, it typically formats the result as a tool response message (with role `tool`) and appends it to the conversation history. The LLM receives this result in the next completion request, allowing for multi-step reasoning where subsequent AI-generated functions can reference previous execution outputs stored via `resolveMemory` or direct return values.