Skip to main content

Overview

Blocks write their results to the DataBus using the output field. SemanticTest supports three different output formats to give you flexibility in how data is stored.

1. String Format (Simple)

Store the entire block output in a single named slot:
{
  "block": "HttpRequest",
  "input": { "url": "https://api.example.com/users/1" },
  "output": "response"
}
What happens:
  • Block’s entire output object is stored in the response slot
  • Access it later with ${response.status}, ${response.body}, etc.
DataBus after execution:
{
  response: {
    status: 200,
    body: '{"id": 1, "name": "John"}',
    headers: { /* ... */ }
  }
}
When to use:
  • Most common format
  • Simple and clear
  • Keep all related data together

2. Object Format (Mapping)

Map specific output fields to different slots:
{
  "block": "JsonParser",
  "input": "${response.body}",
  "output": {
    "parsed": "userData",
    "error": "parseError"
  }
}
What happens:
  • Block’s parsed field goes to userData slot
  • Block’s error field goes to parseError slot
DataBus after execution:
{
  response: { /* ... */ },
  userData: {
    id: 1,
    name: "John"
  },
  parseError: null
}
When to use:
  • Split block output into separate slots
  • Different parts need different names
  • Avoid deeply nested data access

3. Default Format (No Output Field)

If you don’t specify output, the block ID is used:
{
  "id": "fetchUser",
  "block": "HttpRequest",
  "input": { "url": "https://api.example.com/users/1" }
  // No output field
}
What happens:
  • Output is stored in a slot named after the block ID
  • Access it with ${fetchUser.status}, ${fetchUser.body}, etc.
DataBus after execution:
{
  fetchUser: {
    status: 200,
    body: '{"id": 1, "name": "John"}',
    headers: { /* ... */ }
  }
}
When to use:
  • Quick prototyping
  • Block ID is already descriptive
  • Less typing

Comparison

  • String Format
  • Object Format
  • Default Format
{
  "id": "step1",
  "block": "HttpRequest",
  "output": "userResponse"
}

// Access with:
// ${userResponse.status}
// ${userResponse.body}

Full Pipeline Example

{
  "pipeline": [
    {
      "id": "request",
      "block": "HttpRequest",
      "input": { "url": "https://api.example.com/users/1" },
      "output": "response"              // String format
    },
    {
      "id": "parse",
      "block": "JsonParser",
      "input": "${response.body}",
      "output": {                        // Object format
        "parsed": "user",
        "error": "parseError"
      }
    },
    {
      "id": "validate",
      "block": "ValidateContent",
      "input": {
        "from": "user.email",
        "as": "text"
      }
      // Default format - uses "validate" as slot name
    }
  ],
  "assertions": {
    "response.status": 200,
    "user.id": 1,
    "validate.passed": true
  }
}
DataBus state:
{
  response: {
    status: 200,
    body: '{"id": 1, "email": "user@example.com"}'
  },
  user: {
    id: 1,
    email: "user@example.com"
  },
  parseError: null,
  validate: {
    passed: true,
    checks: { /* ... */ }
  }
}

Best Practices

// Good - clear what the data is
"output": "userProfile"
"output": "authToken"
"output": "validationResult"

// Bad - vague
"output": "data"
"output": "result"
"output": "temp"
// Without object format
{
  "block": "JsonParser",
  "output": "parsed"
}
// Access: ${parsed.parsed.user.email} (awkward)

// With object format
{
  "block": "JsonParser",
  "output": {
    "parsed": "user"
  }
}
// Access: ${user.email} (clean)
// Good - ID describes what it does
{
  "id": "authenticateUser",
  "block": "HttpRequest"
  // Can use default output: ${authenticateUser.body}
}

// Bad - generic ID
{
  "id": "step1",
  "block": "HttpRequest"
  // ${step1.body} doesn't tell us what it is
}
// When a block produces multiple types of data
{
  "block": "StreamParser",
  "input": "${response.body}",
  "output": {
    "text": "aiMessage",
    "toolCalls": "aiTools",
    "chunks": "streamChunks",
    "metadata": "streamMeta"
  }
}

// Now you can use them independently:
// ${aiMessage} in one block
// ${aiTools} in another block

Common Patterns

HTTP Request and Parse

{
  "pipeline": [
    {
      "block": "HttpRequest",
      "input": { "url": "${API_URL}" },
      "output": "httpResponse"
    },
    {
      "block": "JsonParser",
      "input": "${httpResponse.body}",
      "output": {
        "parsed": "data"
      }
    }
  ]
}

Stream Parsing with Split Output

{
  "block": "StreamParser",
  "input": "${response.body}",
  "config": { "format": "sse-openai" },
  "output": {
    "text": "aiText",
    "toolCalls": "aiToolCalls",
    "metadata": "streamInfo"
  }
}

Validation Results

{
  "block": "LLMJudge",
  "input": {
    "text": "${aiResponse}",
    "expected": {
      "expectedBehavior": "Should greet the user"
    }
  },
  "output": "judgement"
}

// Access later:
// ${judgement.score}
// ${judgement.reasoning}

Output Field Reference

Different blocks produce different output fields. Check each block’s documentation:
BlockCommon Output Fields
HttpRequeststatus, body, headers
JsonParserparsed, error
StreamParsertext, toolCalls, chunks, metadata
ValidateContentpassed, failures, score, checks
ValidateToolspassed, failures, score, actualTools, expectedTools
LLMJudgescore, reasoning, shouldContinue, nextPrompt, details
Loop_loopTo, _maxLoops, _terminate
MockDataWhatever you configure

Next Steps

I