Companion AI: setup, providers, and extension guide
Implemented scope
Plugins/NiceCompanionAI supplies a Blueprint-callable GameInstance subsystem for server-only text requests. It works independently of CMC/Mover and falls back to a localized fixed guidance line without a gateway. Services/CompanionAI is a separate private Python HTTP service with provider adapters. This is the transport foundation for the future NPC plugin, not a complete companion actor, chat UI, voice system, or faction/command implementation.
The Unreal method currently sends a single user message per request. The gateway contract supports bounded conversation history for future NPC sessions. There is no persistent AI memory yet, no automatic gameplay context retrieval, and no model selection UI. Selecting a provider uses server environment configuration and a gateway restart; Unreal does not need to be rebuilt.
Provider/model support means text request translation and response parsing. It does not imply every provider feature or every model is supported. Streaming, audio, tools/function calling, embeddings, image input, and managed agents are outside this interface. No live paid/free provider requests were made during development. Request-format fixture tests are not an account-level compatibility guarantee.
Free options
Checked against official documentation on 16 October 2026:
- Local Ollama is the first development choice when available hardware can run a suitable model. Local inference avoids per-request cloud API charges, but consumes your hardware/electricity, and each model has its own license. Cloud Ollama offerings are separate. Ollama compatibility documentation.
- Gemini API free tier is a hosted option for eligible models/accounts/regions, with quotas. Check data-use terms before sending player dialogue; the pricing page distinguishes free and paid data handling. Gemini pricing.
- Groq Free Plan is another hosted development option, with model-specific rate limits rather than unlimited capacity. Groq limits.
Use a small local instruct model for development or a quota-limited hosted option for a prototype. Do not assume any free cloud tier will cover a released multiplayer game's traffic. The default offline provider uses no model, needs no API account, and returns a fixed guidance message.
Provider coverage
Set COMPANION_PROVIDER to a preset below. Always select an exact currently available model in the provider's console; no cloud model is hard-coded.
| Preset | Protocol | Credential environment variable | Official reference |
|---|---|---|---|
offline |
fixed guidance | none | local implementation |
ollama |
OpenAI-compatible chat | none for local endpoint | Ollama |
openai |
Chat Completions | OPENAI_API_KEY |
OpenAI |
anthropic |
Messages | ANTHROPIC_API_KEY |
Anthropic |
gemini |
generateContent | GEMINI_API_KEY |
Gemini |
azure |
Azure OpenAI v1 chat | AZURE_OPENAI_API_KEY |
Microsoft |
bedrock |
Converse via optional boto3 | AWS SDK credential chain / IAM role | AWS |
cohere |
v2 Chat | COHERE_API_KEY |
Cohere |
groq |
compatible chat | GROQ_API_KEY |
Groq |
mistral |
compatible chat | MISTRAL_API_KEY |
Mistral |
deepseek |
compatible chat | DEEPSEEK_API_KEY |
DeepSeek |
xai |
compatible chat | XAI_API_KEY |
xAI |
openrouter |
compatible chat | OPENROUTER_API_KEY |
OpenRouter |
together |
compatible chat | TOGETHER_API_KEY |
Together |
fireworks |
compatible chat | FIREWORKS_API_KEY |
Fireworks |
compatible |
configurable compatible chat | COMPANION_PROVIDER_API_KEY |
operator-selected compatible service |
Azure requires COMPANION_ENDPOINT=https://YOUR-RESOURCE.openai.azure.com/openai/v1/chat/completions and COMPANION_MODEL set to the deployment name. The native preset uses API-key authentication, not Entra token refresh. Bedrock requires installing boto3 in the gateway's own Python environment, AWS region configuration, IAM permissions, and model access; it does not use a static bearer API key. This does not implement Google Vertex AI IAM or other managed variants of the direct Gemini API.
Start a local gateway
Use Python 3.10 or later. All providers except Bedrock use the standard library, so they require no pip install. Keep Python and provider secrets on the server. Run from the project root in PowerShell:
$env:COMPANION_PROVIDER = 'offline'
# Generate once for this development session. Do not commit or share the value.
$env:COMPANION_GATEWAY_TOKEN = [guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N')
$env:COMPANION_GATEWAY_URL = 'http://127.0.0.1:8765/v1/companion'
python Services/CompanionAI/gateway.py
The gateway stays in the foreground. Start Unreal or the game server from another process that inherits the same COMPANION_GATEWAY_URL and COMPANION_GATEWAY_TOKEN. An already-running editor will not see environment changes made in a different shell. For example, use a second shell with those same values, or start the gateway in a separate terminal process and then launch Unreal from the configured parent shell. Do not write the token into .ini files or Blueprint defaults.
For local Ollama, install and start Ollama separately, pull a model suitable for the machine and license, then set:
$env:COMPANION_PROVIDER = 'ollama'
$env:COMPANION_MODEL = 'YOUR_INSTALLED_MODEL_NAME'
python Services/CompanionAI/gateway.py
For Groq, use the same gateway token and URL, set COMPANION_PROVIDER=groq, set COMPANION_MODEL to an available model ID, and inject GROQ_API_KEY into the gateway process through your secret manager or process environment. For Gemini, use gemini, an available Gemini model ID, and GEMINI_API_KEY. Credentials are never read from HTTP request fields. Restart the gateway after changing provider/model; the game transport stays the same.
Configuration reference
| Variable | Default / meaning |
|---|---|
COMPANION_PROVIDER |
offline |
COMPANION_MODEL |
empty; required for every model provider |
COMPANION_ENDPOINT |
preset endpoint, except Azure/custom must set one; for Gemini this is the API root |
COMPANION_KEY_ENV |
override the name of the environment variable containing the provider key |
COMPANION_TIMEOUT |
20 seconds; range 1โ60 |
COMPANION_MAX_TOKENS |
256; range 1โ2048; reasoning models may spend some budget on reasoning |
AWS_REGION |
us-east-1, Bedrock only |
COMPANION_BIND |
127.0.0.1, gateway listener |
COMPANION_PORT |
8765 |
COMPANION_GATEWAY_TOKEN |
required by gateway, at least 32 ASCII characters with no whitespace; also present on the trusted Unreal server |
COMPANION_GATEWAY_URL |
read by Unreal; unset means local fixed guidance |
Cloud endpoint overrides require HTTPS. Loopback HTTP is allowed for development. Redirects are rejected to avoid forwarding credentials to redirected hosts. There is no automatic retry or switch to a different paid provider. Provider failures return fixed offline guidance with fallback=true; detailed upstream bodies and dialogue are not logged.
Unreal integration
- Enable/build
NiceCompanionAI(enabled by default). A future C++ NPC module should declareNiceCompanionAIas a module and plugin dependency when using its public API. - In the authoritative NPC/companion interaction handler, validate player ownership, companion permissions, distance, and a per-player cooldown/quota first. Client requests must enter through a player-owned server RPC; do not put an unrestricted RPC on a world NPC.
- Get
UNiceCompanionAISubsystemfrom the GameInstance and bindOnReplybefore callingAskCompanion(Message). The returned GUID correlates the reply with the requesting player/companion. Invalid GUID means rejected before dispatch. Local fallback is deferred to the next world tick. - Maintain a server-side map from GUID to the authorized recipient. Revalidate that the player and companion still exist when a reply arrives. Deliver personal dialogue through an owning-client RPC; replicate party narration only to the relevant party.
OnReply(RequestId, Text, bFallback, bSuccess)reports results.bSuccess=falseindicates a transport/response failure despite supplying fallback text. A gateway-generated fallback hasbSuccess=true, bFallback=true. Missing gateway settings intentionally select local fallback.- Call
CancelRequestwhen the conversation closes or the owning NPC/player goes away. Cancellation suppresses local delivery; a provider request already accepted upstream may still incur usage. World cleanup during travel and subsystem shutdown cancel pending/deferred delivery. No persistence, gameplay rewards, or inventory mutation is performed by this subsystem.
Standalone, listen server, and dedicated server can call the subsystem. A remote client is rejected by a runtime net-mode check as well as Blueprint authority metadata. The subsystem also applies a 0.5-second instance-wide request interval and permits at most four pending/deferred requests; this is overload protection, not a per-player billing policy. NPC ownership routing and UI are not implemented here yet.
Gateway contract
POST /v1/companion, Authorization: Bearer <gateway token>, JSON body:
{"messages":[{"role":"user","content":"How do I equip armor?"}]}
Only user and assistant history roles are accepted. The system prompt is owned by the gateway. Requests cannot choose a model, endpoint, API key, token limit, or system instructions. Limits: 32 KiB body, 16 history entries, 2,000 characters per entry, 12,000 total content characters, final entry must be from the user, four active generations. The gateway returns:
{"text":"Open your inventory and choose the Armor tab.","provider":"configured-provider","fallback":false}
The example text illustrates the contract; it is not a claim that the inventory UI already exists. Responses are bounded to 8,000 characters. Thinking blocks are omitted. No response field executes a command.
Add a provider
For an OpenAI-compatible text endpoint, either use compatible with COMPANION_ENDPOINT, COMPANION_MODEL, and COMPANION_PROVIDER_API_KEY, or add a named entry to PRESETS in providers.py:
"my_provider": (ChatCompletions, "https://api.example.com/v1/chat/completions", "MY_PROVIDER_API_KEY"),
Check the provider's official API documentation, including token parameter names and response differences. The shared adapter uses max_completion_tokens for OpenAI/Azure and max_tokens elsewhere; a provider that differs needs its own adapter.
For a native protocol:
- Subclass
Adapterinproviders.pyand implementcomplete(config, system, messages, send=post_json). - Resolve secrets through
self.key(config)or a server SDK credential chain. Never accept credentials or an endpoint from player requests. - Translate the normalized system string and
{role, content}messages into that protocol. Applyconfig.max_tokensandconfig.timeout. - Call the injected
send(url, headers, payload, timeout)for HTTP APIs. SDK adapters must explicitly disable uncontrolled retries and use bounded timeouts. - Parse only visible text and return
Reply(text, config.provider). Reject empty/malformed responses. RaiseProviderErrorwith a short safe code on failure; never include request payloads, authorization headers, or raw provider responses in error strings. - Register
(YourAdapter, default_endpoint, default_key_environment_name)inPRESETS. Keep all model IDs operator-configurable. - Add tests with an injected fake
send. Cover exact headers/body, multi-block text, empty/malformed replies, errors, timeout behavior, and missing credentials. Add a documentation row and setup example. - Run contract tests and then perform an explicitly configured live smoke test with your account and chosen model before deploying it. Verify quota/billing and data terms. Adding an adapter does not require changing character, quest, inventory, or NPC movement code.
Deployment boundary and remaining work
The supplied standard-library HTTP server is a private development service, not a public player endpoint. For production use a managed reverse proxy/service with TLS, restricted inbound access, request-rate and body-size enforcement, connection limits, per-player quotas, and observability without raw dialogue. The four-generation limit does not bound the development HTTP server's total connection threads.
Dedicated servers can authenticate to a private gateway using a server-managed secret. For player-hosted games, do not distribute a publisher master gateway token to listen-server users. Use a future backend that validates EOS identities/session tickets and issues short-lived, scoped credentials, or let hosts use their own local model/provider account. EOS-authenticated AI access is not implemented in this slice. No EOS portal changes have been made.
The NPC plugin now supplies companion conversation UI, an owner-only bounded recent conversation and offline gameplay hints; see VehiclesAndNPCs.md. Rich story context, spoiler policy, moderation and per-player cost limits remain production integration tasks. The fixed fallback can later become a curated localized hint catalog. Persistent character progression and shared party story state remain separate from provider conversation data.
Verification
From the project root:
python -m unittest discover -s Services/CompanionAI -p 'test_*.py' -v
Tests cover each adapter family and compatible preset, credential failure, malformed replies, input constraints, endpoint transport, redirect rejection, HTTP authentication, endpoint injection rejection, route handling, and offline fallback. They use fixtures and loopback networking only. Unreal build status and remaining multiplayer tests are recorded in the architecture document.