sapaicore

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 9 Imported by: 0

README

adk-provider-sapaicore

ADK Go v2 model provider for SAP AI Core.

Implements model.LLM so any ADK agent can use GPT, Claude, Gemini, Mistral, or any other model deployed on SAP AI Core without changing agent code.

Install

go get github.com/PedroKlein/adk-provider-sapaicore

Requires Go 1.25+.

Quick Start

The simplest setup uses orchestration mode with auto-discovery (the default):

provider, err := sapaicore.NewProvider(
    sapaicore.WithEndpoint(os.Getenv("AI_CORE_ENDPOINT")),
    sapaicore.WithAuth(
        os.Getenv("AI_CORE_CLIENT_ID"),
        os.Getenv("AI_CORE_CLIENT_SECRET"),
        os.Getenv("AI_CORE_AUTH_URL"),
    ),
)

Then pass the model to any ADK agent:

llm, _ := provider.Model("gpt-4.1")

agent := llmagent.New(llmagent.Config{
    Name:        "my-agent",
    Model:       llm,
    Instruction: "You are a helpful assistant.",
})

r := runner.New(runner.Config{AppName: "my-app", Agent: agent})

for event, err := range r.Run(ctx, userID, sessionID, userMsg) {
    // handle events
}

Any model available on your SAP AI Core instance works by name:

provider.Model("gpt-4.1-mini")
provider.Model("anthropic--claude-4.5-sonnet")
provider.Model("gemini-2.5-flash")
provider.Model("o4-mini")
provider.Model("mistralai--mistral-large-instruct")
provider.Model("deepseek-r1-0528")

Streaming

Streaming works through the standard ADK interface. The provider yields partial text chunks followed by a final aggregated response:

for resp, err := range llm.GenerateContent(ctx, req, true) {
    if resp.Partial {
        fmt.Print(resp.Content.Parts[0].Text) // incremental text
        continue
    }
    // Final response with full text, usage metadata, and finish reason
    fmt.Println(resp.Content.Parts[0].Text)
    fmt.Println(resp.UsageMetadata.PromptTokenCount)
}

Tool Calling

Define tools using standard ADK/genai types. The provider handles the full cycle: function call requests from the model, and function results sent back.

llm, _ := provider.Model("gpt-4.1-mini")

req := &model.LLMRequest{
    Contents: []*genai.Content{
        {Parts: []*genai.Part{{Text: "What's the weather in Berlin?"}}, Role: "user"},
    },
    Config: &genai.GenerateContentConfig{
        Tools: []*genai.Tool{{
            FunctionDeclarations: []*genai.FunctionDeclaration{{
                Name:        "get_weather",
                Description: "Get current weather for a city",
                Parameters:  &genai.Schema{Type: "OBJECT", Properties: map[string]*genai.Schema{
                    "city": {Type: "STRING"},
                }, Required: []string{"city"}},
            }},
        }},
    },
}

// Model returns FunctionCall parts
for resp, _ := range llm.GenerateContent(ctx, req, false) {
    for _, part := range resp.Content.Parts {
        if part.FunctionCall != nil {
            fmt.Printf("Call: %s(%v)\n", part.FunctionCall.Name, part.FunctionCall.Args)
        }
    }
}

Sending tool results back works through the standard content history:

// After executing the tool, send the result back:
contents := []*genai.Content{
    {Parts: []*genai.Part{{Text: "What's the weather?"}}, Role: "user"},
    {Parts: []*genai.Part{{FunctionCall: call}}, Role: "model"},
    {Parts: []*genai.Part{{FunctionResponse: &genai.FunctionResponse{
        ID: call.ID, Name: call.Name,
        Response: map[string]any{"temp": "22°C", "condition": "sunny"},
    }}}, Role: "user"},
}

Foundation-Models Mode

For per-model deployments (dedicated capacity, custom fine-tunes):

provider, _ := sapaicore.NewProvider(
    sapaicore.WithEndpoint(os.Getenv("AI_CORE_ENDPOINT")),
    sapaicore.WithAuth(clientID, clientSecret, authURL),
    sapaicore.WithDeployments(map[string]string{
        "gpt-4.1":      "d1234abc",
        "gpt-4.1-mini": "d5678def",
    }),
)

llm, _ := provider.Model("gpt-4.1") // routes to deployment d1234abc

Extra Model Parameters

For provider-specific features beyond what ADK exposes:

// Claude extended thinking
llm, _ := provider.Model("anthropic--claude-4.5-sonnet",
    sapaicore.WithModelParams(map[string]any{
        "thinking":   map[string]any{"type": "enabled", "budget_tokens": 16384},
        "max_tokens": 64000,
    }),
)

// Claude 4.6+ native 1M context window
llm, _ := provider.Model("anthropic--claude-4.6-sonnet",
    sapaicore.WithModelParams(map[string]any{
        "max_tokens": 200000,
    }),
)

// OpenAI reasoning effort
llm, _ := provider.Model("o4-mini",
    sapaicore.WithModelParams(map[string]any{
        "reasoning_effort": "high",
    }),
)

// Logprobs
llm, _ := provider.Model("gpt-4.1-mini",
    sapaicore.WithModelParams(map[string]any{
        "logprobs":     true,
        "top_logprobs": 3,
    }),
)

Features

Supported
Feature Orchestration Foundation Notes
Non-streaming generation
Streaming (SSE) Partial chunks + final aggregated response
Tool calling Function calls and function results
Tool calling (streaming) Deltas assembled into complete calls
System instructions
Multi-turn conversation Full message history
Temperature / TopP
MaxOutputTokens
StopSequences
FrequencyPenalty / PresencePenalty
JSON response format With schema validation
Extra model params (WithModelParams) Thinking, reasoning_effort, logprobs, etc.
Server-side timeout/retries - Orchestration only
OAuth2 token caching Auto-refresh before expiry
Auto-discovery - Finds orchestration deployment automatically
Custom HTTP client
Custom headers
Resource groups
Refusal handling ErrorCode="refusal"
BeforeModelCallback (model override) req.Model respected at runtime
Roadmap

Phase 1 - ADK field coverage (extract from GenerateContentConfig, no new APIs):

Feature ADK Field Status
Seed (deterministic outputs) Seed Planned
TopK sampling TopK Planned
Logprobs in response LogprobsResult Planned
Tool choice (auto/none/required) ToolConfig Planned (foundation mode only*)

*SAP AI Core orchestration mode doesn't support tool_choice yet (tracking issue).

Phase 2 - SAP AI Core orchestration modules (new With* provider options):

Feature SAP Module Description
Content filtering filtering Input/output safety filtering
Data masking masking PII redaction before sending to LLM
Document grounding (RAG) grounding Retrieve from enterprise knowledge bases
Translation translation Input/output language translation
Module fallback fallback chain Try model A, fall back to model B
Prompt caching cache_control Cost reduction for repeated context

Phase 3 - Pending SAP AI Core support:

Feature Notes
Tool choice in orchestration mode Waiting on SAP to ship (#1500)
Multi-modal input (images) SAP supports image_url in messages

API Reference

Provider Options
Option Description
WithEndpoint(url) SAP AI Core API base URL (required)
WithAuth(id, secret, authURL) OAuth2 credentials (required)
WithOrchestration() Orchestration mode: auto-discovers deployment (default)
WithDeploymentID(id) Orchestration mode: explicit deployment ID
WithDeployments(map) Foundation-models mode: per-model deployment IDs
WithResourceGroup(group) Resource group (default: "default")
WithHTTPClient(client) Custom *http.Client
WithHeaders(headers) Extra HTTP headers on every request
WithTimeout(seconds) Server-side LLM timeout in seconds (default: 600)
WithMaxRetries(n) Server-side retry count (default: 2)

If none of WithOrchestration, WithDeploymentID, or WithDeployments is specified, orchestration auto-discovery is used. These three options are mutually exclusive.

Model Options
Option Description
WithModelParams(map) Extra params forwarded to the model
Errors
sapaicore.ErrMissingConfig      // required option not provided
sapaicore.ErrDeploymentNotFound // model name not in Deployments map (foundation mode only)
sapaicore.ErrDiscovery          // orchestration deployment auto-discovery failed

Credentials

From your SAP AI Core service key (BTP cockpit → Instances → Service Keys):

Option Service Key Field
WithEndpoint serviceurls.AI_API_URL
WithAuth clientID uaa.clientid
WithAuth clientSecret uaa.clientsecret
WithAuth authURL uaa.url + /oauth/token

How It Works

Orchestration mode sends all requests to SAP AI Core's harmonized API:

POST {endpoint}/v2/inference/deployments/{deploymentId}/v2/completion

The model name is embedded in the request body. SAP AI Core routes to the appropriate provider (OpenAI, Anthropic, Google, etc.).

Foundation-models mode sends requests directly to per-model deployments:

POST {endpoint}/v2/inference/deployments/{perModelId}/v1/chat/completions

Dependencies

Two direct dependencies:

  • google.golang.org/adk/v2 - ADK model interface
  • google.golang.org/genai - genai types (Content, Schema, Tool, etc.)

Development

This project uses mise for toolchain management. Run mise install to get the correct Go and tooling versions.

mise install
mise run check  # build + vet + lint + test

Or run individual tasks:

mise run build
mise run lint
mise run test
Smoke Tests

Integration tests against a live SAP AI Core instance covering both API modes, streaming, tool calling, extended thinking, and full ADK agent loops.

go test -tags=smoke ./smoketest/ -v -timeout=5m

See smoketest/README.md for credentials setup and the full test catalog.

License

MIT

Documentation

Overview

Package sapaicore implements the ADK Go v2 model.LLM interface for SAP AI Core.

It bridges Google's Agent Development Kit with SAP AI Core's inference API, supporting two modes of operation:

  • Orchestration: a single deployment handles all models via the SAP AI Core harmonized API. Supports extended thinking, response format, tool calling, and server-side timeout/retry configuration.

  • Foundation-models: per-model deployment IDs with a direct OpenAI-compatible chat completions API.

Quick Start

provider, err := sapaicore.NewProvider(
    sapaicore.WithEndpoint("https://api.ai.prod.us-east-1.aws.ml.hana.ondemand.com"),
    sapaicore.WithAuth(clientID, clientSecret, authURL),
    sapaicore.WithOrchestration(), // auto-discovers deployment
)
if err != nil {
    log.Fatal(err)
}

llm, err := provider.Model("gpt-4.1")

Both streaming and non-streaming generation are supported via the standard ADK model.LLM interface.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrMissingConfig      = errors.New("sapaicore: missing required configuration")
	ErrDeploymentNotFound = errors.New("sapaicore: deployment not found for model")
	ErrDiscovery          = errors.New("sapaicore: orchestration deployment discovery")
)

Sentinel errors.

Functions

This section is empty.

Types

type ModelOption

type ModelOption func(*modelConfig)

ModelOption configures a specific model instance returned by Provider.Model.

func WithModelParams

func WithModelParams(params map[string]any) ModelOption

WithModelParams adds extra parameters forwarded directly to the model. In orchestration mode these go into model.params (e.g. thinking, reasoning_effort). In foundation-models mode these are merged into the top-level request body.

Example
package main

import (
	"context"
	"fmt"
	"log"

	sapaicore "github.com/PedroKlein/adk-provider-sapaicore"
)

func main() {
	provider, err := sapaicore.NewProvider(context.Background(),
		sapaicore.WithEndpoint("https://api.ai.prod.us-east-1.aws.ml.hana.ondemand.com"),
		sapaicore.WithAuth("client-id", "client-secret", "https://auth.example.com/oauth/token"),
		sapaicore.WithDeploymentID("d1234abc"),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Enable extended thinking for Claude models.
	llm, err := provider.Model("anthropic--claude-4.5-sonnet",
		sapaicore.WithModelParams(map[string]any{
			"thinking":   map[string]any{"type": "enabled", "budget_tokens": 16384},
			"max_tokens": 64000,
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(llm.Name())
}
Output:
anthropic--claude-4.5-sonnet

type Option

type Option func(*providerConfig)

Option configures a Provider. Pass options to NewProvider.

func WithAuth

func WithAuth(clientID, clientSecret, authURL string) Option

WithAuth sets the OAuth2 client credentials. authURL is the token endpoint, e.g. "https://xxx.authentication.xxx.hana.ondemand.com/oauth/token".

func WithDeploymentID

func WithDeploymentID(id string) Option

WithDeploymentID enables orchestration mode using a specific deployment ID.

func WithDeployments

func WithDeployments(deployments map[string]string) Option

WithDeployments enables foundation-models mode with per-model deployment IDs.

func WithEndpoint

func WithEndpoint(endpoint string) Option

WithEndpoint sets the SAP AI Core API base URL.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom HTTP client for all requests.

func WithHeaders

func WithHeaders(headers http.Header) Option

WithHeaders adds custom HTTP headers to every request.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the server-side retry count for LLM requests. Default is 2 retries.

func WithOrchestration

func WithOrchestration() Option

WithOrchestration enables orchestration mode by automatically discovering the orchestration deployment. It queries the SAP AI Core deployments API at provider creation time to find the running orchestration deployment.

func WithResourceGroup

func WithResourceGroup(group string) Option

WithResourceGroup sets the SAP AI Core resource group. Defaults to "default".

Example
package main

import (
	"context"
	"fmt"
	"log"

	sapaicore "github.com/PedroKlein/adk-provider-sapaicore"
)

func main() {
	provider, err := sapaicore.NewProvider(context.Background(),
		sapaicore.WithEndpoint("https://api.ai.prod.us-east-1.aws.ml.hana.ondemand.com"),
		sapaicore.WithAuth("client-id", "client-secret", "https://auth.example.com/oauth/token"),
		sapaicore.WithDeploymentID("d1234abc"),
		sapaicore.WithResourceGroup("my-team-rg"),
	)
	if err != nil {
		log.Fatal(err)
	}

	llm, err := provider.Model("gpt-4.1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(llm.Name())
}
Output:
gpt-4.1

func WithTimeout

func WithTimeout(seconds int) Option

WithTimeout sets the server-side LLM request timeout in seconds. Default is 600 seconds.

type Provider

type Provider struct {
	// contains filtered or unexported fields
}

Provider creates model.LLM instances backed by SAP AI Core deployments.

func NewProvider

func NewProvider(ctx context.Context, opts ...Option) (*Provider, error)

NewProvider validates the given options and returns a ready-to-use Provider. It returns ErrMissingConfig if required options are absent.

If no mode is specified, orchestration auto-discovery is used by default. NewProvider makes an HTTP call to discover the orchestration deployment ID when auto-discovery is active.

ctx is used for any HTTP calls made during provider initialization (e.g. orchestration deployment discovery). It does not affect subsequent Model() or GenerateContent() calls.

Example (FoundationModels)
package main

import (
	"context"
	"fmt"
	"log"

	sapaicore "github.com/PedroKlein/adk-provider-sapaicore"
)

func main() {
	provider, err := sapaicore.NewProvider(context.Background(),
		sapaicore.WithEndpoint("https://api.ai.prod.us-east-1.aws.ml.hana.ondemand.com"),
		sapaicore.WithAuth("client-id", "client-secret", "https://auth.example.com/oauth/token"),
		sapaicore.WithDeployments(map[string]string{
			"gpt-4.1":      "d1234abc",
			"gpt-4.1-mini": "d5678def",
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	llm, err := provider.Model("gpt-4.1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(llm.Name())
}
Output:
gpt-4.1
Example (Orchestration)
package main

import (
	"context"
	"fmt"
	"log"

	sapaicore "github.com/PedroKlein/adk-provider-sapaicore"
)

func main() {
	provider, err := sapaicore.NewProvider(context.Background(),
		sapaicore.WithEndpoint("https://api.ai.prod.us-east-1.aws.ml.hana.ondemand.com"),
		sapaicore.WithAuth("client-id", "client-secret", "https://auth.example.com/oauth/token"),
		sapaicore.WithDeploymentID("d1234abc"), // or use WithOrchestration() for auto-discovery
	)
	if err != nil {
		log.Fatal(err)
	}

	llm, err := provider.Model("gpt-4.1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(llm.Name())
}
Output:
gpt-4.1

func (*Provider) Model

func (p *Provider) Model(name string, opts ...ModelOption) (model.LLM, error)

Model returns a model.LLM for the given model name.

In orchestration mode, name is any SAP AI Core model identifier (e.g. "gpt-4.1", "anthropic--claude-4.5-sonnet").

In foundation-models mode, name must exist in the map provided to WithDeployments. Returns ErrDeploymentNotFound if the name is not registered.

Directories

Path Synopsis
internal
auth
Package auth manages OAuth2 client-credentials tokens with thread-safe caching.
Package auth manages OAuth2 client-credentials tokens with thread-safe caching.
convert
Package convert transforms between Google genai types and OpenAI-compatible wire types.
Package convert transforms between Google genai types and OpenAI-compatible wire types.
foundation
Package foundation implements the SAP AI Core foundation-models mode strategy.
Package foundation implements the SAP AI Core foundation-models mode strategy.
openai
Package openai defines wire types for the OpenAI-compatible chat completions format used by both SAP AI Core API modes.
Package openai defines wire types for the OpenAI-compatible chat completions format used by both SAP AI Core API modes.
orchestration
Package orchestration implements the SAP AI Core orchestration mode strategy.
Package orchestration implements the SAP AI Core orchestration mode strategy.
request
Package request provides shared HTTP execution for SAP AI Core inference requests.
Package request provides shared HTTP execution for SAP AI Core inference requests.
stream
Package stream handles Server-Sent Events parsing and incremental aggregation of streaming chat completion responses.
Package stream handles Server-Sent Events parsing and incremental aggregation of streaming chat completion responses.
Package smoketest provides integration tests against a live SAP AI Core instance.
Package smoketest provides integration tests against a live SAP AI Core instance.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL