Thanks for your interest in contributing. This document covers everything you need to get started.
High-value contributions:
- New provider adapters (see below for how)
- Bugs with a reproduction case
- Documentation improvements and typo fixes
- New examples for real-world use cases
- Performance improvements with benchmarks
Out of scope (for now):
- Breaking changes to the public API without a discussion issue first
- Large new features without prior discussion — open an issue first
git clone https://github.com/piyushgupta344/structured-llm
cd structured-llm
pnpm installMake sure tests pass before making any changes:
pnpm testThe test suite runs entirely offline (no API keys needed) — all provider calls are mocked.
src/
generate.ts # core orchestrator — start here to understand the flow
generate-array.ts # array extraction wrapper
generate-stream.ts # streaming implementation
client.ts # createClient() factory
retry.ts # retry loop, JSON extraction, error messages
hooks.ts # hook runner
models.ts # model capability registry + cost table
usage.ts # token counting + cost calculation
errors.ts # error classes
types.ts # all public types
providers/
types.ts # ProviderAdapter interface
registry.ts # auto-detect client → adapter
openai.ts # OpenAI (+ base for compat providers)
anthropic.ts
gemini.ts
mistral.ts
cohere.ts
compat/base.ts # base for OpenAI-compatible providers
schema/
detect.ts # resolve schema → SchemaAdapter
adapters/zod.ts # Zod v3/v4 → SchemaAdapter
integrations/
next/index.ts
hono/index.ts
express/index.ts
tests/
fixtures/mock-clients.ts # typed mock clients for all providers
unit/ # unit tests (no API keys needed)
examples/ # runnable examples (need API keys)
This is the most common contribution. Here's exactly what to do.
Create src/providers/yourprovider.ts:
import type { AdapterRequest, AdapterResponse, ProviderAdapter } from "./types.js";
import { ProviderError } from "../errors.js";
export class YourProviderAdapter implements ProviderAdapter {
readonly name = "yourprovider" as const;
private client: any;
constructor(client: any) {
this.client = client;
}
async complete(req: AdapterRequest): Promise<AdapterResponse> {
const { model, messages, schema, schemaName, mode, temperature, maxTokens } = req;
try {
if (mode === "tool-calling") {
// implement tool calling
}
if (mode === "json-mode") {
// implement json mode if supported
}
// prompt-inject fallback
// ...
} catch (err) {
const e = err as any;
throw new ProviderError("yourprovider", e?.message ?? String(err), e?.status, err);
}
}
// optional — only if provider supports streaming
async *stream(req: AdapterRequest): AsyncIterable<string> {
// yield string chunks
}
}
export function isYourProviderClient(client: any): boolean {
return client?.constructor?.name === "YourProviderSDKClass";
}Look at src/providers/anthropic.ts or src/providers/mistral.ts for complete examples.
If the provider is OpenAI-compatible (uses the same API format), you don't need a new adapter — just add it to registry.ts with the right baseURL pattern. Look at how Groq and xAI are handled there.
In src/providers/registry.ts:
// Add to the imports
import { YourProviderAdapter, isYourProviderClient } from "./yourprovider.js";
// Add to adapterFromClient()
if (isYourProviderClient(client)) return new YourProviderAdapter(client);
// Add to adapterFromProvider()
case "yourprovider": {
const { YourProviderSDK } = await import("yourprovider-sdk");
return new YourProviderAdapter(new YourProviderSDK({ apiKey: key }));
}In src/types.ts, add to the ProviderName union:
export type ProviderName =
| "openai"
| "anthropic"
| "yourprovider" // add here
| ...In registry.ts, add to getEnvKey():
const envMap: Record<ProviderName, string> = {
yourprovider: "YOUR_PROVIDER_API_KEY",
...
}In src/models.ts, add the provider's models to the registry with their capabilities and pricing.
In tests/fixtures/mock-clients.ts, add a mock for your provider (see the existing ones for reference).
Add tests/unit/providers/yourprovider.test.ts and test detection + extraction in both tool-calling and json-mode.
Add the provider to the providers table and show an example of how to create the client.
pnpm test # run all tests
pnpm test:watch # watch mode
pnpm test:coverage # with coverage report
pnpm typecheck # TypeScript without buildingAll tests use mock clients — no real API calls, no API keys needed.
If you want to test against a real API (optional), create a .env file:
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
Then run examples directly:
npx tsx examples/01-sentiment-analysis.tsWe use Conventional Commits:
feat: add Cohere provider adapter
fix: handle empty tool call arguments in OpenAI adapter
docs: add Ollama example to README
test: add coverage for retry with exponential backoff
chore: update model pricing table
Before opening a PR, make sure:
-
pnpm testpasses with no failures -
pnpm typecheckhas no errors - New code has corresponding tests
- If adding a provider: mock client + tests + README entry
- If adding a feature: at least one example or test demonstrating it
- Commit messages follow conventional commits
This project uses semantic versioning. Version bumps are handled by the maintainers. You do not need to bump the version in your PR — just describe what changed in your PR description and the maintainers will assign the appropriate version when releasing.
Open a GitHub Discussion for anything that isn't a bug or feature request.