Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Use your own LLM

By default, CyteType runs every job on models it hosts. With llm_configs, the annotation runs on models from your own provider account instead, for example to work under your institution’s agreement with a provider, to use a particular model, or to use a model you host yourself.

What changes, and what does not:

Configure a model

Pass a list of model configurations: dictionaries in Python, named lists in R.

Python
R
import os

adata = annotator.run(
    study_context="...",
    llm_configs=[
        {
            "provider": "openai",
            "name": "<model-name>",
            "apiKey": os.environ["OPENAI_API_KEY"],
        }
    ],
)

OPENAI_API_KEY stands for a variable you set yourself; CyteType does not read it automatically. Replace <model-name> with the model name as your provider spells it.

Configuration fields

FieldRequiredMeaning
providerYesThe provider; see Providers.
nameYesThe model name, as your provider spells it.
apiKeyYes, except with bedrockYour API key for the provider.
baseUrlNoThe address of an OpenAI-compatible endpoint. Used by openai (and by vertex); the other providers connect to their standard endpoint and ignore it.
modelSettingsNoSettings sent with each request to the model, such as temperature or max_tokens.
targetAgentsNoThe agents this model serves; see Several models.
awsAccessKeyId, awsSecretAccessKey, awsDefaultRegionWith bedrock, all threeYour AWS credentials and region; see AWS Bedrock.
allowFallbackNoTrue makes this a fallback model, used only when the other models for the same agent fail. Default False.

The client also accepts skipValidation, but the server ignores it and tests every model (see Validation).

Providers

Providerprovider value
Anthropicanthropic
AWS Bedrockbedrock
DeepSeekdeepseek
Fireworks AIfireworks
Googlegoogle
Groqgroq
Hugging Facehuggingface
Mistralmistral
Nebiusnebius
OpenAI, or any OpenAI-compatible endpointopenai
OpenRouteropenrouter
Together AItogether
Google Vertex AIvertex
xAIxai

For vertex, set baseUrl to the OpenAI-compatible endpoint of your Vertex AI project; CyteType sends apiKey with each request as the key parameter.

Several models and targetAgents

A job is carried out by several agents, each responsible for one part of the work:

AgentRole
contextualizerInterprets the study context and builds each cluster’s context.
annotatorProposes and weighs candidate identities, places them in the reference hierarchy and refines labels across clusters.
reviewerReviews each annotation independently: confidence, heterogeneity and the comparison with your own labels.

targetAgents assigns models to agents. It says which agents a model is for; it does not prevent the model from being used elsewhere:

With a single model, the same model annotates and reviews each cluster. To have the review done by a different model, give the reviewer its own model.

targetAgents also accepts summarizer, clinician and chat, but they have no effect: no step of the current workflow uses the summarizer, and target analysis and Chat always run on CyteType’s hosted models.

To dedicate each model to its own agents, give every model a targetAgents list:

Python
R
llm_configs = [
    {
        "provider": "anthropic",
        "name": "<model-name>",
        "apiKey": os.environ["ANTHROPIC_API_KEY"],
        "targetAgents": ["annotator", "reviewer"],
    },
    {
        "provider": "openai",
        "name": "<model-name>",
        "apiKey": os.environ["OPENAI_API_KEY"],
        "targetAgents": ["contextualizer"],
    },
    {
        "provider": "openrouter",
        "name": "<model-name>",
        "apiKey": os.environ["OPENROUTER_API_KEY"],
        "targetAgents": ["annotator", "reviewer"],
        "allowFallback": True,
    },
]

Here the first model serves the annotator and the reviewer, with the third as their fallback, and the second model serves the contextualizer.

AWS Bedrock

Bedrock uses AWS credentials instead of apiKey:

Python
R
llm_configs = [
    {
        "provider": "bedrock",
        "name": "<bedrock-model-id>",
        "awsAccessKeyId": os.environ["AWS_ACCESS_KEY_ID"],
        "awsSecretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"],
        "awsDefaultRegion": "<aws-region>",
    }
]

OpenAI-compatible endpoints

Many serving tools and providers offer an OpenAI-compatible API. To use one, set provider to openai and baseUrl to the endpoint:

Python
R
llm_configs = [
    {
        "provider": "openai",
        "name": "<model-name>",
        "apiKey": os.environ["MY_ENDPOINT_API_KEY"],
        "baseUrl": "https://llm.example.org/v1",
    }
]

Local models with Ollama

Ollama serves models on your own machine through an OpenAI-compatible API. Because CyteType’s servers call the model, it needs a public address. This tutorial gives a local Ollama server a temporary public address with ngrok. ngrok is quick to set up and needs no domain name; on its free plan, the public address changes every time you restart ngrok, and the tunnel closes when you close its terminal.

You need:

Step 1: Make Ollama listen on all interfaces

By default, Ollama only accepts requests from the same machine (127.0.0.1) on port 11434. To let a reverse proxy connect to it, configure it to listen on all network interfaces (0.0.0.0).

Create an override directory for the Ollama service:

sudo mkdir -p /etc/systemd/system/ollama.service.d

Create a configuration file:

sudo nano /etc/systemd/system/ollama.service.d/override.conf

Add the following content:

[Service]
Environment="OLLAMA_HOST=0.0.0.0"

Save the file and exit the editor (in nano, press Ctrl+O, then Enter to write, then Ctrl+X to exit). Reload systemd and restart Ollama:

sudo systemctl daemon-reload
sudo systemctl restart ollama

Check that Ollama listens on all interfaces. Look for an entry with 0.0.0.0:11434 or *:11434:

ss -tlnp | grep 11434

The output looks similar to this:

LISTEN 0  4096       0.0.0.0:11434       0.0.0.0:*  users:(("ollama",pid=1234,fd=7))

Ollama now accepts connections from other devices on your local network and from the tunnel.

Step 2: Install ngrok

On Debian or Ubuntu:

curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
  | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \
  && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
  | sudo tee /etc/apt/sources.list.d/ngrok.list \
  && sudo apt update \
  && sudo apt install ngrok

For other systems, see the ngrok download page.

Step 3: Sign up and add your authtoken

Create a free account on the ngrok dashboard, copy your authtoken from there, and add it to your local configuration (once):

ngrok config add-authtoken <token>

Step 4: Start the tunnel

Forward a public address to Ollama’s port:

ngrok http 11434

ngrok shows your public address on the Forwarding line:

Session Status                online
Account                       Your Name (Plan: Free)
Version                       3.x.x
Region                        United States (us)
Forwarding                    https://random-string-of-letters.ngrok-free.app -> http://localhost:11434

Keep this terminal open; closing it stops the tunnel.

Step 5: Test the endpoint

The OpenAI-compatible API is under /v1. Replace the address with yours and <model-name> with your model:

curl https://your-ngrok-url.ngrok-free.app/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<model-name>",
    "messages": [
      {
        "role": "user",
        "content": "Why is the sky blue?"
      }
    ]
  }'

Step 6: Use the model in CyteType

Use the openai provider with your public address as baseUrl. Ollama does not check API keys, so any text works as apiKey:

Python
R
adata = annotator.run(
    study_context="...",
    llm_configs=[
        {
            "provider": "openai",
            "name": "<model-name>",
            "apiKey": "ollama",
            "baseUrl": "https://your-ngrok-url.ngrok-free.app/v1",
        }
    ],
)

Before you submit:

Validation before the job starts

Your configuration is checked twice before the job starts:

  1. By the client, before anything is uploaded. It checks the fields: a known provider, a model name, and either apiKey or all three AWS fields, and valid agent names in targetAgents. Python raises a pydantic.ValidationError; CyteTypeR stops with a message such as provider must be one of: ....

  2. By the server, at submission. It sends each model a short test that needs a tool call, with up to three attempts. If any model fails, the whole request is rejected: Python raises LLMValidationError, and CyteTypeR stops with Job submission failed: [LLM_VALIDATION_FAILED] .... The message names the models that failed.

A rejected request creates no job and does not count toward your limits. The files had already been uploaded, though, and are uploaded again when you resubmit. The usual causes are a misspelled model name, a wrong or expired key, an endpoint that cannot be reached from the internet, and a model that does not support tool calling.

Keep keys out of files and code

Parallel requests

n_parallel_clusters (from 1 to 50, default 2) sets how many clusters are annotated at the same time when you use your own models. Higher values finish sooner but send more requests at once, which can run into your provider’s rate limits. Without your own models, the server uses its own setting and ignores this value.

Python
R
adata = annotator.run(study_context="...", llm_configs=llm_configs, n_parallel_clusters=8)

See also