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.
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 ofagentscope-core into independent extension modules. Each provider module owns its chat model, credential, formatter, DTO, exception, and SDK/API client, etc.
Migration checklist
- Add the provider extension module dependency. For example, DashScope:
agentscope-extensions-model-openai, agentscope-extensions-model-gemini, agentscope-extensions-model-anthropic, and agentscope-extensions-model-ollama.
- Replace provider imports from
io.agentscope.core.model.*withio.agentscope.extensions.model.<provider>.*. - Replace provider formatter imports from
io.agentscope.core.formatter.<provider>.*withio.agentscope.extensions.model.<provider>.formatter.*. - 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 aModelRegistry 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:
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 theModel instance to the agent:
Spring Boot applications
For Spring Boot, prefer provider-specific starters such asagentscope-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 throughMETA-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 areapiKey, modelName, stream, formatter, defaultOptions. Three typical setups:
- Streaming
- Tools
- Reasoning
Calling a chat model
TheModel interface exposes a unified stream(messages, tools, options) returning Flux<ChatResponse>:
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 viaReActAgent.call(msgs, structuredOutputClass, runtimeContext):
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 supportsjson_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 theresponse_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 AgentScopeMsg 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:
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 aCredentialBase subclass and a ChatModelBase subclass.
Step 1: Define the credential
ExtendCredentialBase and implement getChatModelClass():
Step 2: Implement the chat model
ExtendChatModelBase 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
CallCredentialBase#listModels(), returning Mono<List<ModelCard>>:
getChatModelClass() returns the matching ChatModelBase subclass — useful for reflectively building a default model: