Skip to main content
Loom uses a server-side proxy architecture for all LLM interactions. API keys are stored exclusively on the server, and clients communicate through HTTP proxy endpoints. This design provides enhanced security, centralized credential management, and unified observability.

Architecture Diagram

Key Properties

Security

API keys never leave the server. No secrets in client binaries.

Centralized Credentials

Single source of truth for API keys. Easy rotation and auditing.

Unified Observability

All LLM requests flow through proxy layer for logging and monitoring.

Multi-Provider Support

Server can host multiple providers simultaneously with separate credentials.

Request Flow

1. Client Creates Provider-Specific Client

Clients use ProxyLlmClient from loom-server-llm-proxy crate:

2. Client Sends Request to Provider-Specific Endpoint

The ProxyLlmClient implements the LlmClient trait and forwards requests to provider-specific endpoints:

3. Server Routes to Provider Client

The server’s LlmService manages all provider clients and routes requests:

4. Provider Client Makes API Call

Provider-specific clients handle the actual API communication:

5. Response Streams Back Through Proxy

SSE events flow from provider → server → client:

Proxy Endpoints

Per-Provider Endpoints

Each provider has dedicated complete and stream endpoints:
/proxy/anthropic/complete
POST
Non-streaming completion for Anthropic ClaudeRequest Body: LlmRequest JSON
Response: LlmResponse JSON
/proxy/anthropic/stream
POST
SSE streaming completion for Anthropic ClaudeRequest Body: LlmRequest JSON
Response: SSE stream of LlmEvent JSON
/proxy/openai/complete
POST
Non-streaming completion for OpenAI GPTRequest Body: LlmRequest JSON
Response: LlmResponse JSON
/proxy/openai/stream
POST
SSE streaming completion for OpenAI GPTRequest Body: LlmRequest JSON
Response: SSE stream of LlmEvent JSON
/proxy/vertex/complete
POST
Non-streaming completion for Google Vertex AIRequest Body: LlmRequest JSON
Response: LlmResponse JSON
/proxy/vertex/stream
POST
SSE streaming completion for Google Vertex AIRequest Body: LlmRequest JSON
Response: SSE stream of LlmEvent JSON
/proxy/zai/complete
POST
Non-streaming completion for Z.ai (智谱AI)Request Body: LlmRequest JSON
Response: LlmResponse JSON
/proxy/zai/stream
POST
SSE streaming completion for Z.aiRequest Body: LlmRequest JSON
Response: SSE stream of LlmEvent JSON

Wire Format

Request Format (All Providers)

Complete Response Format

Streaming Event Format

SSE stream with LlmEvent JSON payloads:
The SSE format uses \n\n as the event delimiter. Each data: line contains a JSON-encoded LlmEvent.

LlmService Architecture

The LlmService crate (loom-server-llm-service) provides server-side provider abstraction:

Configuration

Provider Availability Checks

Provider-Specific Methods

The server can have all providers configured simultaneously. Clients choose which provider to use by selecting the appropriate endpoint path.

Client Authentication

The proxy supports optional bearer token authentication:
The server validates tokens and enforces authorization policies based on user identity.

Provider Implementations

Anthropic Client (loom-server-llm-anthropic)

  • API: POST /v1/messages
  • Headers: x-api-key, anthropic-version: 2023-06-01
  • System messages: Extracted to top-level system field
  • Tool results: Sent as tool_result content blocks
  • Streaming: SSE with message_startcontent_block_deltamessage_stop

OpenAI Client (loom-server-llm-openai)

  • API: POST /chat/completions
  • Headers: Authorization: Bearer {api_key}
  • Tool choice: Defaults to "auto" when tools provided
  • Streaming: SSE with data: [DONE] marker

Vertex AI Client (loom-server-llm-vertex)

  • API: Google Cloud Vertex AI API
  • Auth: Service account credentials
  • Models: Gemini Pro, Gemini Flash, etc.

Z.ai Client (loom-server-llm-zai)

  • API: POST /api/paas/v4/chat/completions (OpenAI-compatible)
  • Headers: Authorization: Bearer {api_key}
  • Models: glm-4.7, glm-4.6, glm-4.5, glm-4.5-flash, etc.
  • Streaming: SSE with data: [DONE] marker (OpenAI-compatible)

Benefits

Security

  • No API keys in client binaries or repositories
  • Centralized credential rotation
  • Audit logging at proxy layer
  • Token-based client authentication

Observability

  • All LLM requests logged server-side
  • Unified metrics across providers
  • Cost tracking per user/organization
  • Performance monitoring

Flexibility

  • Add providers without client updates
  • A/B test different models
  • Dynamic provider selection
  • Fallback to alternate providers

Cost Control

  • Rate limiting per user/organization
  • Budget enforcement
  • Provider pooling (e.g., Claude subscription sharing)
  • Usage analytics

Adding a New Provider

1

Create provider client crate

Create loom-server-llm-{provider} with LlmClient trait implementation.
2

Add to LlmService

Update loom-server-llm-service to include the new provider client.
3

Add proxy endpoints

Add /proxy/{provider}/complete and /proxy/{provider}/stream routes in loom-server.
4

Update ProxyLlmClient

Add convenience constructor in loom-server-llm-proxy (e.g., ProxyLlmClient::new_provider()).
No client-side changes required! Once the server is updated, all clients automatically gain access to the new provider through the proxy.

Architecture Overview

High-level system architecture

State Machine

Agent state machine design

LLM Client Spec

Detailed LLM client specification

Anthropic OAuth Pool

Claude subscription pooling