# AI (/docs/nodes/ai)

LLM inference via OpenRouter (300+ models) or Venice AI (private inference).



One AI node, two BYOK providers. Pick a credential from either provider, choose a model, and reference the result downstream as `{aiResponse.data}`. The node, configuration UI, and output shape are identical across providers; only the credential and the model catalog change.

Providers [#providers]

| Provider       | Best for                                                                                   | Catalog                                                               | Notes                                                                                               |
| -------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **OpenRouter** | Access to 300+ models in one place (GPT, Claude, Gemini, DeepSeek, Grok, Qwen, Kimi, etc.) | Aggregated; model IDs are `provider/model` (e.g. `openai/gpt-5-mini`) | Live model list loaded from your key. Cost reporting included.                                      |
| **Venice AI**  | Private inference, uncensored models, Web3-native                                          | Curated (Llama, Qwen, DeepSeek, Dolphin, Venice Uncensored)           | Live model list loaded from your key. Bring-your-own-cost (no per-call billing through Solaris AI). |

Pick whichever fits your workflow. You can add credentials for both and switch per-node.

Quick start [#quick-start]

1. Add an [OpenRouter](https://openrouter.ai/keys) or [Venice AI](https://venice.ai/settings/api) key under [Credentials](/docs/credentials/adding).
2. Drop an AI node onto the canvas. In the config dialog, pick your credential.
3. Choose a model from the live dropdown (or type any model ID).
4. Write a prompt, e.g. `Summarize this token: {json birdeyeResponse.data}`.
5. Wire the next node to read `{aiResponse.data}`.

That's the whole loop. Everything below is detail for when you need it.

Prerequisites [#prerequisites]

* An API key from either provider:
  * **OpenRouter**: [openrouter.ai/keys](https://openrouter.ai/keys)
  * **Venice AI**: [venice.ai/settings/api](https://venice.ai/settings/api). Add any USD balance to your Venice account first; Venice keys stay inactive on a $0 balance and will fail at runtime. See the [Venice AI setup walkthrough](/docs/credentials/integrations#venice-ai) for the exact steps.
* Add it as a credential in [Credentials](/docs/credentials/adding).

Operations [#operations]

| Operation       | Description                   |
| --------------- | ----------------------------- |
| Chat completion | Send a prompt, get a response |

Input modes [#input-modes]

| Mode         | When to use                                                                                                                                                                       |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Prompt       | Default. Write a natural-language prompt with template expressions like `{birdeyeResponse.data.value}`.                                                                           |
| JSON Request | When you need multi-turn messages, a `developer` role, or multimodal content parts. Send an OpenAI-compatible chat-completions body, see [JSON Request mode](#json-request-mode). |

Configuration [#configuration]

| Field           | Type              | Required          | Description                                                                                                                                                                                              |
| --------------- | ----------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AI Provider Key | select            | Yes               | Your OpenRouter or Venice credential from Connections. The selected credential's platform decides which provider runs.                                                                                   |
| Response Name   | text              | Yes               | The variable name downstream nodes use to reference this node's output. Defaults to `aiResponse`, so you read the reply as `{aiResponse.data}`. Rename it if you have multiple AI nodes in one workflow. |
| model           | searchable select | Yes               | Pick from the loaded model list (live from the active provider) or type a full model ID. Defaults to `gpt-5-mini` (OpenRouter) or `llama-3.2-3b` (Venice) if left blank in non-strict execution.         |
| prompt          | string            | Yes (prompt mode) | User message. Supports template expressions.                                                                                                                                                             |
| requestJson     | string            | Yes (JSON mode)   | OpenAI-compatible JSON body, see [JSON Request mode](#json-request-mode).                                                                                                                                |
| systemPrompt    | string            | No                | System instructions. Also templated; undefined paths fail the same way as in `prompt`.                                                                                                                   |
| temperature     | number            | No                | `0` to `2`, controls randomness.                                                                                                                                                                         |
| maxTokens       | number            | No                | Maximum response length.                                                                                                                                                                                 |
| responseFormat  | string            | No                | `text` (default) or `json_object`. See [Response format](#response-format).                                                                                                                              |

Select a credential first to load the live model list. The list is fetched directly from the active provider, so it always reflects what your key can use.

**Switching credentials across providers clears the saved model.** OpenRouter IDs (e.g. `openai/gpt-5-mini`) and Venice IDs (e.g. `llama-3.2-3b`) aren't interchangeable, so the node prompts you to pick again rather than ship a mismatched pair to the wire.

The user prompt is hard-capped at 200,000 characters. Anything longer is truncated with a marker (`[Prompt truncated by Solaris to stay within model context limits]`) before the call goes out.

Model IDs by provider [#model-ids-by-provider]

| Provider   | Format               | Example                                                                                 |
| ---------- | -------------------- | --------------------------------------------------------------------------------------- |
| OpenRouter | `<provider>/<model>` | `openai/gpt-5-mini`, `anthropic/claude-opus-4.6`, `google/gemini-3.1-pro-preview`       |
| Venice AI  | bare canonical ID    | `llama-3.2-3b`, `llama-3.3-70b`, `qwen-2.5-vl`, `deepseek-r1-671b`, `venice-uncensored` |

You can type a custom ID in either case. The runtime forwards it unchanged to the active provider, so typos fail at the wire with the provider's own error message (e.g. "model not found").

Advanced parameters [#advanced-parameters]

These are OpenAI-compatible parameters; both providers accept them.

| Field            | Type   | Description                                                                                 |
| ---------------- | ------ | ------------------------------------------------------------------------------------------- |
| topP             | number | Nucleus sampling threshold. Range `(0, 1]` (0 is invalid; values above 1 are clamped to 1). |
| frequencyPenalty | number | Penalize repeated tokens. Range `-2` to `2`.                                                |
| presencePenalty  | number | Penalize tokens already present. Range `-2` to `2`.                                         |
| stop             | string | Comma-separated stop sequences. Up to 4 (extras are dropped).                               |
| seed             | number | Deterministic output seed (model-dependent). Must be an integer.                            |

Reasoning [#reasoning]

Some models support extended reasoning. When enabled, the model may return additional reasoning metadata alongside the reply.

| Field            | Type    | Description                                                                                                                                                                   |
| ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| reasoningEnabled | boolean | Enable extended thinking.                                                                                                                                                     |
| reasoningEffort  | string  | `xhigh`, `high`, `medium`, `low`, `minimal`, or `none`. Roughly: `xhigh` \~ 95% of token budget on reasoning, `high` \~ 80%, `medium` \~ 50%, `low` \~ 20%, `minimal` \~ 10%. |

Reasoning is model-dependent on both providers. Claude 4.6 (via OpenRouter) uses adaptive thinking automatically (the effort setting is ignored). Models that don't support reasoning will reject the call. Venice's reasoning-capable models surface their thinking as `reasoning_content` and are normalized into the same `reasoning` output field as OpenRouter.

JSON Request mode [#json-request-mode]

When `inputMode` is `json`, the body must be an object with an OpenAI-compatible `messages[]` array. This is the only shape the runtime forwards to either provider.

Static JSON is checked in the editor before the workflow runs. If the JSON contains template expressions, the final shape is checked after those templates render at runtime. Legacy provider formats such as Gemini `contents[]` or Anthropic top-level `system` are rejected when the rendered request is parsed.

```json
{
  "messages": [
    { "role": "system", "content": "You are a concise assistant." },
    { "role": "user", "content": "Summarize: {json birdeyeResponse.data}" }
  ]
}
```

`role` must be one of `system`, `user`, `assistant`, `developer`. `content` is a non-empty string, or an OpenAI-format content-parts array (`[{ "type": "text", "text": "..." }, { "type": "image_url", "image_url": { "url": "..." } }]`) for multimodal models.

The body may also set:

| Field                               | Effect                                                                                                                                            |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `temperature`                       | Overrides the node-level `temperature`.                                                                                                           |
| `max_tokens` (or `maxOutputTokens`) | Overrides the node-level `maxTokens`. Capped at 128,000. On Venice this is sent on the wire as `max_completion_tokens` (Venice's preferred name). |

All other fields in the body are ignored. Advanced parameters (`topP`, `frequencyPenalty`, etc.) and `responseFormat` come from node settings, not the JSON body. Set them in the editor's [Advanced parameters](#advanced-parameters) panel or [Response format](#response-format) toggle.

Template expressions inside `requestJson` are resolved before the body is parsed as JSON, so a path like `{json codeResponse.data}` interpolates a JSON value at that position. Undefined paths fail loudly with `AI JSON input uses undefined variables: <path>` instead of producing invalid JSON or empty substitutions.

Response format [#response-format]

The `responseFormat` setting controls how the model's reply lands in the output envelope. &#x2A;*It must agree with what your prompt asks for.** Instructing the model to respond in JSON without flipping this toggle leaves `.data` as a string that downstream nodes can't traverse with field paths.

| Value            | Behavior                                                                                                                                                                                                                                                                             |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `text` (default) | The reply is returned verbatim. `.data` is a string. Use `{aiResponse.data}` to drop it into a downstream prompt or HTTP body.                                                                                                                                                       |
| `json_object`    | The provider is asked for structured output and the reply is `JSON.parse`d before it lands in the envelope. `.data` is a parsed object you can address with field paths like `{json aiResponse.data.summary}`. If the model returns invalid JSON, the node fails with a clear error. |

Some models don't support structured outputs and will fail when `json_object` is set. Pick a model that does, or fall back to `text` and parse downstream.

Output [#output]

In &#x2A;*`text`** mode (default):

```json
{
  "success": true,
  "data": "The current price of SOL is approximately $150.",
  "model": "openai/gpt-5-mini",
  "usage": { "promptTokens": 42, "completionTokens": 18 }
}
```

In &#x2A;*`json_object`** mode:

```json
{
  "success": true,
  "data": { "price": 150, "asset": "SOL", "confidence": "high" },
  "model": "llama-3.3-70b",
  "usage": { "prompt_tokens": 42, "completion_tokens": 18 }
}
```

The `model` field reflects the provider's canonical ID (slashed for OpenRouter, bare for Venice). The `usage` shape mirrors whatever the provider returns; OpenRouter uses camelCase via its SDK, Venice uses snake\_case directly. Both expose token counts you can read with `{aiResponse.usage.prompt_tokens}` or `{aiResponse.usage.promptTokens}` as appropriate.

Reasoning models may add a `reasoning` field when reasoning is enabled.

Reference patterns:

* `{aiResponse.data}` is the model's reply as a string. In `text` mode this is the raw model output. In `json_object` mode `.data` is an object, so without the `json ` prefix the template substitutes `[object Object]` (string coercion). Use `{json aiResponse.data}` instead when `.data` is an object.
* `{aiResponse.data.field}` is a scalar field (string / number / boolean) from a `json_object` reply, dropped in unquoted. The right shape for prompts and most string fields in HTTP query strings.
* `{json aiResponse.data.field}` is the same field JSON-encoded (strings get quotes, objects/arrays get JSON syntax). Use this when interpolating into JSON HTTP bodies, or when the field itself is an object or array.
* `{json aiResponse}` is the full response envelope, useful for HTTP bodies or debugging.

Template variables [#template-variables]

The system prompt, prompt, and JSON-input fields all support template expressions like `{json codeResponse.data.field}`. Two things to know:

* **Use the variable picker** (the `{ }` button next to any text field). It walks upstream nodes and suggests valid paths, including nested field paths once the workflow has run once. Before the first run, it falls back to a static schema for known node types so you still get useful suggestions.
* **Undefined paths fail loudly.** If a path doesn't resolve (typo, wrong nesting, upstream node didn't emit that field), the node fails before calling the model. Checked across all three template fields (`systemPrompt`, `prompt`, `requestJson`) so a single failure surfaces every offending path at once. The error message mentions the offending path: `Prompt template uses undefined variables: <path>` in prompt mode (covers system prompt + prompt), `AI JSON input uses undefined variables: <path>` in JSON Request mode (covers system prompt + JSON body). This is the most common cause of "I don't see any data" replies; fix the path instead of debugging the model. A path that resolves to `null` (e.g. `balanceResponse.mint` on a SOL balance) is treated as a present value, not a missing one.

When to pick which provider [#when-to-pick-which-provider]

* **Default to OpenRouter** if you need access to a specific commercial model (GPT, Claude, Gemini) or want the broadest catalog.
* **Pick Venice** when you need private inference, want to run uncensored or open-source models, or already use Venice for the rest of your stack.

You can use both in the same workflow. Each AI node carries its own credential, so one node can call GPT-5 while the next routes to Llama through Venice.

Common use cases [#common-use-cases]

* Analyze on-chain data and generate trading signals
* Summarize token metrics into human-readable alerts
* Parse unstructured data into structured JSON with `json_object` response format
* Use reasoning models for complex multi-step analysis
* Route sensitive prompts through Venice for private inference, public prompts through OpenRouter for breadth

Next steps [#next-steps]

* [Jupiter](/docs/nodes/defi/jupiter) connects AI to on-chain swaps
* [Configuring Nodes](/docs/editor/configuring-nodes) explains template expressions for dynamic prompts
