Skip to main content

LLM Planner

The LLM planner turns a goal written in plain language, such as "Charge order ORD-42 and email the customer a receipt", into a plan of steps that the engine runs with your capabilities. It ships as the nexora-planner-llm plugin and works with four providers:

ProviderPageReachesDefault modelSDK
anthropicAnthropicClaude modelsclaude-opus-5-5Official Anthropic Java SDK
openaiOpenAIOpenAI modelsnone, set NEXORA_LLM_MODELOfficial OpenAI Java SDK
openrouterOpenRouterHundreds of models from many vendorsnone, set NEXORA_LLM_MODELOfficial OpenAI Java SDK, pointed at OpenRouter
geminiGeminiGoogle Gemini modelsnone, set NEXORA_LLM_MODELOfficial Google Gen AI Java SDK

Not sure whether it fits your workload? See Use cases.

How it works​

  1. An intent arrives. The engine asks the LLM planner first, ahead of the rule-based planner, for every goal that has at least one capability to plan with. To keep some goals on rules, see Routing common goals to rules.
  2. The planner sends the model the goal, the capabilities the intent's tenant may use, and the names of the values in the intent context. The values themselves never leave your process.
  3. The model answers with a plan as structured JSON. Each provider is asked to enforce the plan's JSON schema.
  4. The planner checks the plan before anything runs: every capability exists, step ids are unique, dependencies form no cycle, each required input is bound, and each binding points at a context value or step output that will exist.
  5. A valid plan runs and is cached. Anything else (a refusal, a timeout, an invalid plan, a bad key) logs a WARN with the reason, and the engine runs the rule-based plan instead.

The model can only arrange your capabilities. It cannot add a capability, call anything you did not register, or reach a capability another tenant owns.

Quick start​

  1. Build the plugin JAR:

    mvn package -pl nexora-planner-llm -am
  2. Load it with your capabilities, and keep your step definitions, which become the fallback plan:

    NexoraEngine engine = NexoraEngine.builder()
    .withPlugin(myCapabilities)
    .withPluginJar(Path.of("nexora-planner-llm/target/nexora-planner-llm-0.5.0-SNAPSHOT-plugin.jar"))
    .withStepDefinition(...)
    .build();

    The build names the JAR after the project version, for example nexora-planner-llm-0.5.0-SNAPSHOT-plugin.jar on a snapshot build. With the CLI, list the JAR under plugins in nexora.json.

  3. Pick a provider and set its key. For example:

    export ANTHROPIC_API_KEY=...
  4. Try it without writing code. The demo runs one goal through the rule-based planner and the LLM planner side by side:

    mvn package -pl tests/llm-planner-demo -am
    java -jar tests/llm-planner-demo/target/llm-planner-demo.jar --offline # no key needed
    java -jar tests/llm-planner-demo/target/llm-planner-demo.jar # uses your provider

Choosing a provider​

  • Start with Anthropic if you have no preference. It is the only provider with a default model, it has server-side refusal fallback, and it is the most tested path here.
  • Use OpenAI if your organisation already has an OpenAI account and approved models.
  • Use OpenRouter to try several models behind one key, to compare cost and quality, or to reach models from vendors that have no Java SDK of their own.
  • Use Gemini if you are on Google Cloud or already pay for Gemini, and want to call Google directly rather than through OpenRouter.

Whichever you pick, the planner behaves the same: same prompt, same schema, same checks, same fallback. You can switch providers by changing environment variables, with no code change.

Settings shared by every provider​

VariableDefaultMeaning
NEXORA_LLM_PROVIDERdetected from the keys setanthropic, openai, openrouter or gemini
NEXORA_LLM_MODELprovider defaultModel to plan with
NEXORA_LLM_MAX_TOKENS16000Output token limit, including any reasoning
NEXORA_LLM_TIMEOUT60 secondsPer attempt
NEXORA_LLM_MAX_RETRIES2SDK retries for rate limits, overload and server errors
NEXORA_LLM_CACHE_TTLPT10MHow long a plan is reused; 0 turns the cache off
NEXORA_LLM_PRIORITY100Planner priority; higher is tried first
NEXORA_LLM_API_KEY_FILEFile holding the key, for example a mounted secret

Each setting also has a nexora.llm.* system property, which takes precedence. API keys are never read from system properties. The full list is in the module reference.

Reading the logs​

Every fallback logs one WARN line with a reason:

WARN LLM planner declined, falling back to the next planner provider=anthropic model=claude-opus-5-5
goalHash=3f9a1c0e7b2d reason=INVALID_PLAN detail=the model returned no steps
ReasonUsually meansWhat to do
INVALID_PLANThe plan failed a check, or no capability fits the goalImprove capability descriptions; declare outputParameters
REJECTEDBad key, no permission, unknown model, or no creditCheck the key, the model name and the account
UNAVAILABLERate limited, overloaded or a server error, after retriesWait, raise retries, or check the provider's status page
NETWORKNo connection, or the request timed outCheck egress, proxy and NEXORA_LLM_TIMEOUT
REFUSEDThe model or a safety filter declinedReword the goal or capability descriptions
TRUNCATEDThe answer hit max_tokensRaise NEXORA_LLM_MAX_TOKENS
EMPTYThe response had no textUsually transient; retry
CLIENT_ERRORAn SDK failure nothing recognised (also logged at ERROR)Read the stack trace; report it if it repeats

Log lines identify the goal by a hash rather than its text. The detail can quote the model's answer, though, and a model may repeat words from the goal in it (for example as an unknown capability id), so treat these logs as you would logs that may contain request text.