# How OpenHuman Handles Tool Calling and Model Interactions via the TinyAgents Adapter Pattern

> Discover how OpenHuman uses the TinyAgents adapter pattern for seamless tool calling and model interactions. Learn about its bidirectional conversion, real-time streaming, and error metadata preservation.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: internals
- Published: 2026-08-26

---

**OpenHuman bridges its internal tool-calling API and LLM inference flow through the TinyAgents adapter, which performs bidirectional request/response conversion, real-time streaming of tool-call fragments, and preservation of rich error metadata.**

The `tinyhumansai/openhuman` repository implements a thin but critical translation layer that unifies disparate provider APIs under a common tool-calling interface. This TinyAgents adapter pattern enables OpenHuman to treat any compatible LLM provider as a drop-in model while maintaining full support for native and prompt-guided tool invocations.

## Core Adapter Architecture

The adapter lives in [`src/openhuman/agent/tinyagents/model.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/model.rs) and performs three essential roles: converting outgoing requests into TinyAgents format, transforming incoming responses back to OpenHuman native types, and bridging streaming deltas for real-time UI updates.

### Outgoing Request Conversion

When a turn executes, OpenHuman builds a `ModelRequest` containing chat history and advertised tool schemas. The `native_chat_messages` function (line 21 in [`src/openhuman/agent/tinyagents/model.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/model.rs)) converts each `ChatMessage` into a TinyAgents-compatible `Message` via the shared logic in [`src/openhuman/agent/message_convert.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/message_convert.rs).

Tool schemas undergo transformation into a **P-format registry** through `pformat_registry_from_request`. This registry enables the fallback text-parser to recover prompt-guided calls when the provider returns unstructured responses rather than native tool invocations.

### Incoming Response Translation

After the provider returns a `ChatResponse`, the adapter creates a `ModelResponse` through `response_to_model_response` (line 59). This conversion follows a strict precedence:

1. **Native tool calls** – Structured `tool_calls` in the response are copied verbatim into `TaToolCall` objects.
2. **Prompt-guided fallback** – When no native calls exist but tools were advertised, the raw text parses against the P-format registry via `parse_tool_calls_with_pformat`.
3. **Visible content** – Non-tool text becomes `ContentBlock::Text`, while reasoning blocks convert to typed `ContentBlock` instances that persist but remain excluded from `Message::text()`.

Token usage metadata transfers into `tinyagents::harness::usage::Usage`, while provider-specific fields like charged USD and context window size store under the custom key `openhuman_usage_meta` within `ModelResponse::raw`. Helper functions `openhuman_usage_meta_raw` and `merge_openhuman_usage_meta` (lines 38-71) encapsulate this logic.

## Real-Time Streaming Bridge

For streaming providers, the adapter’s `forward_provider_delta` function (line 34) relays incremental `ProviderDelta` events onto a `tokio::sync::mpsc::UnboundedSender<ModelStreamItem>`. This bridges the gap between provider-specific server-sent events and TinyAgents’ internal stream protocol.

The mapping converts:
- **TextDelta** → `MessageDelta::text`
- **ThinkingDelta** → `MessageDelta::reasoning`
- **ToolCallStart** → `ToolDelta` with `tool_name` set, creating the UI row before arguments arrive
- **ToolCallArgsDelta** → `ToolDelta` containing the argument fragment

These stream items propagate through `OpenhumanEventBridge` (implemented in [`src/openhuman/agent/tinyagents/host/mod.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/host/mod.rs)) to keep the UI timeline synchronized with the provider’s construction progress.

## Error Preservation and Profile Overrides

TinyAgents represents model errors as simple strings (`TinyAgentsError::Model(String)`), which strips valuable type information. To preserve the original `anyhow::Error` carrying rich `AgentError` variants, the adapter stores the typed error in a shared `ModelErrorSlot` (`Arc<Mutex<Option<anyhow::Error>>>`) before returning the stringified version. The runner later consults this slot to restore down-castable errors for Sentry suppression and structured logging (see line 88 in [`src/openhuman/agent/tinyagents/model.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/model.rs)).

When turns require precise model configurations—such as custom context windows or prompt-guided modes—OpenHuman wraps the underlying `ChatModel` in a `ProfileOverrideModel`. This wrapper swaps the `ModelProfile` without altering the wire protocol, ensuring the adapter continues to see standard request/response shapes while the provider receives corrected limits (defined at lines 95-100).

## Implementation Examples

The following patterns demonstrate the adapter’s conversion flow in practice:

```rust
// Build a TinyAgents request from OpenHuman native data
let request = ModelRequest {
    messages: native_chat_messages(&model_req),
    tools: advertised_tools.clone(),
    ..Default::default()
};

// Execute via the TinyAgents harness
let response = chat_model.invoke(request).await?;

// Convert provider response back to OpenHuman format
let model_response = response_to_model_response(
    &chat_response,
    &pformat_registry_from_request(&original_request),
    !original_request.tools.is_empty(),
);

```

For streaming scenarios, forward deltas as they arrive from the provider:

```rust
// Forward streaming tool call initiation
forward_provider_delta(&tx, ProviderDelta::ToolCallStart {
    call_id: "call_1".into(),
    tool_name: "search".into(),
});

// Stream incremental argument fragments
forward_provider_delta(&tx, ProviderDelta::ToolCallArgsDelta {
    call_id: "call_1".into(),
    delta: "query=rust".into(),
});

```

To recover billing and usage metadata from a completed response:

```rust
if let Some(usage) = usage_info_from_response(&model_response) {
    println!("Charged ${:.2}, window {} tokens", 
             usage.charged_amount_usd, 
             usage.context_window);
}

```

## Summary

- The **TinyAgents adapter** in [`src/openhuman/agent/tinyagents/model.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/model.rs) provides the critical translation seam between OpenHuman’s native types and the TinyAgents model API.
- **Bidirectional conversion** handles both native structured tool calls and legacy prompt-guided invocations via the P-format registry.
- **Streaming bridge** forwards incremental provider deltas through `forward_provider_delta` to enable real-time UI updates during tool construction.
- **Rich error preservation** uses `ModelErrorSlot` to maintain typed `anyhow::Error` information across the adapter boundary.
- **Profile overrides** allow dynamic adjustment of model parameters without breaking the adapter’s protocol expectations.

## Frequently Asked Questions

### What is the TinyAgents adapter pattern in OpenHuman?

The TinyAgents adapter pattern is a translation layer that allows OpenHuman to use any TinyAgents-compatible LLM provider while preserving OpenHuman’s native tool-calling semantics. It converts `ChatMessage` objects to TinyAgents format, handles streaming deltas, and ensures usage metadata survives round-trips across the model boundary.

### How does OpenHuman handle streaming tool call fragments?

OpenHuman consumes incremental `ProviderDelta` events from the provider and forwards them via `forward_provider_delta` in [`src/openhuman/agent/tinyagents/model.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/agent/tinyagents/model.rs). This function maps `ToolCallStart` and `ToolCallArgsDelta` events to TinyAgents `ToolDelta` items, allowing the UI to display tool construction in real-time before the final arguments complete.

### What is the difference between native and prompt-guided tool calling?

**Native tool calling** occurs when the LLM returns structured `tool_calls` in its response, which the adapter copies directly into `TaToolCall` objects. **Prompt-guided fallback** triggers when the provider returns unstructured text; the adapter then parses that text against the P-format registry to extract tool invocations, supporting providers without native function-calling capabilities.

### How does the adapter preserve error information across the boundary?

Before converting errors to TinyAgents’ simple string format, the adapter stores the original `anyhow::Error` in a thread-safe `ModelErrorSlot`. This preserves rich error variants for downstream observability tools, allowing the runner to retrieve the typed error for Sentry suppression and structured logging after the TinyAgents harness returns control.