Skip to main content

Overview

The model layer separates shared contracts from provider implementations. agentscope-core keeps the common APIs (Model, ChatModelBase, Formatter, ModelRegistry, and the ModelProvider SPI). OpenAI, DashScope, Gemini, Anthropic, and Ollama implementations live in their own model extension modules. At runtime, the model layer is two-tiered: at the top sit Credentials (based on io.agentscope.core.credential), which carry a provider’s API auth fields; below them sit Chat Models, the concrete inference implementations attached to a credential.
A Credential carries a provider’s API auth fields (apiKey, baseUrl, …). Starting from a credential, you can call listModels() to enumerate the models available under that provider (returns Mono<List<ModelCard>>). This layering matches the natural UX in a frontend — register the credential first, then pick a model under it — so the UI authenticates once and shows everything that provider supports.

Model extension modules

Provider-specific model implementations have been moved out of agentscope-core into independent extension modules. Each provider module owns its chat model, credential, formatter, DTO, exception, and SDK/API client, etc.

Migration checklist

  1. Add the provider extension module dependency. For example, DashScope:
Other provider artifacts follow the same pattern: agentscope-extensions-model-openai, agentscope-extensions-model-gemini, agentscope-extensions-model-anthropic, and agentscope-extensions-model-ollama.
  1. Replace provider imports from io.agentscope.core.model.* with io.agentscope.extensions.model.<provider>.*.
  2. Replace provider formatter imports from io.agentscope.core.formatter.<provider>.* with io.agentscope.extensions.model.<provider>.formatter.*.
  3. For Spring Boot applications, replace the generic model creation path with the matching provider-specific starter and its agentscope.<provider>.* properties.

Choose a creation path

String model id

For simple non-Spring applications, use a ModelRegistry string id such as dashscope:qwen-plus, openai:gpt-4.1-mini, or deepseek:deepseek-v4-flash. Add the matching model extension module, set the provider’s standard environment variable such as DASHSCOPE_API_KEY, OPENAI_API_KEY, or DEEPSEEK_API_KEY, and pass the id directly to the agent:
The extension module is discovered through Java SPI. The model provider reads its standard environment variables such as DASHSCOPE_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, GLM_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY. Ollama reads OLLAMA_BASE_URL when present and otherwise defaults to the local Ollama endpoint.

Explicit model builder

When you need a custom API key, base URL, formatter, transport, timeout, generation options, or other provider-specific configuration, build the model explicitly and pass the Model instance to the agent:

Spring Boot applications

For Spring Boot, prefer provider-specific starters such as agentscope-openai-spring-boot-starter, agentscope-dashscope-spring-boot-starter, agentscope-gemini-spring-boot-starter, agentscope-anthropic-spring-boot-starter, and agentscope-ollama-spring-boot-starter. These starters directly depend on the matching model extension, create Spring-managed Model beans, and leave the generic starter focused on common AgentScope infrastructure. They do not create models through the static ModelRegistry; advanced users can always provide their own Model bean. OpenAI example:

Builder customizers

Provider-specific starters also expose ordered Spring bean customizers for the auto-configured chat model builders. Use them when property binding covers the common settings but you still need to tune builder-only options such as custom formatters, default generation options, proxy/client settings, or provider-specific flags. Customizer beans are applied after starter properties are bound and before builder.build() is called. Multiple customizers are supported and follow Spring’s @Order / Ordered ordering.

ModelRegistry and ModelCreationContext

ModelRegistry is a global registry for model instance creation and lookup, supporting multiple resolution strategies. During resolution, it tries in priority order: named model instances directly registered via ModelRegistry.register(name, model), custom factories registered via registerFactory(regex, factory), and ModelProvider implementations automatically discovered from extension modules through the Java SPI mechanism. For simple scenarios, prefer a string id in the provider:model format together with the provider’s standard environment variable; for fine-grained control, use explicit model builders. ModelCreationContext is mainly for integration-layer code that must resolve models dynamically.

Advanced integration context

ModelCreationContext is for integration layers that must create models dynamically without importing a concrete provider builder, such as multi-tenant gateways, plugin systems, or framework adapters. It can pass common values such as API key, base URL, endpoint path, stream mode, and extension-defined options/components to the SPI provider:

Cache policy

ModelRegistry caches models resolved from simple provider:model strings. Context-aware creation is not cached by default to avoid reusing a model instance with a different tenant’s API key, base URL, or stream setting. If CachePolicy.ENABLED is used with option(...) or component(...), the user must provide a cacheId.

ModelProvider SPI

Provider extension modules are discovered with Java SPI through META-INF/services/io.agentscope.core.model.spi.ModelProvider. A provider can implement supports(String, ModelCreationContext) and create(String, ModelCreationContext) to consume context values. Simple providers can keep implementing the original supports(String) and create(String) methods because the context-aware methods have compatible defaults.

Chat model

A Chat Model is the LLM driving conversation and tool calling, with input and output potentially spanning multiple modalities. AgentScope Java currently ships: Provider credential classes live with their model extension modules, for example OpenAICredential, AnthropicCredential, DashScopeCredential, GeminiCredential, and OllamaCredential. OpenAI-compatible credentials such as DeepSeekCredential, KimiCredential, and XAICredential remain available from core.

Creating a chat model

Each chat model is built with a builder. The most common fields are apiKey, modelName, stream, formatter, defaultOptions. Three typical setups:
Common builder fields:

Calling a chat model

The Model interface exposes a unified stream(messages, tools, options) returning Flux<ChatResponse>:
A ChatResponse carries a list of content blocks (TextBlock, ThinkingBlock, ToolUseBlock, DataBlock) and a ChatUsage recording token counts and timing. In practice you usually call models indirectly via ReActAgent. For lightweight direct invocation, see agentscope-examples/documentation/.../model/ModelRegistryExample.java.

Generating structured output

The agent layer offers a convenience overload for binding the model output to a Java POJO via ReActAgent.call(msgs, structuredOutputClass, runtimeContext):
How it works: the framework synthesizes a forced structured tool call from the target class, validates and repairs the model output, and writes the result into Msg.metadata under the structured_output key, so getStructuredData(Class) can deserialize it directly. Complete example: agentscope-examples/documentation/.../structuredoutput/StructuredOutputExample.java.

Structured output path selection

The framework provides two structured output paths: If the native path fails (e.g. model returns HTTP 400), the framework automatically falls back to the synthetic tool path — no user intervention needed.

Default behavior per provider

DashScope users: Thinking mode (enableThinking(true)) does not support structured output at all — the framework forces the fallback path.

Explicit configuration

If you confirm your model/endpoint supports json_schema, enable the native path via builder:

Structured output with tool calling

When an agent has both tools and structured output, some OpenAI-compatible providers (e.g. Kimi, Deepseek) prioritise the response_format constraint and skip tool calling entirely. Set nativeStructuredOutputWithTools(false) to resolve this:
DashScopeChatModel supports this option as well. For native OpenAI models (GPT-4o, etc.) the default behavior handles both correctly — no configuration needed.

Formatter

A Formatter converts AgentScope Msg objects into the request payload each provider’s API expects. It is configured via the chat model builder’s formatter(...). Each provider ships two formatters: To switch to multi-agent mode, just pass the MultiAgent variant — no agent code changes:
Per-provider formatters now live with their provider extension modules: If your provider’s payload doesn’t fit any of these, implement the Formatter<TReq, TResp, TParams> interface (io.agentscope.core.formatter) and pass it through the same formatter(...) builder.

Custom provider

The minimal path to a new provider: implement a CredentialBase subclass and a ChatModelBase subclass.

Step 1: Define the credential

Extend CredentialBase and implement getChatModelClass():

Step 2: Implement the chat model

Extend ChatModelBase and implement doStream:

Step 3: Register with the ModelRegistry (optional)

ModelRegistry lets ReActAgent.builder().model("provider:model-name") resolve models from a string:

Frontend integration

What is ModelCard

ModelCard (credential/ModelCard.java) is a declarative description of a model’s capabilities and constraints. It powers frontends — the model picker, parameter form, and capability toggles can render dynamically against it without hard-coding any provider-specific logic. Today, ModelCard is a minimal record:
The ModelCard schema is intentionally minimal at this stage; capability flags (input/output MIME types) and parameter schemas will be added as model-discovery infrastructure matures.

Fetching ModelCards

Call CredentialBase#listModels(), returning Mono<List<ModelCard>>:
getChatModelClass() returns the matching ChatModelBase subclass — useful for reflectively building a default model:
This design lets frontends discover every model available under a provider with just one credential — no hard-coded provider logic.