feat: Add support for OpenAI-compatible LLM endpoints (Groq, Ollama, Azure OpenAI, etc.) #1198
Description
🔴 Required Information
Is your feature request related to a specific problem?
Yes. Currently, ADK-Java only supports Google's Gemini and Claude models out of the box. Developers cannot easily use other LLM
providers that implement the OpenAI Chat Completions API format, such as:
- Groq (fast inference with open models)
- Ollama (local models for offline development)
- Azure OpenAI (enterprise deployments)
- Perplexity (search-augmented responses)
- Any custom OpenAI-compatible endpoint
Python ADK supports this via LiteLLM integration, but Java ADK lacks a native solution. This creates friction for developers who
want to:
- Experiment with different model providers
- Reduce inference costs by using alternative providers
- Develop/test locally without external API calls
- Support enterprise requirements (Azure OpenAI)
Describe the Solution You'd Like
Add a new OpenAiCompatibleLlm class that wraps the existing ChatCompletionsHttpClient to support any OpenAI-compatible endpoint
with a simple builder pattern.
Key capabilities:
- Builder pattern for easy configuration (baseUrl, headers, timeout)
- Pattern-based registry integration (e.g.,
groq-.*,ollama-.*) - Reuses existing
ChatCompletionsHttpClientinfrastructure - Non-streaming requests (matches current
ChatCompletionsHttpClientcapabilities) - Comprehensive tests (unit + integration + manual verification)
Impact on your work
Impact Level: High
This feature is critical for:
- My current project: Building a multi-model agent system that needs to switch between Gemini (complex reasoning) and Groq
(fast responses) based on task type - Cost optimization: Groq is 10x cheaper than Gemini for simple tasks
- Local development: Need Ollama support for offline testing without API costs
Timeline: Would like this in the next release if possible. Currently using a workaround with direct HTTP calls, but it's not
maintainable.
Willingness to contribute
Yes - I have already implemented this feature with:
- ✅ Core
OpenAiCompatibleLlmclass with builder pattern - ✅ 13 unit tests (builder validation, registry integration, error handling)
- ✅ Integration tests with Ollama
- ✅ Manual verification with Groq API (tested successfully)
- ✅ README documentation with examples
- ✅ Follows Google Java Style Guide (google-java-format applied)
- ✅ Aligned with CONTRIBUTING.md requirements
Can submit PR immediately if maintainers approve this approach.
🟡 Recommended Information
Describe Alternatives You've Considered
-
Direct HTTP calls: Manually building requests to OpenAI-compatible endpoints
- ❌ Doesn't integrate with
LlmRegistry - ❌ No pattern matching for model resolution
- ❌ Duplicates HTTP client logic
- ❌ Not maintainable
- ❌ Doesn't integrate with
-
Separate library wrapper: Create external library for each provider
- ❌ Fragments ecosystem
- ❌ Each provider needs separate maintenance
- ❌ Doesn't leverage existing ADK infrastructure
-
Use Python ADK instead: Switch to Python for LiteLLM support
- ❌ Not viable for Java-based projects
- ❌ Team expertise is in Java
-
Wait for official provider support: Request Google add each provider individually
- ❌ Slow (requires coordination with each provider)
- ❌ Doesn't scale to custom/internal endpoints
Why the proposed solution is better: Single implementation supports all OpenAI-compatible providers, reuses existing
infrastructure, and allows custom endpoints.
Proposed API / Implementation
// Example 1: Groq (fast inference)
OpenAiCompatibleLlm groq = OpenAiCompatibleLlm.builder()
.baseUrl("https://api.groq.com/openai/v1/")
.headers(ImmutableMap.of("Authorization", "Bearer " + apiKey))
.modelName("llama-3.3-70b-versatile")
.timeoutMillis(30_000)
.build();
// Register pattern for model resolution
groq.registerWithPattern("groq-.*");
// Use with LlmAgent
LlmAgent agent = LlmAgent.builder()
.model("groq-llama-3.3-70b-versatile")
.instruction("You are a helpful assistant.")
.build();
String response = agent.runAsync(invocationContext).blockingFirst();
// Example 2: Ollama (local models)
OpenAiCompatibleLlm ollama = OpenAiCompatibleLlm.builder()
.baseUrl("http://localhost:11434/v1/")
.headers(ImmutableMap.of()) // No auth for local
.modelName("ollama-llama2")
.build();
ollama.registerWithPattern("ollama-.*");
// Example 3: Azure OpenAI (enterprise)
OpenAiCompatibleLlm azure = OpenAiCompatibleLlm.builder()
.baseUrl("https://<resource>.openai.azure.com/openai/deployments/<deployment>/")
.headers(ImmutableMap.of("api-key", azureApiKey))
.modelName("azure-gpt-4")
.build();
azure.registerWithPattern("azure-.*");Implementation Architecture:
- OpenAiCompatibleLlm extends BaseLlm
- Wraps existing ChatCompletionsHttpClient (no code duplication)
- Uses LlmRegistry.registerLlm(pattern, factory) for pattern matching
- Throws UnsupportedOperationException for live connections (OpenAI API limitation)
Files to be added/modified:
M README.md
A core/src/main/java/com/google/adk/models/OpenAiCompatibleLlm.java
A core/src/test/java/com/google/adk/models/OpenAiCompatibleLlmTest.java
A core/src/test/java/com/google/adk/models/OpenAiCompatibleLlmIntegrationTest.java
A core/src/test/java/com/google/adk/models/ManualGroqTest.java
Additional Context
Testing completed:
- ✅ Unit tests: 13 tests, all passing
- ✅ Integration tests: Ollama-based tests for real endpoints
- ✅ Manual verification: Successfully tested with Groq API (llama-3.3-70b-versatile)
- Simple completion test
- Registry integration test
- Multi-turn conversation with context retention
Design decisions:
- Non-streaming only: Matches ChatCompletionsHttpClient capabilities (streaming can be added in future PR)
- Pattern-based registration: Allows multiple providers with different prefixes (e.g., groq-, ollama-, azure-*)
- No live connections: OpenAI Chat Completions API doesn't support bidirectional live connections
Questions for maintainers:
- Should we add streaming support in a follow-up PR, or include it now?
- Any concerns with the pattern-based registration approach?
- Should integration tests be tagged/skipped in CI (currently requires manual Ollama setup)?
Related: Addresses similar functionality to Python ADK's LiteLLM integration, but using native Java implementation that reuses
existing ADK infrastructure.
Implementation ready: I have a fully tested, working implementation and can submit a PR immediately upon approval of this approach.
Activity
hemasekhar-p commentedon May 18, 2026
Hi @Shaamam, Thank you for submitting this feature request. It is currently under review by our team, we will keep you updated if any additional information is required. We appreciate your input.
hemasekhar-p commentedon May 18, 2026
@anFatum, Please take a look into it.
Shaamam commentedon May 18, 2026
Hi @hemasekhar-p and @anFatum,
Thank you for reviewing this! I've submitted PR #1202 with a complete implementation to facilitate the code review.
The PR includes:
OpenAiCompatibleLlmimplementationAll tests pass locally and the implementation has been verified with real Groq API. Happy to make any adjustments based on your feedback!
svetanis commentedon May 19, 2026
Hello Team! I saw this issue and wanted to flag that I've been working on a related solution.
I submitted a PR (feat:contrib/model-prism: pluggable LLM backend SPI for ADK Java via
ServiceLoader#1199) recently that covers similar ground — OpenAiCompatibleLlm as a base class, plus provider discovery via ServiceLoader SPI as a contrib module. It also includes streaming support and 11 demo classes.Happy to collaborate, align approaches, or break the PR into smaller pieces if that's easier to review. Just wanted to make sure the efforts are visible to each other.
FuturMix commentedon May 19, 2026
Great feature request and solid PR work by @Shaamam and @svetanis. The multi-provider pattern is essential for production agent systems — here are a few architectural considerations from running similar setups:
Model routing by task type is the biggest cost win
The issue author mentioned switching between Gemini (complex reasoning) and Groq (fast responses) based on task type — this is exactly the right pattern. In practice, a well-configured routing layer can reduce API costs by 40-70% without quality loss by sending:
Consider the provider abstraction carefully
Both PRs take slightly different approaches. A few things worth considering for the final design:
Credential isolation: Each provider may need different auth headers (
Authorization: Bearervsx-api-keyvs custom). The builder should support a generic header map rather than assumingAuthorizationonly.Model name normalization: Some providers accept
gpt-5.5while others needopenai/gpt-5.5. Having an optional model alias map in the config avoids hardcoding provider-specific naming.Streaming support: The initial non-streaming implementation is fine for an MVP, but agent workflows with long outputs really benefit from streaming. The SPI approach in PR feat:contrib/model-prism: pluggable LLM backend SPI for ADK Java via
ServiceLoader#1199 seems better positioned for this.Fallback chain: For production reliability, being able to define
primary → fallbackprovider chains (e.g., Groq → Ollama if Groq is rate-limited) is very valuable.The Python ADK's LiteLLM integration handles some of this by delegating to LiteLLM's provider registry — the Java equivalent could follow a similar pattern where an OpenAI-compatible gateway handles provider abstraction externally, and ADK-Java only needs to talk to one endpoint.
svetanis commentedon May 20, 2026
@FuturMix , Thanks for the thoughtful feedback! Just a couple of points regarding your feedback:
Model name normalization. Fair point in a general framework, but the
provider/modelprefix convention sidesteps it here. The developer writes"groq/llama3"in the agent config, andGroqModelProviderjust stripsgroq/before forwarding"llama3"to Groq's endpoint — so the string the developer writes is exactly what the target API expects, no alias map needed. The prefix is purely a routing namespace:"groq/llama3"and"ollama/llama3"are distinct keys inLlmRegistry, so two providers can share the same bare model name without conflict. If we want to go further, prefix stripping could move intoModelProviderRegistryitself to remove even that boilerplate from individual providers.On the LiteLLM suggestion. LiteLLM's zero-code switching works because Python's dynamic type system can resolve a string like
groq/llama3to a live module at runtime —ServiceLoaderis the Java-idiomatic way to achieve the same effect explicitly. With theModelProviderSPI, a provider JAR drops its registration intoMETA-INF/services,ModelProviderRegistry.registerAll()picks it up at startup, and switching providers becomes one property change plus one dependency swap with no app code touched - the same zero-code outcome LiteLLM gives Python developers, through Java's explicit infrastructure rather than dynamic imports.Shaamam commentedon May 20, 2026
@FuturMix Thank you for the detailed feedback!
Regarding streaming support - my implementation already supports it! The
generateContentmethod passes thestreamparameter directly toChatCompletionsHttpClient.complete(), which handles Streaming per the OpenAI specification.From my implementation (line 96-97):
The documentation mentions "non-streaming only" because I focused on testing non-streaming cases, but the underlying infrastructure supports both modes.
Design philosophy: My approach prioritizes simplicity - pattern-based registration is straightforward and reuses existing ADK
infrastructure (ChatCompletionsHttpClient, LlmRegistry). Advanced features like fallback chains and cost-based routing could be added incrementally in follow-up PRs.
@svetanis I see you also submitted PR #1199 with a ServiceLoader approach. Both have merit - would be happy to discuss with maintainers which direction fits ADK's architecture better!
Looking forward to feedback from @hemasekhar-p and @anFatum on preferred approach.
kingpro1985 commentedon May 20, 2026
I saw this while looking at custom model/provider setup issues: "feat: Add support for OpenAI-compatible LLM endpoints (Groq, Ollama, Azure OpenAI, etc.)".
If it helps your testing, CloudGPU exposes a hosted OpenAI-compatible endpoint that can route to GPT, Claude, Gemini, DeepSeek, Kimi and GLM behind one API key.
Base URL: https://gpuapis.com/api/v1
Setup guide: https://gpuapis.com/use-cases/deepseek-gemini-claude-api/
I can share a small test key/credit and a cURL request if you want to verify it in your current client before doing any integration work.
svetanis commentedon May 21, 2026
@Shaamam Thanks for the clarification on streaming — good to know the underlying plumbing supports it.
Looking more carefully at your
OpenAiCompatibleLlm, I think both PRs actually agree on more than it might appear at first:Where we agree:
Both proposals put an
OpenAiCompatibleLlm-style class into ADK core to encapsulate the OpenAI Chat Completions protocol. That's a solid foundation.Where they diverge — the key difference:
Your approach is a utility class: every ADK application that wants to use Groq, Ollama, or any other provider must write wiring code in their own codebase:
This is clean and simple, but it means every application that uses Groq carries this wiring. If the Groq endpoint changes, every app must be updated independently.
The
model-prismSPI approach (a working prototype) takes this one step further. Three small additions go intogoogle-adk.jaritself:ModelProvider— the SPI interface that provider JARs implementModelProviderRegistry— callsServiceLoaderto discover all providers on the classpath;registerAll()is proposed to be called once inside ADK'sRunnerandAdkWebServer.start()OpenAiCompatibleLlm— reusable base class for anyPOST /v1/chat/completionsproviderProvider JARs (e.g.,
adk-provider-groq) live outside ADK, depend ongoogle-adk.jar, and self-register viaMETA-INF/services. From the application developer's perspective, switching to Groq requires exactly two things:That's it. No Java code in the application at all. The provider JAR self-registers via
META-INF/services, ADK's startup discovers it viaServiceLoader, and the model name string routes to the right backend automatically. Switching from Groq to Ollama is a dependency swap plus a config change — identical to the LiteLLM experience for Python developers.Summary:
model-prismapproachOpenAiCompatibleLlm(1 class)ModelProviderSPI +ModelProviderRegistry+OpenAiCompatibleLlmbase classregisterAll()callRunner/AdkWebServer.start()— zero app involvementbuilder()...registerWithPattern()per providerServiceLoader(META-INF/services)Looking forward to hearing what direction fits ADK's roadmap.
akbarkanso commentedon Aug 14, 2026
Hi all, we hit this from the other direction:
contrib/spring-aiis not a working alternative for OpenAI-compatible endpoints on Spring AI 2.0.0, tool results never reach the model #1429. So there is currently no supported path for these gateways, which is the gap this would close.We ended up wrapping
core'sChatCompletionsHttpClientin a smallBaseLlm, the same shape proposed here, about 50-80 lines plus a retry policy. If this lands, ours becomes a deletion.One design question worth settling early: does an official connector own retry/backoff, or delegate it? Spring AI gave us
spring.ai.retry.*for 429s, and shared gateways return 429 routinely, so a straight port of the request/response mapping silently loses rate-limit handling.airouter-dev commentedon Aug 14, 2026
A provider-neutral builder is a good fit for this request. A few interoperability cases are worth making explicit in the contract:
Disclosure: I work on AI Router. Its English-root service can be used as an external HTTPS fixture for authenticated model discovery and OpenAI-compatible Chat Completions: https://ai-router.dev. This does not require a first-party ADK provider preset.