Skip to main content

LLM call (h2oGPTe)

A composite action that runs a single h2oGPTe LLM call — the Workflows equivalent of an LLM step. It supports three modes: direct inference, structured JSON output enforced by a schema (guided_json), and multi-turn agent mode (use_agent, with configurable accuracy, max turns, and agent type).

Authentication needs no configuration: by default the h2oGPTe URL is resolved via H2O Cloud Discovery and access tokens are minted from the platform token auto-injected into every runner (H2O_CLOUD_CLIENT_PLATFORM_TOKEN), so the call runs as the user who triggered the workflow. Pass h2ogpte_address and/or api_key only to override (e.g. to reach an h2oGPTe outside the current cloud).

Because the action runs inline, it shares the calling job's filesystem in both directions:

  • The answer is written to a local file (answer_file, default answer.md) that later steps read directly — no storage round-trip. It is a file rather than an action output because step outputs are single-line key=value pairs meant for small values; LLM answers are long and multiline.
  • The context_files input names local files whose contents are appended to the prompt — so earlier steps (or download steps) can assemble context for the call.

Action

id: h2ogpte-llm
name: h2oGPTe LLM Step

inputs:
prompt:
type: string
required: true
description: "User message sent to the LLM"

system_prompt:
type: string
default: ""
description: "System prompt defining the LLM's role and output format"

answer_file:
type: string
default: "answer.md"
description: "Local file path where the answer is written, readable by later steps in the job"

context_files:
type: string
default: ""
description: "Whitespace-separated local file paths appended to the prompt as context"

h2ogpte_address:
type: string
default: ""
description: "h2oGPTe URL override (default: resolved via H2O Cloud Discovery)"

api_key:
type: string
default: ""
secret: true
description: "h2oGPTe API key override (default: platform token of the triggering user)"

llm:
type: string
default: ""
description: "Model name, e.g. claude-sonnet-4-6 (empty = h2oGPTe default)"

use_agent:
type: bool
default: false
description: "Run as a multi-turn h2oGPTe agent instead of direct inference"

agent_accuracy:
type: string
default: "standard"
description: "Agent accuracy: quick, basic, standard, or maximum (agent mode only)"

agent_max_turns:
type: int
default: 5
description: "Maximum agent turns (agent mode only)"

agent_type:
type: string
default: ""
description: "Agent flavor: search, data_science, or deep_research (agent mode only)"

json_schema:
type: string
default: ""
description: "JSON schema enforcing structured output via guided_json (non-agent mode only)"

collection_id:
type: string
default: ""
description: "h2oGPTe collection ID for RAG grounding (empty = no RAG)"

timeout:
type: int
default: 600
description: "Query timeout in seconds"

outputs:
chat_session_id:
description: "h2oGPTe chat session ID of the call, for follow-ups or auditing"
value: ${{ .steps.query.outputs.chat_session_id }}

steps:
- name: "Install h2oGPTe client"
run: sudo uv pip install --system h2ogpte h2o-authn h2o-cloud-discovery

- id: query
name: "Query h2oGPTe"
timeout: "30m"
env:
H2OGPTE_ADDRESS: "${{ .inputs.h2ogpte_address }}"
H2OGPTE_API_KEY: "${{ .inputs.api_key }}"
PROMPT: "${{ .inputs.prompt }}"
SYSTEM_PROMPT: "${{ .inputs.system_prompt }}"
ANSWER_FILE: "${{ .inputs.answer_file }}"
CONTEXT_FILES: "${{ .inputs.context_files }}"
LLM: "${{ .inputs.llm }}"
USE_AGENT: "${{ .inputs.use_agent }}"
AGENT_ACCURACY: "${{ .inputs.agent_accuracy }}"
AGENT_MAX_TURNS: "${{ .inputs.agent_max_turns }}"
AGENT_TYPE: "${{ .inputs.agent_type }}"
JSON_SCHEMA: "${{ .inputs.json_schema }}"
COLLECTION_ID: "${{ .inputs.collection_id }}"
TIMEOUT: "${{ .inputs.timeout }}"
run: |
cat > llm_step.py <<'PY'
import json
import os

from h2ogpte import H2OGPTE

address = os.environ.get('H2OGPTE_ADDRESS')
api_key = os.environ.get('H2OGPTE_API_KEY')

if api_key and address:
client = H2OGPTE(address=address, api_key=api_key)
else:
# Default: resolve h2oGPTe via H2O Cloud Discovery and mint access
# tokens from the runner's auto-injected platform token, so the
# call runs as the user who triggered the workflow.
import h2o_authn
import h2o_discovery

disc = h2o_discovery.discover()
if not address:
address = disc.services['h2ogpte'].uri.rstrip('/')
token_provider = h2o_authn.TokenProvider(
refresh_token=os.environ['H2O_CLOUD_CLIENT_PLATFORM_TOKEN'],
client_id=disc.clients.get('platform').oauth2_client_id,
)
client = H2OGPTE(address=address, token_provider=token_provider)

message = os.environ['PROMPT']
for path in os.environ.get('CONTEXT_FILES', '').split():
with open(path) as f:
message += f"\n\n=== {path} ===\n{f.read()}"

collection_id = os.environ.get('COLLECTION_ID') or None
chat_session_id = client.create_chat_session(collection_id)
print(f'Chat session: {chat_session_id}')

llm_args = {}
if os.environ['USE_AGENT'] == 'true':
llm_args.update({
'use_agent': True,
'agent_accuracy': os.environ['AGENT_ACCURACY'],
'agent_max_turns': int(os.environ['AGENT_MAX_TURNS']),
})
if os.environ.get('AGENT_TYPE'):
llm_args['agent_type'] = os.environ['AGENT_TYPE']
elif os.environ.get('JSON_SCHEMA'):
llm_args.update({
'response_format': 'json_object',
'guided_json': json.loads(os.environ['JSON_SCHEMA']),
})

query_kwargs = {
'message': message,
'timeout': int(os.environ['TIMEOUT']),
}
if os.environ.get('SYSTEM_PROMPT'):
query_kwargs['system_prompt'] = os.environ['SYSTEM_PROMPT']
if os.environ.get('LLM'):
query_kwargs['llm'] = os.environ['LLM']
if llm_args:
query_kwargs['llm_args'] = llm_args
if not collection_id:
query_kwargs['rag_config'] = {'rag_type': 'llm_only'}

with client.connect(chat_session_id) as session:
reply = session.query(**query_kwargs)

if not reply or not reply.content:
raise SystemExit('h2oGPTe returned an empty response')

# reply.content can be truncated for long or agentic responses;
# the stored chat message is authoritative.
stored = client.list_chat_messages(chat_session_id, 0, 1)[-1].content
answer = stored if len(stored) > len(reply.content) else reply.content

with open(os.environ['ANSWER_FILE'], 'w') as f:
f.write(answer)

print(f'--- answer ({len(answer)} chars) ---')
print(answer)

with open(os.environ['H2O_WORKFLOWS_OUTPUT'], 'a') as f:
f.write(f'chat_session_id={chat_session_id}\n')
PY
python3 -u llm_step.py

Publish the action to make it callable from workflows.

Use it from your workflow

The answer lands in answer.md on the job filesystem, so the next step reads it directly:

id: ask-llm
name: Ask LLM

jobs:
ask:
timeout: "45m"
steps:
- uses: <workspace-id>:h2ogpte-llm@latest
id: llm
with:
prompt: "Explain the difference between AutoML and manual model tuning in two paragraphs."
llm: "claude-sonnet-4-6"

- name: Show answer
run: cat answer.md

Add system_prompt, collection_id (RAG grounding), json_schema (structured output), or the use_agent inputs listed above as needed. To feed files produced by earlier steps into the prompt, list them in context_files.


Feedback