Current section
Files
Jump to
Current section
Files
src/glean/provider.gleam
/// Provider abstraction.
/// A record with function fields — Gleam's idiomatic replacement for interfaces.
/// Any library can create a Provider value without modifying this module.
import gleam/option.{type Option, None}
import glean/error.{type FinishReason, type GleanError}
import glean/message.{type Message}
import glean/schema.{type Schema}
import glean/stream.{type StreamEvent, type Usage}
/// The provider abstraction. Contains function fields for generation.
pub type Provider {
Provider(
/// Human-readable provider name (e.g., "openai", "anthropic")
name: String,
/// The model ID (e.g., "gpt-4o", "claude-sonnet-4-20250514")
model_id: String,
/// Non-streaming generation
generate: fn(GenerateRequest) -> Result(GenerateResult, GleanError),
/// Streaming generation: calls callback for each event, returns final result
stream: fn(GenerateRequest, fn(StreamEvent) -> Nil) ->
Result(GenerateResult, GleanError),
)
}
/// Everything the provider needs to make a generation call.
pub type GenerateRequest {
GenerateRequest(
messages: List(Message),
settings: ModelSettings,
tools: List(ToolSpec),
tool_choice: ToolChoice,
response_format: ResponseFormat,
)
}
/// The result of a generation call.
pub type GenerateResult {
GenerateResult(
content: List(GeneratedContent),
finish_reason: FinishReason,
usage: Usage,
model_id: String,
)
}
/// Content generated by the model.
pub type GeneratedContent {
GeneratedText(text: String)
GeneratedReasoning(text: String)
GeneratedToolCall(
tool_call_id: String,
tool_name: String,
arguments_json: String,
)
}
/// Model settings for controlling generation behavior.
pub type ModelSettings {
ModelSettings(
max_output_tokens: Option(Int),
temperature: Option(Float),
top_p: Option(Float),
top_k: Option(Int),
stop_sequences: Option(List(String)),
seed: Option(Int),
presence_penalty: Option(Float),
frequency_penalty: Option(Float),
)
}
/// A tool specification as sent to the provider API.
pub type ToolSpec {
ToolSpec(name: String, description: String, input_schema: Schema)
}
/// How the model should choose tools.
pub type ToolChoice {
/// Model decides whether to call tools
Auto
/// Model must call at least one tool
Required
/// Model must not call any tools
NoTools
/// Model must call this specific tool
SpecificTool(name: String)
}
/// The format the model should respond in.
pub type ResponseFormat {
TextFormat
JsonFormat(schema: Option(Schema), name: Option(String))
}
/// Default model settings (all None — provider uses its defaults).
pub fn default_settings() -> ModelSettings {
ModelSettings(
max_output_tokens: None,
temperature: None,
top_p: None,
top_k: None,
stop_sequences: None,
seed: None,
presence_penalty: None,
frequency_penalty: None,
)
}
/// Extract all text content from a generate result.
pub fn result_text(result: GenerateResult) -> String {
result.content
|> collect_text_content("")
}
fn collect_text_content(content: List(GeneratedContent), acc: String) -> String {
case content {
[] -> acc
[GeneratedText(text), ..rest] -> collect_text_content(rest, acc <> text)
[_, ..rest] -> collect_text_content(rest, acc)
}
}
/// Extract tool calls from generated content.
pub fn result_tool_calls(
result: GenerateResult,
) -> List(#(String, String, String)) {
result.content
|> collect_tool_call_content([])
}
fn collect_tool_call_content(
content: List(GeneratedContent),
acc: List(#(String, String, String)),
) -> List(#(String, String, String)) {
case content {
[] -> list.reverse(acc)
[GeneratedToolCall(id, name, args), ..rest] ->
collect_tool_call_content(rest, [#(id, name, args), ..acc])
[_, ..rest] -> collect_tool_call_content(rest, acc)
}
}
import gleam/list