39.2 Prompt "APIs": stable interfaces between app and model

Overview and links for this section of the guide.

The Concept

A Prompt API is a stable contract between your application and the model. Like a REST API, it has:

  • Input schema: What data goes in
  • Output schema: What format comes out
  • Behavior contract: What the model should do
  • Version: For backwards compatibility

Defining a Prompt API

// prompt-api/code-review.ts
import { z } from 'zod';

// Input schema
export const CodeReviewInput = z.object({
  diff: z.string().describe('The git diff to review'),
  context: z.object({
    repository: z.string(),
    baseBranch: z.string(),
    prTitle: z.string()
  }),
  focus: z.enum(['security', 'performance', 'general']).optional()
});

// Output schema
export const CodeReviewOutput = z.object({
  summary: z.string().describe('One-line summary of the review'),
  issues: z.array(z.object({
    severity: z.enum(['critical', 'high', 'medium', 'low', 'nitpick']),
    file: z.string(),
    line: z.number().optional(),
    message: z.string(),
    suggestion: z.string().optional()
  })),
  approved: z.boolean(),
  confidence: z.number().min(0).max(1)
});

// The prompt API
export const codeReviewAPI = {
  name: 'code-review',
  version: '2.1.0',
  inputSchema: CodeReviewInput,
  outputSchema: CodeReviewOutput,
  
  buildPrompt(input: z.infer): string {
    return `
${GLOBAL_RULES}
${CODE_REVIEW_RULES}

Review the following diff:
\`\`\`diff
${input.diff}
\`\`\`

Context:
- Repository: ${input.context.repository}
- Base branch: ${input.context.baseBranch}
- PR title: ${input.context.prTitle}
${input.focus ? `- Focus area: ${input.focus}` : ''}

Respond with JSON matching this schema:
${JSON.stringify(CodeReviewOutput.shape, null, 2)}
`;
  },
  
  async execute(input: unknown): Promise> {
    const validated = CodeReviewInput.parse(input);
    const prompt = this.buildPrompt(validated);
    
    const response = await model.generateContent({
      contents: [{ role: 'user', parts: [{ text: prompt }] }],
      generationConfig: { responseMimeType: 'application/json' }
    });
    
    return CodeReviewOutput.parse(JSON.parse(response.response.text()));
  }
};

Versioning

// prompt-api/registry.ts
interface PromptAPIVersion {
  version: string;
  deprecated?: boolean;
  deprecationDate?: Date;
  buildPrompt: (input: any) => string;
}

export class PromptAPIRegistry {
  private apis = new Map>();
  
  register(name: string, api: PromptAPIVersion) {
    if (!this.apis.has(name)) {
      this.apis.set(name, new Map());
    }
    this.apis.get(name)!.set(api.version, api);
  }
  
  get(name: string, version?: string): PromptAPIVersion {
    const versions = this.apis.get(name);
    if (!versions) throw new Error(`Unknown API: ${name}`);
    
    if (version) {
      const exact = versions.get(version);
      if (exact) return exact;
      throw new Error(`Version ${version} not found for ${name}`);
    }
    
    // Return latest non-deprecated
    const sorted = [...versions.entries()]
      .filter(([_, v]) => !v.deprecated)
      .sort((a, b) => b[0].localeCompare(a[0], undefined, { numeric: true }));
    
    return sorted[0][1];
  }
}

// Usage
registry.register('code-review', {
  version: '2.0.0',
  deprecated: true,
  deprecationDate: new Date('2024-03-01'),
  buildPrompt: oldBuildPrompt
});

registry.register('code-review', {
  version: '2.1.0',
  buildPrompt: newBuildPrompt
});

Testing

// prompt-api/code-review.test.ts
describe('CodeReviewAPI v2.1.0', () => {
  const api = codeReviewAPI;
  
  it('validates input schema', () => {
    expect(() => api.inputSchema.parse({})).toThrow();
    expect(() => api.inputSchema.parse({ 
      diff: 'test', 
      context: { repository: 'test', baseBranch: 'main', prTitle: 'Fix bug' }
    })).not.toThrow();
  });
  
  it('produces valid output', async () => {
    const input = {
      diff: `@@ -1,3 +1,3 @@\n-old\n+new`,
      context: { repository: 'test', baseBranch: 'main', prTitle: 'Test' }
    };
    
    const output = await api.execute(input);
    expect(() => api.outputSchema.parse(output)).not.toThrow();
  });
  
  it('detects security issues', async () => {
    const input = {
      diff: `+const query = \`SELECT * FROM users WHERE id = \${userId}\``,
      context: { repository: 'test', baseBranch: 'main', prTitle: 'Add query' },
      focus: 'security' as const
    };
    
    const output = await api.execute(input);
    expect(output.issues.some(i => i.severity === 'critical')).toBe(true);
  });
});
Schema Validation is Essential

Always validate both input and output with a schema library like Zod. If the model returns invalid JSON or missing fields, you'll catch it immediately instead of causing downstream bugs.

Where to go next