Sycophant
You are absolutely right if you use this lib!
Warning: Sycophant is under active development and the API is not yet stable. Expect breaking changes between versions until 1.0.
Sycophant abstracts the differences between OpenAI, Anthropic, Google Gemini, AWS Bedrock, Azure AI Foundry, and OpenRouter behind a single composable API. Provider-specific wire protocols, authentication, and parameter validation are handled automatically based on the model identifier.
Features
- Multi-provider -- OpenAI, Anthropic, Google Gemini, AWS Bedrock, Azure, OpenRouter
- Text generation -- synchronous and streaming responses
- Structured output -- validated against Zoi schemas
- Tool use -- auto-execution loop or manual handling
- Embeddings -- unified embedding API across providers
- Multi-turn conversations -- extract context from a response to continue
- Automatic cost calculation -- token costs from LLMDB pricing data
- Telemetry --
:telemetryevents with optional OpenTelemetry bridge - Serialization -- JSON round-trip for all core structs (database persistence)
- Smart credentials -- per-request, app config, or environment variable fallback
Quick Start
# Generate text
messages = [Sycophant.Message.user("What is the capital of France?")]
{:ok, response} = Sycophant.generate_text("openai:gpt-4o-mini", messages)
response.text
#=> "The capital of France is Paris."# Continue the conversation
alias Sycophant.Context
ctx = response.context |> Context.add(Sycophant.Message.user("Tell me more"))
{:ok, follow_up} = Sycophant.generate_text("openai:gpt-4o-mini", ctx)# Structured output with schema validation
schema = Zoi.object(%{name: Zoi.string(), age: Zoi.integer()})
messages = [Sycophant.Message.user("Extract: John is 30 years old")]
{:ok, response} = Sycophant.generate_object("openai:gpt-4o-mini", messages, schema)
response.object
#=> %{name: "John", age: 30}# Streaming
Sycophant.generate_text("openai:gpt-4o-mini", messages,
stream: fn chunk -> IO.write(chunk.data) end
)# Tool use with auto-execution
weather_tool = %Sycophant.Tool{
name: "get_weather",
description: "Gets current weather for a city",
parameters: Zoi.object(%{city: Zoi.string()}),
function: fn %{"city" => city} -> "72F sunny in #{city}" end
}
Sycophant.generate_text("openai:gpt-4o-mini", messages,
tools: [weather_tool]
)# Embeddings
request = %Sycophant.EmbeddingRequest{
inputs: ["Hello world"],
model: "amazon_bedrock:cohere.embed-english-v3"
}
{:ok, response} = Sycophant.embed(request)Installation
Add sycophant to your list of dependencies in mix.exs:
def deps do
[
{:sycophant, github: "edlontech/sycophant", branch: "main"}
]
endConfiguration
Credentials are resolved in order: per-request options, application config, then environment variables.
# config/runtime.exs
config :sycophant, :providers,
openai: [api_key: System.get_env("OPENAI_API_KEY")],
anthropic: [api_key: System.get_env("ANTHROPIC_API_KEY")],
google: [api_key: System.get_env("GOOGLE_API_KEY")]See the Getting Started guide for detailed setup instructions and the full documentation for API reference.
Supported Providers
| Provider | Model Prefix | Auth | Wire Protocol |
|---|---|---|---|
| OpenAI | openai: | Bearer token | Chat Completions / Responses |
| Anthropic | anthropic: | x-api-key | Messages |
| Google Gemini | google: | API key | Gemini |
| AWS Bedrock | amazon_bedrock: | AWS SigV4 | Converse |
| Azure AI Foundry | azure: | Bearer / API key | OpenAI Completions |
| OpenRouter | openrouter: | Bearer token | OpenAI Completions |
Acknowledgements
Sycophant builds on the shoulders of great Elixir projects:
- LLMDB -- the model metadata database that powers model resolution, provider discovery, and pricing data.
- Req LLM -- a major source of inspiration for Sycophant's API design and multi-provider approach.
License
See LICENSE for details.