1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//! `llmprism` is a Rust library for talking to Large Language Model (LLM) providers
//! -- OpenAI, Anthropic, Gemini, and ten more -- through one consistent, fluent
//! API, instead of learning each provider's own SDK and JSON shape. It's a Rust
//! port of the PHP/Laravel library [Prism](https://prismphp.com).
//!
//! # A first example
//!
//! ```no_run
//! # #[cfg(feature = "openai")]
//! # async fn example() -> Result<(), llmprism::Error> {
//! use llmprism::Registry;
//!
//! // Reads OPENAI_API_KEY / ANTHROPIC_API_KEY from the environment and registers
//! // whichever providers have a key set (gated by the crate features you enabled).
//! let registry = Registry::from_env();
//!
//! let response = registry
//! .text("openai", "gpt-4o-mini")?
//! .with_prompt("Say hello in one word.")
//! .generate()
//! .await?;
//!
//! println!("{}", response.text.unwrap_or_default());
//! # Ok(())
//! # }
//! ```
//!
//! Swap `"openai"` for `"anthropic"` and nothing else about this code needs to
//! change -- that consistency across providers is the reason this crate exists.
//!
//! # Streaming
//!
//! Call [`stream`](text::PendingTextRequest::stream) instead of `generate` to get
//! the reply incrementally, as a stream of [`StreamEvent`]s, instead of waiting
//! for the whole thing:
//!
//! ```no_run
//! # #[cfg(feature = "openai")]
//! # async fn example() -> Result<(), llmprism::Error> {
//! use futures::StreamExt;
//! use llmprism::{Registry, StreamEvent};
//!
//! let registry = Registry::from_env();
//! let mut stream = registry
//! .text("openai", "gpt-4o-mini")?
//! .with_prompt("Count to three.")
//! .stream();
//!
//! while let Some(event) = stream.next().await {
//! if let StreamEvent::TextDelta { text } = event? {
//! print!("{text}");
//! }
//! }
//! # Ok(())
//! # }
//! ```
//!
//! Tool calling works the same way here as it does for `generate` -- if the
//! model asks to call a tool, `llmprism` runs it, reports the result as a
//! [`StreamEvent::ToolResult`], and keeps streaming the model's next round trip
//! automatically. See [`stream_loop`] for how that's implemented.
//!
//! # Structured output
//!
//! Use [`Registry::structured`] instead of [`Registry::text`] to get a reply
//! guaranteed to match a JSON shape you describe with [`schema`], rather than
//! free-form text:
//!
//! ```no_run
//! # #[cfg(feature = "openai")]
//! # async fn example() -> Result<(), llmprism::Error> {
//! use llmprism::schema::{BooleanSchema, ObjectSchema, Schema, StringSchema};
//! use llmprism::Registry;
//!
//! let schema = ObjectSchema::new("review")
//! .with_property(Schema::String(StringSchema::new("summary")), true)
//! .with_property(Schema::Boolean(BooleanSchema::new("recommended")), true);
//!
//! let registry = Registry::from_env();
//! let response = registry
//! .structured("openai", "gpt-4o-mini", schema)?
//! .with_prompt("Review this crate's ergonomics in one sentence.")
//! .generate()
//! .await?;
//!
//! println!("{}", response.data);
//! # Ok(())
//! # }
//! ```
//!
//! `response.data` is a `serde_json::Value` you can deserialize into your own
//! type with `serde_json::from_value`. See [`structured`] for how different
//! providers get a model to comply with the schema under the hood.
//!
//! # How the pieces fit together
//!
//! - [`Registry`] is where you register providers -- or let `Registry::from_env`
//! do it for you -- and it's the one place your application code asks for a
//! provider by name. In tests, you register a [`testing::FakeProvider`] under
//! that same name instead, so no other code has to know it's being tested.
//! (`from_env` isn't linked above: it only exists once any provider feature
//! is enabled, so a doc build with none enabled has nothing to link to.)
//! - Every capability -- [`text`] generation (streaming or not),
//! [`structured`] output, [`moderation`], [`embeddings`], [`rerank`]ing,
//! [`images`], and [`audio`] (text-to-speech and speech-to-text) --
//! follows the same shape:
//! ask the registry for a fluent request builder, chain `.with_*()` calls
//! to describe what you want, then call an async method like
//! [`text::PendingTextRequest::generate`] (or
//! [`text::PendingTextRequest::stream`] for incremental output) to run it.
//! Not every provider implements every capability -- Anthropic, for
//! instance, has no moderation endpoint -- calling one it doesn't returns
//! [`Error::Unsupported`] rather than failing to compile.
//! - [`Tool`] is how you give the model something it can call on your behalf --
//! a weather lookup, a database query, anything. Hand a list of tools to a text
//! request and `llmprism` handles the entire back-and-forth: if the model asks to
//! call a tool, the tool runs, its result goes back to the model, and this
//! repeats until the model gives a final answer (see [`tool_loop`]).
//! - [`schema`] describes the *shape* of data -- a tool's arguments, or a
//! [`structured`]-output request's required shape -- in a way every provider
//! understands, without you hand-writing each provider's own JSON Schema
//! dialect.
//! - [`value_objects`] holds the plain data types shared by every provider and
//! every capability: conversation messages, token usage, finish reasons, and so
//! on.
//!
//! # Feature flags
//!
//! Nothing is enabled by default. Each provider lives behind its own Cargo feature
//! flag (`openai`, `anthropic`, `groq`, `deepseek`, `mistral`, `xai`,
//! `openrouter`, `perplexity`, `zai`, `ollama`, `voyageai`, `elevenlabs`,
//! `gemini`), so a binary that only uses one provider isn't forced to compile
//! in HTTP client code for the rest. Enable `full` to turn on every provider
//! at once. `groq` through `zai` all live in `providers::openai_compatible`
//! (not linked here since that module, like `providers` itself, only exists
//! once one of its features is enabled) and cover Text generation only --
//! see that module's own docs (with the relevant feature enabled) for why.
pub use Error;
pub use Provider;
pub use Registry;
pub use StreamEvent;
pub use Tool;