OpenAI-compatible chat: Chat completion
Sends one prompt to any API that speaks OpenAI’s /chat/completions (OpenAI, Mistral, Ollama, vLLM, LM Studio, LiteLLM) and returns the reply. Between them only base_url, model and the credential change.
- Kind:
openai-compatible.chat - Palette: Integrations, under OpenAI-compatible chat
- Credential: optional; a credential of kind
bearer, named incredential
When to use
Section titled “When to use”- Calling a hosted model other than Claude, or one you run yourself.
- A local model with no authentication: leave
credentialout, since this connector’s credential is optional. - Getting an answer in a fixed shape: give
output_schemaand branch onnodes.<id>.output.data.
The request is made by whichever agent claims the step, so localhost in base_url means that
agent’s machine. To reach a model at home, run an agent next to it that advertises connector
plus a capability of your own, and name that capability in the step’s requires. Under
spool dev the embedded agent is on your machine, so localhost works there as it is.
Connection (with)
Section titled “Connection (with)”| Field | Required | Description |
|---|---|---|
base_url |
yes | OpenAI-compatible API base, including the version segment. OpenAI: https://api.openai.com/v1 · Mistral: https://api.mistral.ai/v1 · Ollama: http://localhost:11434/v1 · vLLM / LM Studio / LiteLLM: your endpoint. For example: https://api.openai.com/v1. |
model |
yes | Model id for the target provider (gpt-4o-mini, mistral-small-latest, llama3.2, …). For example: gpt-4o-mini. |
Input (input)
Section titled “Input (input)”| Field | Type | Required | Description |
|---|---|---|---|
prompt |
string | yes | The user message. Reference upstream data with {{ … }}. Written in a markdown editor; the rendered HTML is stored beside it as <name>_html. |
system |
string | no | Optional system prompt to steer the model (persona, format, constraints). |
max_tokens |
integer | no | The most tokens the reply may use, sent as max_tokens. Unset leaves it to the provider. A model that thinks spends from the same budget, and a reply cut off at it fails a step that gives output_schema, with no retry. From 1 to 131072. |
output_schema |
object | no | A JSON Schema for the reply. When set, the model is asked to answer in this shape, and the parsed reply is the step’s data. The top must be an object; strict providers also want every object to list all its properties in “required” and set “additionalProperties”: false. |
output_name |
string | no | The name the schema goes by in the request. Default "result". |
output_mode |
string | no | How the shape is asked for: json_schema sends it as a strict response_format; json_object asks only for JSON and puts the schema in the system prompt, for providers without json_schema. One of "json_schema", "json_object". Default "json_schema". |
Settings
Section titled “Settings”Beside with and input in the node’s config.
| Field | Type | Required | Description |
|---|---|---|---|
credential |
string | no | Name of the project credential the action authenticates with. |
fail_on_error_status |
boolean | no | Treat any 4xx/5xx as a failure. On by default: an action either did its job or did not. Default true. |
optional_when_untrusted |
boolean | no | Run without the credential when the run is untrusted (a fork pull request), instead of failing. The credential is still withheld. |
retry_on |
array | no | Status codes that fail the node instead of returning, so its retry: policy applies (e.g. [429, 503]). |
Output
Section titled “Output”The HTTP response. The reply is under body: nodes.<id>.output.body.<field>.
| Field | Type | Description |
|---|---|---|
status |
integer | The HTTP status code. |
headers |
object | The response headers. |
body |
object | The response body, parsed as JSON when it is JSON. |
body.choices |
array | The replies; choices[0].message.content is the text. |
body.id |
string | |
body.model |
string | |
body.usage |
object | prompt_tokens, completion_tokens and total_tokens. |
data |
object | The reply as JSON, checked against output_schema. Set only when the step gives one. |
How it runs
Section titled “How it runs”- On an agent with the
connectorcapability. The managed environments have one, so this needs no agent of your own. - One
POSTrequest to an address built fromwith. - Any 4xx or 5xx fails the step, unless
fail_on_error_statusisfalse.retry:asks again after a 5xx, a 401, 408, 409, 423, 425 or 429, or a 4xx withRetry-After, and not after any other 4xx, which would get the same answer. - With
output_schemaset, the reply is parsed and checked against it and returned asdata. A reply that does not fit fails the step, quoting what the model said; the step’sretry:sends the request again, except after a refusal or a cut-off reply.
Example
Section titled “Example”version: hyperspool-graph/v1nodes: - id: summarize type: openai-compatible.chat config: credential: openai with: base_url: https://api.openai.com/v1 model: gpt-4o-mini input: system: Answer in at most two sentences. prompt: { $template: "Summarise this changelog: {{ input.changelog }}" } - id: post type: shell config: run: { $template: "echo '{{ nodes.summarize.output.body.choices[0].message.content }}'" }edges: - { from: summarize, to: post }The reply is at nodes.<id>.output.body.choices[0].message.content.
With output_schema, classify an email and route on the category:
version: hyperspool-graph/v1nodes: - id: classify type: openai-compatible.chat config: credential: openai with: { base_url: https://api.openai.com/v1, model: gpt-4o-mini } input: prompt: { $template: "Classify this support email. {{ input.email }}" } output_schema: type: object properties: category: { type: string, enum: [billing, bug, other] } urgent: { type: boolean } required: [category, urgent] additionalProperties: false retry: { attempts: 3 } - id: route type: switch config: cases: - { name: billing, when: "nodes.classify.output.data.category == 'billing'" } - { name: bug, when: "nodes.classify.output.data.category == 'bug'" } default: otheredges: - { from: classify, to: route }The schema goes out as a strict response_format of type json_schema, which OpenAI, Mistral,
Ollama and vLLM accept; on OpenRouter it depends on which provider serves the model. Strict
mode wants every property listed in required and "additionalProperties": false on every
object. For a provider that only offers JSON mode, set output_mode: json_object: the request
asks for any JSON object and puts the schema in the system prompt. Either way the reply is
checked against the schema here, so a provider that ignores the request still cannot hand a
wrong shape to the next step. A reply that does not fit fails the step with the model’s text in
the error, and retry: asks again. See
Connectors.
A strict response_format makes the model answer in the schema’s shape, so an enum gets one
of its values even when none of them is right. When the answer might not be on the list, add a
value such as unknown or other and give it a branch.
Reply length
Section titled “Reply length”max_tokens caps the reply, from 1 to 131072. Unset, the request leaves it out and the
provider’s own default applies. A model that thinks before it answers spends from the same
budget. A reply cut off at the cap ends with finish_reason: length; with output_schema set,
that fails the step, and retry: does not ask again, since the same cap would cut it off again.
Some OpenAI reasoning models take only max_completion_tokens and refuse max_tokens; leave it
unset for those.
A reasoning model (GLM, DeepSeek R1, Qwen with thinking on) can spend 300 to 1500 tokens
thinking before it writes a word of the answer, so a max_tokens of 1024 cuts it off on
anything tricky: give it 4096 or more. Its thinking is at
nodes.<id>.output.body.choices[0].message.reasoning_content (reasoning on some servers),
apart from the answer in content. Cut off before the thinking ends, some servers copy the
unfinished thinking into content as well; with output_schema set that is not read as the
answer, and the step fails saying the model gave no answer after its reasoning.
See Connectors for credentials and versions, and the node reference for every other node.