Defining Clear Interfaces
Interfaces are the contracts between chain steps. Well-defined interfaces make chains reliable, testable, and maintainable.
What is a Chain Interface?
An interface specifies:
- What data comes in (input schema)
- What data goes out (output schema)
- What guarantees are made (invariants)
- What can go wrong (error conditions)
Input Schemas
Defining Required Fields
Be explicit about what each step needs:
const extractionStepInput = {
// Required fields
text: {
type: "string",
required: true,
minLength: 1,
maxLength: 50000,
description: "The raw text to extract information from"
},
extractionType: {
type: "enum",
required: true,
values: ["entities", "facts", "quotes", "all"],
description: "What type of information to extract"
},
// Optional fields
language: {
type: "string",
required: false,
default: "en",
description: "Language of the input text"
}
};
Documenting Input Expectations
Output Schemas
Structured Output Contracts
Define exactly what a step produces:
const sentimentAnalysisOutput = {
sentiment: {
type: "enum",
values: ["positive", "negative", "neutral", "mixed"],
guaranteed: true
},
confidence: {
type: "number",
range: [0, 1],
guaranteed: true
},
reasoning: {
type: "string",
guaranteed: true,
maxLength: 500
},
emotions: {
type: "array",
items: {
emotion: "string",
intensity: "number"
},
guaranteed: false, // May be empty
description: "Detected emotional signals if present"
}
};
Embedding Output Contracts in Prompts
Interface Versioning
Why Version Interfaces
As chains evolve, interfaces change. Versioning prevents breaking changes:
// Version 1.0
const outputV1 = {
sentiment: "positive",
score: 0.85
};
// Version 2.0 - Added breaking change
const outputV2 = {
analysis: {
sentiment: "positive",
confidence: 0.85,
breakdown: { /* new nested structure */ }
}
};
Migration Strategies
// Adapter pattern for backwards compatibility
function adaptV1toV2(v1Output) {
return {
analysis: {
sentiment: v1Output.sentiment,
confidence: v1Output.score,
breakdown: null // Not available in V1
}
};
}
Interface Documentation Template
Use this template for each step:
## Step: [Step Name]
### Purpose
[What this step does in one sentence]
### Input Interface
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| field1 | string | Yes | Description |
| field2 | number | No | Description (default: 0) |
### Output Interface
| Field | Type | Guaranteed | Description |
|-------|------|------------|-------------|
| result | object | Yes | The main output |
| metadata | object | Yes | Processing metadata |
### Error Conditions
- `INVALID_INPUT`: Input validation failed
- `PROCESSING_ERROR`: Step could not complete
- `TIMEOUT`: Step exceeded time limit
### Example
Input:
```json
{ "field1": "value", "field2": 42 }
Output:
{ "result": {...}, "metadata": {...} }
## Testing Interfaces
### Unit Testing a Step
```javascript
describe("SentimentAnalysisStep", () => {
it("should return valid output schema", async () => {
const input = { text: "I love this product!" };
const output = await runStep(input);
// Verify required fields
expect(output.sentiment).toBeDefined();
expect(["positive", "negative", "neutral", "mixed"]).toContain(output.sentiment);
expect(output.confidence).toBeGreaterThanOrEqual(0);
expect(output.confidence).toBeLessThanOrEqual(1);
});
it("should handle empty input gracefully", async () => {
const input = { text: "" };
const output = await runStep(input);
expect(output.error).toBe("INVALID_INPUT");
});
});
Integration Testing Between Steps
describe("Chain Integration", () => {
it("step1 output should be valid step2 input", async () => {
const step1Output = await runStep1({ text: "Sample text" });
// Verify step1 output matches step2 input requirements
expect(step1Output).toMatchSchema(step2InputSchema);
// Verify chain works end-to-end
const step2Output = await runStep2(step1Output);
expect(step2Output.success).toBe(true);
});
});
Exercise: Design an Interface
Design complete input and output interfaces for this step:
Step: CompetitorAnalyzer Purpose: Analyze a competitor's product based on provided information
Common Interface Mistakes
1. Ambiguous Types
Bad: date: "string" - What format? ISO? Unix timestamp?
Good: date: { type: "string", format: "ISO-8601", example: "2024-01-15T10:30:00Z" }
2. Missing Null Handling
Bad: score: "number" - What if we can't calculate it?
Good: score: { type: "number | null", null_meaning: "Could not be calculated" }
3. Unstated Constraints
Bad: items: "array"
Good: items: { type: "array", minItems: 1, maxItems: 100, items: { type: "object", ... } }
4. No Error Format
Bad: Return whatever error happens to occur
Good: Standard error format:
{
error: true,
error_code: "SPECIFIC_ERROR",
error_message: "Human readable explanation",
recoverable: boolean
}
Key Takeaways
- Interfaces define the contract between chain steps
- Input schemas specify required/optional fields with types and constraints
- Output schemas guarantee what the step will produce
- Version interfaces to manage changes over time
- Test interfaces at unit and integration levels
- Document interfaces clearly using a consistent template
- Handle errors with standardized formats
Next, we'll put these interfaces into practice by planning complete chain architectures.

