How to Integrate memU with OpenRouter for Multi-Provider LLM Access

You integrate memU with OpenRouter by declaring an OpenRouter profile in MemoryService with provider: "openrouter", which automatically routes requests through HTTPLLMClient using the OpenRouterLLMBackend for chat and embedding operations.

The NevaMind-AI/memU framework abstracts LLM provider logic into modular backend classes, allowing seamless switching between providers without changing application code. To access multiple LLM providers through OpenRouter's unified API, you configure a profile that specifies OpenRouter-specific parameters, and memU handles the rest—from request formatting to response parsing—according to the source code in src/memu/llm/backends/openrouter.py.

Architecture Overview

memU isolates provider-specific logic in dedicated backend classes while exposing a uniform interface through MemoryService. When you integrate memU with OpenRouter, the framework utilizes three core components working in concert.

  • OpenRouterLLMBackend – Located in src/memu/llm/backends/openrouter.py, this class inherits from the generic LLMBackend and constructs OpenAI-compatible request payloads for chat, vision, and summary endpoints.
  • _OpenRouterEmbeddingBackend – Implemented in src/memu/llm/http_client.py (lines 52-63), this class handles embedding requests using OpenRouter's OpenAI-compatible embedding interface.
  • HTTPLLMClient – Defined in src/memu/llm/http_client.py, this generic async HTTP client delegates to the appropriate backend based on the provider string in your profile, routing calls to OpenRouter's /api/v1/chat/completions or /api/v1/embeddings endpoints.

Step-by-Step Integration Guide

Configure the OpenRouter Profile

In MemoryService, pass an llm_profiles dictionary containing your OpenRouter configuration. The provider field must be set to "openrouter" to trigger backend selection.

Required parameters include:

  • base_url: "https://openrouter.ai"
  • api_key: Your OpenRouter API key
  • chat_model: The target model (e.g., "anthropic/claude-3.5-sonnet")
  • embed_model: Optional embedding model (e.g., "openai/text-embedding-3-small")

Initialize MemoryService

When you instantiate MemoryService from src/memu/app/service.py, it lazily creates an HTTPLLMClient for each profile. The client reads the provider value and loads OpenRouterLLMBackend for chat operations and _OpenRouterEmbeddingBackend for embeddings.

Execute LLM Operations

Once initialized, use service.llm_client to execute async operations like summarize(), chat(), or embed(). The client automatically invokes the backend's build_*_payload methods to create OpenAI-compatible JSON, sends the HTTP request, and uses parse_*_response methods to extract results.

Code Examples

Basic Setup and Chat Completion

Create a service instance with an OpenRouter profile and generate a summary:

from memu.app import MemoryService
import os

service = MemoryService(
    llm_profiles={
        "default": {
            "provider": "openrouter",          # ← selects OpenRouter backend

            "client_backend": "httpx",         # uses HTTPLLMClient

            "base_url": "https://openrouter.ai",
            "api_key": os.getenv("OPENROUTER_API_KEY"),
            "chat_model": "anthropic/claude-3.5-sonnet",
            "embed_model": "openai/text-embedding-3-small",
        },
    },
)

# Summarize text asynchronously

summary, raw = await service.llm_client.summarize(
    "Explain the benefits of using OpenRouter with memU.",
    max_tokens=150,
)
print(summary)

Generating Embeddings

Generate vector embeddings through OpenRouter's embedding endpoint:

embeddings, raw = await service.llm_client.embed(
    ["memU", "OpenRouter", "LLM integration"]
)
print(embeddings[0][:5])   # first 5 dimensions of the first vector

Full Memory Workflow

Process conversation files and persist categorized memory to markdown, as demonstrated in examples/example_4_openrouter_memory.py:

import asyncio, os
from memu.app import MemoryService

async def generate_memory_md(categories, output_dir):
    os.makedirs(output_dir, exist_ok=True)
    for cat in categories:
        name = cat.get("name", "unknown")
        summary = cat.get("summary", "")
        path = os.path.join(output_dir, f"{name}.md")
        with open(path, "w", encoding="utf-8") as f:
            f.write(summary.replace("<content>", "").replace("</content>", "").strip()
                    or "*No content available*")

async def main():
    api_key = os.getenv("OPENROUTER_API_KEY")
    service = MemoryService(
        llm_profiles={
            "default": {
                "provider": "openrouter",
                "client_backend": "httpx",
                "base_url": "https://openrouter.ai",
                "api_key": api_key,
                "chat_model": "anthropic/claude-3.5-sonnet",
                "embed_model": "openai/text-embedding-3-small",
            },
        },
    )
    conv_files = [
        "examples/resources/conversations/conv1.json",
        "examples/resources/conversations/conv2.json",
        "examples/resources/conversations/conv3.json",
    ]
    categories = []
    for f in conv_files:
        if os.path.exists(f):
            result = await service.memorize(resource_url=f, modality="conversation")
            categories = result.get("categories", [])
    await generate_memory_md(categories, "examples/output/openrouter_example")
    print("Memory categories written to examples/output/openrouter_example/")

if __name__ == "__main__":
    asyncio.run(main())

Key Implementation Details

Backend Selection Logic

Inside HTTPLLMClient.__init__, the _load_backend and _load_embedding_backend methods map the provider string to concrete classes. When provider equals "openrouter", the client instantiates OpenRouterLLMBackend for chat/vision/summary tasks and _OpenRouterEmbeddingBackend for embedding requests. This mapping occurs in src/memu/llm/http_client.py.

Request Payload Construction

For every LLM operation, HTTPLLMClient delegates payload construction to the active backend. The OpenRouterLLMBackend.build_chat_payload and build_summary_payload methods generate OpenAI-compatible JSON containing model, messages, and temperature fields. OpenRouter accepts these payloads at its /api/v1/chat/completions endpoint. After receiving the HTTP response, the client passes the JSON to parse_chat_response or parse_summary_response to extract generated text or embedding vectors.

Summary

  • Profile-based configuration – Set provider: "openrouter" in your MemoryService profile to enable OpenRouter integration.
  • Automatic backend selection – HTTPLLMClient automatically loads OpenRouterLLMBackend and _OpenRouterEmbeddingBackend based on the provider name.
  • OpenAI-compatible protocol – The backend constructs standard OpenAI request formats that OpenRouter consumes, enabling access to multiple underlying LLM providers through a single interface.
  • Async workflow support – All operations including memorize, summarize, and embed are fully async and compatible with OpenRouter's REST API.

Frequently Asked Questions

Do I need to modify the backend code to add new OpenRouter models?

No. The OpenRouterLLMBackend in src/memu/llm/backends/openrouter.py uses the model string you provide in the profile (e.g., "anthropic/claude-3.5-sonnet"). Simply update the chat_model or embed_model field in your profile to switch models; no code changes are required.

Can I use different embedding and chat models from different providers?

Yes. OpenRouter aggregates multiple providers, so you can specify chat_model: "anthropic/claude-3.5-sonnet" and embed_model: "openai/text-embedding-3-small" in the same profile. The _OpenRouterEmbeddingBackend and OpenRouterLLMBackend handle the respective API calls independently.

How does MemoryService handle API errors from OpenRouter?

The HTTPLLMClient in src/memu/llm/http_client.py manages HTTP-level errors and response parsing. If OpenRouter returns an error (e.g., rate limiting or invalid model), the client propagates the HTTP exception or parsing error through the standard async error handling mechanism, allowing you to catch and handle failures in your application logic.

Is it possible to use multiple LLM providers simultaneously in one application?

Yes. You can define multiple profiles in the llm_profiles dictionary passed to MemoryService, each with different provider values (e.g., one for OpenRouter, one for native OpenAI). MemoryService lazily creates separate HTTPLLMClient instances for each profile, enabling you to route specific operations to different providers within the same application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →