Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/components/Claude.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ The Text Prompt component uses Anthropic's Claude models to generate text respon
- **System Message**: (Optional) Context to define the assistant's behavior or persona.
- **Max Tokens**: (Optional) Limit the length of the generated response.
- **Temperature**: (Optional) Control randomness (0.0 to 1.0).
- **Structured Output**: (Optional) Define the output fields (name, type, description, required) and Claude returns JSON matching them, available on the parsed output. Supports nested objects and lists; the JSON Schema is built for you.

### Output

Expand All @@ -88,6 +89,7 @@ Returns a payload containing:
- **usage**: Input and output token counts.
- **stopReason**: Why the generation ended (e.g., "end_turn", "max_tokens").
- **model**: The specific model version used.
- **parsed**: When Structured Output is configured, the response parsed into an object (only on a normal end_turn completion).

### Notes

Expand All @@ -102,6 +104,10 @@ Returns a payload containing:
"data": {
"id": "msg_01X9JGt5...123456",
"model": "claude-3-5-sonnet-latest",
"parsed": {
"severity": "low",
"summary": "Deployment completed successfully"
},
"response": {
"content": [
{
Expand Down
8 changes: 7 additions & 1 deletion docs/components/OpenAI.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The Text Prompt component generates text responses using OpenAI's language model

- **Model**: Select the OpenAI model to use (e.g., gpt-4, gpt-3.5-turbo)
- **Prompt**: The text prompt to send to the model (supports expressions)
- **Structured Output**: (Optional) Define the output fields (name, type, description, required) and the model returns JSON matching them, available on the parsed output. Supports nested objects and lists; the JSON Schema (OpenAI strict mode, including nullable handling for optional fields) is built for you.

### Output

Expand All @@ -39,13 +40,14 @@ Returns the generated response including:
- **model**: The model used for generation
- **usage**: Token usage information (prompt tokens, completion tokens, total tokens)
- **id**: Response ID for tracking
- **parsed**: When Structured Output is configured, the response parsed into an object.

### Notes

- Requires a valid OpenAI API key configured in the application settings
- Response quality and speed depend on the selected model
- Token usage is tracked and may incur costs based on your OpenAI plan
- Supports OpenAI-compatible providers by setting a custom Base URL in the integration settings (e.g., Azure OpenAI, Ollama, vLLM)
- Supports OpenAI-compatible providers by setting a custom Base URL in the integration settings (e.g., Azure OpenAI, Ollama, vLLM). Note: structured output uses the OpenAI Responses API text.format parameter and may not be supported by all compatible providers.

### Example Output

Expand All @@ -54,6 +56,10 @@ Returns the generated response including:
"data": {
"id": "cmpl-1234567890",
"model": "gpt-5.2",
"parsed": {
"language": "en",
"summary": "Hello, world!"
},
"text": "Hello, world!",
"usage": {
"input_tokens": 10,
Expand Down
23 changes: 18 additions & 5 deletions pkg/integrations/claude/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,24 @@ type ContentBlockSource struct {
}

type CreateMessageRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
System string `json:"system,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
Model string `json:"model"`
Messages []Message `json:"messages"`
System string `json:"system,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
OutputConfig *OutputConfig `json:"output_config,omitempty"`
}

// OutputConfig configures Claude's response format (structured outputs).
// See https://platform.claude.com/docs/en/build-with-claude/structured-outputs
type OutputConfig struct {
Format *OutputFormat `json:"format,omitempty"`
}

// OutputFormat constrains the final text response to a JSON schema.
type OutputFormat struct {
Type string `json:"type"` // always "json_schema"
Schema any `json:"schema"` // the JSON Schema object
}

type MessageContent struct {
Expand Down
4 changes: 4 additions & 0 deletions pkg/integrations/claude/example_output_text_prompt.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
"data": {
"id": "msg_01X9JGt5...123456",
"model": "claude-3-5-sonnet-latest",
"parsed": {
"summary": "Deployment completed successfully",
"severity": "low"
},
"response": {
"content": [
{
Expand Down
60 changes: 60 additions & 0 deletions pkg/integrations/claude/text_prompt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package claude

import (
"encoding/json"
"fmt"
"io"
"net/http"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/superplanehq/superplane/pkg/configuration"
"github.com/superplanehq/superplane/pkg/core"
gitprovider "github.com/superplanehq/superplane/pkg/git/provider"
"github.com/superplanehq/superplane/pkg/integrations/structuredoutput"
)

const MessagePayloadType = "claude.message"
Expand All @@ -24,17 +26,27 @@ type TextPromptSpec struct {
MaxTokens int `json:"maxTokens"`
Temperature *float64 `json:"temperature"`
Files []string `json:"files"`
OutputFields any `json:"outputFields"`
}

type MessagePayload struct {
ID string `json:"id"`
Model string `json:"model"`
Text string `json:"text"`
Parsed any `json:"parsed,omitempty"`
Usage *MessageUsage `json:"usage,omitempty"`
StopReason string `json:"stopReason,omitempty"`
Response *CreateMessageResponse `json:"response"`
}

// TextPromptNodeMetadata is node-level metadata surfaced in the UI so the
// configured model and options are visible on the node without opening it.
type TextPromptNodeMetadata struct {
Model string `json:"model" mapstructure:"model"`
MaxTokens int `json:"maxTokens" mapstructure:"maxTokens"`
StructuredOutput bool `json:"structuredOutput" mapstructure:"structuredOutput"`
}

func (c *TextPrompt) Name() string {
return "claude.textPrompt"
}
Expand Down Expand Up @@ -63,6 +75,7 @@ func (c *TextPrompt) Documentation() string {
- **System Message**: (Optional) Context to define the assistant's behavior or persona.
- **Max Tokens**: (Optional) Limit the length of the generated response.
- **Temperature**: (Optional) Control randomness (0.0 to 1.0).
- **Structured Output**: (Optional) Define the output fields (name, type, description, required) and Claude returns JSON matching them, available on the parsed output. Supports nested objects and lists; the JSON Schema is built for you.

## Output

Expand All @@ -71,6 +84,7 @@ Returns a payload containing:
- **usage**: Input and output token counts.
- **stopReason**: Why the generation ended (e.g., "end_turn", "max_tokens").
- **model**: The specific model version used.
- **parsed**: When Structured Output is configured, the response parsed into an object (only on a normal end_turn completion).

## Notes

Expand Down Expand Up @@ -154,6 +168,11 @@ func (c *TextPrompt) Configuration() []configuration.Field {
},
},
},
structuredoutput.ConfigField(
"outputFields",
"Structured Output",
"Define the fields Claude should return. The response is constrained to a JSON object with these fields (available on the `parsed` output). Supports nested objects and lists.",
),
}
}

Expand Down Expand Up @@ -196,6 +215,26 @@ func (c *TextPrompt) Setup(ctx core.SetupContext) error {
}
}

fields, err := structuredoutput.Decode(spec.OutputFields)
if err != nil {
return err
}
if err := structuredoutput.Validate(fields); err != nil {
return err
}
Comment thread
cursor[bot] marked this conversation as resolved.

if ctx.Metadata != nil {
maxTokens := spec.MaxTokens
if maxTokens == 0 {
maxTokens = 4096
}
_ = ctx.Metadata.Set(TextPromptNodeMetadata{
Model: spec.Model,
MaxTokens: maxTokens,
StructuredOutput: len(fields) > 0,
})
}

return nil
}

Expand All @@ -219,6 +258,11 @@ func (c *TextPrompt) Execute(ctx core.ExecutionContext) error {
return fmt.Errorf("maxTokens must be at least 1")
}

fields, err := structuredoutput.Decode(spec.OutputFields)
if err != nil {
return err
}

client, err := NewClient(ctx.HTTP, ctx.Integration)
if err != nil {
return err
Expand Down Expand Up @@ -246,6 +290,12 @@ func (c *TextPrompt) Execute(ctx core.ExecutionContext) error {
req.System = spec.SystemMessage
}

if len(fields) > 0 {
req.OutputConfig = &OutputConfig{
Format: &OutputFormat{Type: "json_schema", Schema: structuredoutput.BuildSchema(fields, false)},
}
}

response, err := client.CreateMessage(req)
if err != nil {
return err
Expand All @@ -262,6 +312,16 @@ func (c *TextPrompt) Execute(ctx core.ExecutionContext) error {
Response: response,
}

// When a schema is configured, parse the model's JSON text into a structured
// object. Only trust the output on a normal completion (end_turn); a refusal
// or truncation (max_tokens) may not conform to the schema.
if len(fields) > 0 && response.StopReason == "end_turn" && text != "" {
var parsed any
if err := json.Unmarshal([]byte(text), &parsed); err == nil {
payload.Parsed = parsed
}
}

return ctx.ExecutionState.Emit(
core.DefaultOutputChannel.Name,
MessagePayloadType,
Expand Down
Loading