Skip to main content

Providers

Aigentic provides a consistent interface for working with different Large Language Model (LLM) providers. This allows you to easily switch between models or use multiple models in your application.

Supported Models​

Aigentic supports several LLM providers out of the box:

agent<String, Answer> {
openAIModel {
apiKey("your-api-key")
modelIdentifier(OpenAIModelIdentifier.GPT5_5)
}
// ...
}

ollamaModel connects to http://localhost:11434/v1/ unless you set apiUrl().

Add logLevel(LogLevel.DEBUG) to geminiModel or openAIModel to log every HTTP request and response, which helps when a provider returns an error you don't understand. LogLevel is in community.flock.aigentic.core.model.

Timeouts​

The Gemini provider streams every response, so data keeps arriving while the model writes a long answer. Two timeouts control how long Aigentic waits:

  • requestTimeout(ms): the maximum time for the whole request, including the stream. Raise it when a long structured output takes more than 5 minutes.
  • socketTimeout(ms): the maximum time without any data. The model sends nothing while it thinks, which can take more than a minute on a high thinking level. Unless you set it, the socket timeout is the same as the request timeout.
geminiModel {
apiKey("your-api-key")
modelIdentifier(GeminiModelIdentifier.Gemini3_8Flash)
requestTimeout(10 * 60_000)
}

Without either setting, both timeouts are 5 minutes. Gemini retries a request once after a server error and up to twice after a network error or a timeout, so a run can take longer than requestTimeout. Gemini also sends at most 50 requests per minute per model and waits when it reaches that limit.

The other providers:

  • VertexAI: requestTimeout(ms), 5 minutes by default. VertexAI doesn't stream, so there's no socketTimeout.
  • OpenAI and Ollama: a fixed socket timeout of 60 seconds.

When a request still fails, for example because of a timeout or a dropped connection, the run ends with Outcome.Fatal instead of throwing an exception.

Attachment resolution​

Providers read every image and PDF page as a limited number of tokens. For dense documents or scans with small print, the default can be too little, and the model may misread a character, for example an 8 as a 6. Each provider has its own setting to read your files at a higher resolution, with its own levels.

Aigentic sends the setting with every file the model gets: the attachments you pass to start() and the files in the agent's context. Without the setting, the provider's default stays.

import community.flock.aigentic.gemini.model.GeminiMediaResolution

agent<String, Answer> {
geminiModel {
apiKey("your-api-key")
modelIdentifier(GeminiModelIdentifier.Gemini3_8Flash)
mediaResolution(GeminiMediaResolution.HIGH)
}
// ...
}

By default, Gemini reads an image at 1120 tokens and a PDF page at 560.

GeminiMediaResolutionTokens per imageTokens per PDF page
LOW280280
MEDIUM560560
HIGH11201120
ULTRA_HIGH22401120 (sent as HIGH)

These are the counts Gemini documents for Gemini 3 models. A PDF page with a text layer also counts the tokens of that text. Gemini accepts ULTRA_HIGH for images only and rejects it on a PDF with 400 INVALID_ARGUMENT, so with ULTRA_HIGH a PDF attachment is sent at HIGH, the highest level Gemini accepts for it.

Gemini only supports a media resolution per file on Gemini 3 and newer models. Setting mediaResolution() on a Gemini 2.x model, or on a Custom identifier with major version 2 or lower, throws an AigenticException as soon as the model is built. A Custom identifier without a version match is passed through as-is.

Ollama has no resolution setting, and like OpenAI it doesn't support PDF attachments yet.

Thinking configuration​

Reasoning models let you control how much they think before answering. Aigentic exposes this through two generationConfig DSL functions: thinkingBudget(tokens) and thinkingLevel(level). They are mutually exclusive; configuring both throws an AigenticException. Leaving both unset uses the model's default behaviour.

Migrating from 0.11.0: thinkingBudget() on a Gemini or VertexAI 3.x model identifier used to be forwarded as-is; it now throws an AigenticException (use thinkingLevel() instead). thinkingBudget() on an OpenAI or Ollama model used to be silently ignored; it now throws too. Sampling defaults are no longer sent to Gemini and VertexAI 3.x models or to OpenAI reasoning models, and an explicit temperature() or topP() on an OpenAI reasoning model now throws, see Sampling parameters.

Thinking errors, like thinkingBudget() on a model that only supports thinkingLevel(), are raised as soon as the model is built, for example inside geminiModel { ... }. You get them before any request is sent, not later when the agent runs. Every ThinkingLevel works on a model that supports thinkingLevel(): a level the model doesn't have is mapped to one it does, as described below.

AigenticException is internal to Aigentic, so catch Exception if you want to handle these errors yourself.

import community.flock.aigentic.core.model.ThinkingLevel

agent<String, Answer> {
geminiModel {
apiKey("your-api-key")
modelIdentifier(GeminiModelIdentifier.Gemini3_5Flash)
generationConfig {
thinkingLevel(ThinkingLevel.LOW)
}
}
// ...
}

Gemini 2.x models only support thinkingBudget() (a token budget, -1 for dynamic, 0 to disable except on Pro). Gemini 3.x models only support thinkingLevel().

ModelDefault levelSupported levels
gemini-2.5-pro / gemini-2.5-flash / gemini-2.5-flash-liten/athinkingBudget only
gemini-3.8-flashmediumLOW, MEDIUM, HIGH (MINIMAL maps to LOW)
gemini-3.7-flashmediumLOW, MEDIUM, HIGH (MINIMAL maps to LOW)
gemini-3.6-flashmediumMINIMAL, LOW, MEDIUM, HIGH
gemini-3.5-flashmediumMINIMAL, LOW, MEDIUM, HIGH
gemini-3.5-flash-liteminimalMINIMAL, LOW, MEDIUM, HIGH
gemini-3.1-pro-previewhighLOW, MEDIUM, HIGH (MINIMAL maps to LOW)
gemini-3.1-flash-liteminimalMINIMAL, LOW, MEDIUM, HIGH
gemini-3-flash-previewhighMINIMAL, LOW, MEDIUM, HIGH

ThinkingLevel is an aigentic-level concept; each provider maps it to what the underlying model supports. MINIMAL maps to the lowest level the model supports: low on gemini-3.8-flash, gemini-3.7-flash and gemini-3.1-pro-preview, since those models don't have a MINIMAL level of their own.

For a Custom(identifier) model identifier, Aigentic infers the same rules from the identifier's major version number, parsed with (?:^|/)gemini-(\d+) (so "gemini-2.5-flash", "models/gemini-2.5-flash", and a full resource path like "publishers/google/models/gemini-2.5-flash" or "projects/.../locations/.../publishers/google/models/gemini-2.5-flash" all match): a major version of 2 or lower only supports thinkingBudget() (a thinkingLevel() throws), a major version of 3 or higher only supports thinkingLevel() with all four levels, MINIMAL forwarded as-is (a thinkingBudget() throws). An identifier with no version match (a tuned endpoint, a proxy name, or an experimental name like gemini-exp-1206) is passed through without validation — both thinkingBudget() and thinkingLevel() are accepted as-is.

Sampling parameters​

generationConfig also exposes temperature(value), topK(value) and topP(value). All three are nullable and default to unset. Reasoning models don't accept custom sampling parameters, so Aigentic only fills in a default when the underlying model actually uses one.

Model classDefault when unsetBehavior when explicitly set
Gemini 2.x, VertexAI 2.x, a Custom identifier with major version ≤ 2 or no version matchtemperature=0.0, topK=1, topP=0.1 (plus candidate_count=1)Sent as configured
Gemini 3.x, VertexAI 3.x, a Custom identifier with major version ≥ 3Omitted from the request entirelySent as configured
OpenAI/Ollama reasoning models (the o-series and the gpt-5 family only, by explicit identifier)Omitted from the requestThrows an AigenticException when the model is built
OpenAI non-reasoning models (gpt-4o, gpt-4.1, gpt-3.5-turbo, ...) and every Custom identifier (including Ollama)temperature=0.0, topP=0.1Sent as configured

On Gemini and VertexAI 3.x models, an explicitly set topK()/topP() is forwarded as-is and accepted by the API (200); Google recommends leaving them unset on 3.x for best reasoning quality.

topK is never sent to OpenAI-compatible models (OpenAI or Ollama), even when configured — the client has never supported it there, and it is not validated either.

Like the thinking-configuration errors above, an OpenAI reasoning model rejecting an explicit temperature()/topP() is raised client-side as soon as the model is built, not later when the agent runs.

Output length​

maxOutputTokens(tokens) in generationConfig caps how many tokens the model writes per response. Leave it unset to use the provider's limit. When Gemini stops at the limit, the run ends with Outcome.Fatal and a message that tells you to raise it.

Structured output (Gemini & Vertex AI)​

You don’t need structured output to use output types. With any provider you can define agent<Input, Output> and Aigentic will parse the model’s response into your Output type in the normal chat/tool‑calling flow.

Structured output is an optional mode for Gemini and Vertex AI that uses the provider’s native JSON schema support. Aigentic will use it automatically when:

  • You specify an output type on your agent (e.g., agent<Input, Output>), and
  • You don’t add any tools to the agent

Why use it

  • Handles complex, deeply nested objects more reliably

When not to use it

  • You need tools (structured output can’t run tools as a Gemini limitation)
  • Your output is simple enough for the normal flow

Troubleshooting

  • If you see Gemini errors like MALFORMED_FUNCTION_CALL, try removing tools and rely on structured output by specifying an output type.
@AigenticParameter
data class Summary(val title: String, val bullets: List<String>)

val vertexAgent = agent<String, Summary> {
vertexAIModel {
project("your-project-id")
location("your-location")
modelIdentifier(VertexAIModelIdentifier.Gemini3_5Flash)
}
task("Summarize the text as title + bullets") {
addInstruction("Return a title and bullet points")
}
// No tools added → structured output request
}

Bring your own model​

All model implementations in Aigentic implement the Model interface, which defines the contract for interacting with LLM providers:

interface Model {
val modelIdentifier: ModelIdentifier
val generationSettings: GenerationSettings
val mediaResolutionLevel: String? get() = null

suspend fun sendRequest(
messages: List<Message>,
tools: List<ToolDescription>,
structuredOutputParameter: Parameter?,
): ModelResponse
}

You can implement this interface to connect to your own model. Override mediaResolutionLevel if your model reads attachments at a configurable resolution, so it's reported to the platform with each run.

Breaking change in 0.12.0

GenerationSettings.temperature, topK and topP changed from Float/Int/Float to Float?/Int?/Float?. If your custom Model implementation reads these fields directly (e.g. generationSettings.temperature.toDouble()), update it to handle null — it now means "not configured" rather than a provider-specific default.