integration

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Apr 14, 2026 License: MIT Imports: 0 Imported by: 0

Documentation

Overview

Package integration contains example-style tests that demonstrate how to call the inference-go library against real providers.

The package is internal so it does not become part of the public API surface, but you can run the examples locally with:

go test ./internal/integration -run Example

All examples are best-effort: they only attempt live API calls when the relevant environment variables are set, e.g.:

ANTHROPIC_API_KEY=<your key>
OPENAI_API_KEY=<your key>
GEMINI_API_KEY=<your key>   (or GOOGLE_API_KEY=<your key>)
Example (Anthropic_basicConversation)

Example_anthropic_basicConversation demonstrates a minimal non-streaming call to Anthropic's Messages API using the normalized inference-go API.

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "anthropic", &inference.AddProviderConfig{
	SDKType:                  spec.ProviderSDKTypeAnthropic,
	Origin:                   spec.DefaultAnthropicOrigin,
	ChatCompletionPathPrefix: spec.DefaultAnthropicChatCompletionPrefix,
	APIKeyHeaderKey:          spec.DefaultAnthropicAuthorizationHeaderKey,
	// DefaultHeaders are optional; the official SDK sets anthropic-version.
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic provider:", err)
	return
}

apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "ANTHROPIC_API_KEY not set; skipping live Anthropic call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "anthropic", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "claude-haiku-4-5-20251001",
		Stream:          false,
		MaxPromptLength: 4096,
		MaxOutputLength: 256,
		SystemPrompt:    "You are a concise, helpful assistant.",
	},
	Inputs: []spec.InputUnion{
		{
			Kind: spec.InputKindInputMessage,
			InputMessage: &spec.InputOutputContent{
				Role: spec.RoleUser,
				Contents: []spec.InputOutputContentItemUnion{
					{
						Kind: spec.ContentItemKindText,
						TextItem: &spec.ContentItemText{
							Text: "Say hello from Anthropic in one short sentence.",
						},
					},
				},
			},
		},
	},
}

resp, err := ps.FetchCompletion(ctx, "anthropic", req, &spec.FetchCompletionOptions{CompletionKey: "haiku45"})
if err != nil {
	fmt.Fprintln(os.Stderr, "FetchCompletion error:", err)
	if resp != nil && resp.Error != nil {
		fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
	}
	return
}

for _, out := range resp.Outputs {
	if out.Kind != spec.OutputKindOutputMessage || out.OutputMessage == nil {
		continue
	}
	for _, c := range out.OutputMessage.Contents {
		if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
			fmt.Fprintln(os.Stderr, "Anthropic assistant:", c.TextItem.Text)
		}
	}
}
fmt.Println("OK")
Output:
OK
Example (Anthropic_functionToolRoundTrip)

Example_anthropic_functionToolRoundTrip demonstrates the full Anthropic client-tool flow that requires strict ordering:

  1. user message + tool choice
  2. assistant emits a function tool call
  3. caller executes the tool locally
  4. next request sends: - original user turn - assistant tool call - user tool_result - extra user text
  5. assistant returns the final answer

This is the flow where, for Anthropic, tool_result must immediately follow the assistant tool-use turn as the next user turn.

ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "anthropic", &inference.AddProviderConfig{
	SDKType:                  spec.ProviderSDKTypeAnthropic,
	Origin:                   spec.DefaultAnthropicOrigin,
	ChatCompletionPathPrefix: spec.DefaultAnthropicChatCompletionPrefix,
	APIKeyHeaderKey:          spec.DefaultAnthropicAuthorizationHeaderKey,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic provider:", err)
	return
}

apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "ANTHROPIC_API_KEY not set; skipping live Anthropic call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "anthropic", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

tool := spec.ToolChoice{
	Type:        spec.ToolTypeFunction,
	ID:          "echo-tool",
	Name:        "echo_text",
	Description: "Echo the provided text back in a deterministic tool result.",
	Arguments: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"text": map[string]any{
				"type": "string",
			},
		},
		"required":             []any{"text"},
		"additionalProperties": false,
	},
}

initialUser := newUserTextInput(
	`Use the echo_text tool with text "anthropic tool round trip". Do not answer yet; just call the tool.`,
)

firstReq := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "claude-sonnet-4-6",
		MaxOutputLength: 512,
		SystemPrompt: strings.Join([]string{
			"You are validating a client tool round trip.",
			"When the tool is forced, emit only the tool call in the first response.",
			"Do not provide the final answer until after the tool result is returned.",
		}, " "),
	},
	Inputs:      []spec.InputUnion{initialUser},
	ToolChoices: []spec.ToolChoice{tool},
	ToolPolicy: &spec.ToolPolicy{
		Mode: spec.ToolPolicyModeTool,
		AllowedTools: []spec.AllowedTool{
			{ToolChoiceID: tool.ID},
		},
		DisableParallel: true,
	},
}

firstResp, err := ps.FetchCompletion(ctx, "anthropic", firstReq, &spec.FetchCompletionOptions{
	CompletionKey: "sonnet46",
})
if err != nil {
	fmt.Fprintln(os.Stderr, "first FetchCompletion error:", err)
	return
}

call, err := firstFunctionToolCall(firstResp)
if err != nil {
	fmt.Fprintln(os.Stderr, "expected a function tool call, got error:", err)
	return
}
fmt.Fprintf(
	os.Stderr,
	"tool call: name=%s id=%s args=%s\n",
	call.Name,
	nonEmpty(call.CallID, call.ID),
	call.Arguments,
)

toolOutput, err := runEchoTool(call)
if err != nil {
	fmt.Fprintln(os.Stderr, "error executing local tool:", err)
	return
}
fmt.Fprintf(os.Stderr, "tool result for %s: %s\n", toolOutput.CallID, firstToolOutputText(toolOutput))

// This second request intentionally sends:
//   - prior original user turn
//   - assistant tool call
//   - tool output
//   - extra user text
//
// The Anthropic adapter should normalize the last two into the immediate next
// user turn, with tool_result first and user text after it.
secondReq := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "claude-sonnet-4-6",
		MaxOutputLength: 256,
		SystemPrompt: strings.Join([]string{
			"You have now received the tool result.",
			"Answer briefly in plain text.",
			"Do not call any tool again.",
		}, " "),
	},
	Inputs: []spec.InputUnion{
		initialUser,
		{
			Kind:             spec.InputKindFunctionToolCall,
			FunctionToolCall: call,
		},
		{
			Kind:               spec.InputKindFunctionToolOutput,
			FunctionToolOutput: toolOutput,
		},
		newUserTextInput("Now finish in one short sentence."),
	},
	ToolChoices: []spec.ToolChoice{tool},
	ToolPolicy: &spec.ToolPolicy{
		Mode: spec.ToolPolicyModeNone,
	},
}

secondResp, err := ps.FetchCompletion(ctx, "anthropic", secondReq, &spec.FetchCompletionOptions{
	CompletionKey: "sonnet46",
})
if err != nil {
	fmt.Fprintln(os.Stderr, "second FetchCompletion error:", err)
	return
}

finalText := responseText(secondResp)
if finalText != "" {
	fmt.Fprintln(os.Stderr, "final assistant text:", finalText)
}

fmt.Println("OK")
Output:
OK
Example (Anthropic_toolsAndThinkingStreaming)

Example_anthropic_toolsAndThinkingStreaming demonstrates:

  • streaming text + thinking
  • function tools + anthropic server web search
  • JSON schema output request
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "anthropic", &inference.AddProviderConfig{
	SDKType:                  spec.ProviderSDKTypeAnthropic,
	Origin:                   spec.DefaultAnthropicOrigin,
	ChatCompletionPathPrefix: spec.DefaultAnthropicChatCompletionPrefix,
	APIKeyHeaderKey:          spec.DefaultAnthropicAuthorizationHeaderKey,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic provider:", err)
	return
}

apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "ANTHROPIC_API_KEY not set; skipping live Anthropic call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "anthropic", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

tools := []spec.ToolChoice{
	{
		Type:        spec.ToolTypeFunction,
		ID:          "extract-key-points",
		Name:        "extract_key_points",
		Description: "Extract 3 key points from the provided text.",
		Arguments: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"text": map[string]any{"type": "string"},
			},
			"required":             []any{"text"},
			"additionalProperties": false,
		},
	},
	{
		Type: spec.ToolTypeWebSearch,
		ID:   "web-search",
		Name: spec.DefaultWebSearchToolName,
		WebSearchArguments: &spec.WebSearchToolChoiceItem{
			MaxUses:           1,
			SearchContextSize: spec.WebSearchContextSizeMedium,
		},
	},
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:         "claude-sonnet-4-6",
		Stream:       true,
		SystemPrompt: "Use tools when helpful. Keep the final answer short.",
		Reasoning: &spec.ReasoningParam{
			Type:  spec.ReasoningTypeSingleWithLevels,
			Level: spec.ReasoningLevelMedium,
		},
		OutputParam: &spec.OutputParam{
			Format: &spec.OutputFormat{
				Kind: spec.OutputFormatKindJSONSchema,
				JSONSchemaParam: &spec.JSONSchemaParam{
					Name: "answer",
					Schema: map[string]any{
						"type": "object",
						"properties": map[string]any{
							"summary":     map[string]any{"type": "string"},
							"source_used": map[string]any{"type": "boolean"},
						},
						"required":             []any{"summary", "source_used"},
						"additionalProperties": false,
					},
				},
			},
		},
	},
	Inputs: []spec.InputUnion{{
		Kind: spec.InputKindInputMessage,
		InputMessage: &spec.InputOutputContent{
			Role: spec.RoleUser,
			Contents: []spec.InputOutputContentItemUnion{{
				Kind:     spec.ContentItemKindText,
				TextItem: &spec.ContentItemText{Text: "What is the latest stable Go version? If unknown, say so."},
			}},
		},
	}},
	ToolChoices: tools,
	ToolPolicy: &spec.ToolPolicy{
		Mode:            spec.ToolPolicyModeAuto,
		DisableParallel: true,
	},
}

_, err = ps.FetchCompletion(ctx, "anthropic", req, &spec.FetchCompletionOptions{
	CompletionKey: "sonnet46",
	StreamHandler: func(ev spec.StreamEvent) error {
		switch ev.Kind {
		case spec.StreamContentKindThinking:
			if ev.Thinking != nil {
				fmt.Fprintf(os.Stderr, "[thinking] %s\n", ev.Thinking.Text)
			}
		case spec.StreamContentKindText:
			if ev.Text != nil {
				fmt.Fprint(os.Stderr, ev.Text.Text)
			}
		}
		return nil
	},
})
if err != nil {
	fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
	return
}

fmt.Println("OK")
Output:
OK
Example (GoogleGenerateContent_basicConversation)

Example_googleGenerateContent_basicConversation demonstrates a minimal non-streaming call to Google's Generative AI API (Gemini) using the normalized inference-go API.

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "google", &inference.AddProviderConfig{
	SDKType: spec.ProviderSDKTypeGoogleGenerateContent,
	Origin:  spec.DefaultGoogleGenerateContentOrigin,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Google GenAI provider:", err)
	return
}

apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
	apiKey = os.Getenv("GOOGLE_API_KEY")
}
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "GEMINI_API_KEY not set; skipping live Google GenAI call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "google", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Google GenAI API key:", err)
	return
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "gemini-3-flash-preview",
		Stream:          false,
		MaxPromptLength: 4096,
		MaxOutputLength: 256,
		SystemPrompt:    "You are a concise, helpful assistant.",
	},
	Inputs: []spec.InputUnion{
		{
			Kind: spec.InputKindInputMessage,
			InputMessage: &spec.InputOutputContent{
				Role: spec.RoleUser,
				Contents: []spec.InputOutputContentItemUnion{
					{
						Kind: spec.ContentItemKindText,
						TextItem: &spec.ContentItemText{
							Text: "Say hello from Google Gemini in one short sentence.",
						},
					},
				},
			},
		},
	},
}

resp, err := ps.FetchCompletion(
	ctx, "google", req,
	&spec.FetchCompletionOptions{CompletionKey: "gemini-flash"},
)
if err != nil {
	fmt.Fprintln(os.Stderr, "FetchCompletion error:", err)
	if resp != nil && resp.Error != nil {
		fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
	}
	return
}

for _, out := range resp.Outputs {
	if out.Kind != spec.OutputKindOutputMessage || out.OutputMessage == nil {
		continue
	}
	for _, c := range out.OutputMessage.Contents {
		if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
			fmt.Fprintln(os.Stderr, "Gemini assistant:", c.TextItem.Text)
		}
	}
}
fmt.Println("OK")
Output:
OK
Example (GoogleGenerateContent_functionToolRoundTrip)

Example_googleGenerateContent_functionToolRoundTrip demonstrates a full Gemini function-tool round trip:

  1. user message + tool definition
  2. model emits a function tool call
  3. caller executes the tool locally
  4. next request sends: - original user turn - assistant function tool call - user function tool output - extra user text
  5. model returns the final answer
package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/flexigpt/inference-go"
	"github.com/flexigpt/inference-go/spec"
)

const testModelName = "gemini-2.5-flash"

// Example_googleGenerateContent_functionToolRoundTrip demonstrates a full
// Gemini function-tool round trip:
//
//  1. user message + tool definition
//  2. model emits a function tool call
//  3. caller executes the tool locally
//  4. next request sends:
//     - original user turn
//     - assistant function tool call
//     - user function tool output
//     - extra user text
//  5. model returns the final answer
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
	defer cancel()

	ps, err := newProviderSetWithDebug(slog.LevelInfo)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
		return
	}

	_, err = ps.AddProvider(ctx, "google-tools", &inference.AddProviderConfig{
		SDKType: spec.ProviderSDKTypeGoogleGenerateContent,
		Origin:  spec.DefaultGoogleGenerateContentOrigin,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding Google GenAI provider:", err)
		return
	}

	apiKey := os.Getenv("GEMINI_API_KEY")
	if apiKey == "" {
		apiKey = os.Getenv("GOOGLE_API_KEY")
	}
	if apiKey == "" {
		fmt.Fprintln(os.Stderr, "GEMINI_API_KEY/GOOGLE_API_KEY not set; skipping live Google GenAI tool example")
		fmt.Println("OK")
		return
	}
	if err := ps.SetProviderAPIKey(ctx, "google-tools", apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting Google GenAI API key:", err)
		return
	}

	tool := spec.ToolChoice{
		Type:        spec.ToolTypeFunction,
		ID:          "echo-tool",
		Name:        "echo_text",
		Description: "Echo the provided text back in a deterministic tool result.",
		Arguments: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"text": map[string]any{
					"type": "string",
				},
			},
			"required":             []any{"text"},
			"additionalProperties": false,
		},
	}

	initialUser := newUserTextInput(
		`this is a test. think about what 20 new words you can say and say that in text. then call the echo_text tool with that text.`,
	)

	firstReq := &spec.FetchCompletionRequest{
		ModelParam: spec.ModelParam{
			Name:            testModelName,
			MaxOutputLength: 512,
			Stream:          true,
			Reasoning: &spec.ReasoningParam{
				Type:   spec.ReasoningTypeHybridWithTokens,
				Tokens: 256,
			},
			SystemPrompt: "You are validating a Gemini function tool round trip. " +
				"When the tool is forced, emit only the tool call in the first response.",
		},
		Inputs:      []spec.InputUnion{initialUser},
		ToolChoices: []spec.ToolChoice{tool},
		ToolPolicy: &spec.ToolPolicy{
			Mode: spec.ToolPolicyModeTool,
			AllowedTools: []spec.AllowedTool{
				{ToolChoiceID: tool.ID},
			},
			DisableParallel: true,
		},
	}

	firstResp, err := ps.FetchCompletion(ctx, "google-tools", firstReq, &spec.FetchCompletionOptions{
		CompletionKey: testModelName,
		StreamHandler: func(ev spec.StreamEvent) error {
			switch ev.Kind {
			case spec.StreamContentKindThinking:
				if ev.Thinking != nil {
					fmt.Fprintf(os.Stderr, "\n\n#######[thinking 1] %s\n", ev.Thinking.Text)
				}
			case spec.StreamContentKindText:
				if ev.Text != nil {
					fmt.Fprintf(os.Stderr, "\n\n#######[text 1] %s\n", ev.Text.Text)
				}
			}
			return nil
		},
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, "first FetchCompletion error:", err)
		return
	}

	firstText := responseText(firstResp)
	if firstText != "" {
		fmt.Fprintln(os.Stderr, "FIRST assistant text:", firstText)
	}

	call, err := firstFunctionToolCall(firstResp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "expected a function tool call, got error:", err)
		return
	}
	fmt.Fprintf(
		os.Stderr,
		"\ntool call 1: name=%s id=%s args=%s\n",
		call.Name,
		nonEmpty(call.CallID, call.ID),
		call.Arguments,
	)

	toolOutput, err := runEchoTool(call)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error executing local tool:", err)
		return
	}
	fmt.Fprintf(os.Stderr, "\ntool result 1 for %s: %s\n", toolOutput.CallID, firstToolOutputText(toolOutput))

	history := make([]spec.InputUnion, 0, len(firstResp.Outputs)+2)
	history = append(history, initialUser)
	history = append(history, outputUnionsToInputs(firstResp.Outputs)...)
	history = append(history, spec.InputUnion{
		Kind:               spec.InputKindFunctionToolOutput,
		FunctionToolOutput: toolOutput,
	})

	secondReq := &spec.FetchCompletionRequest{
		ModelParam: spec.ModelParam{
			Name:            testModelName,
			MaxOutputLength: 256,
			Stream:          true,
			Reasoning: &spec.ReasoningParam{
				Type:   spec.ReasoningTypeHybridWithTokens,
				Tokens: 256,
			},
			SystemPrompt: "You have now received the tool result. " +
				"Answer with a sonnet of it. Do not call any tool again.",
		},
		Inputs:      append(history, newUserTextInput("Now finish in one short sentence.")),
		ToolChoices: []spec.ToolChoice{tool},
		ToolPolicy: &spec.ToolPolicy{
			Mode: spec.ToolPolicyModeNone,
		},
	}

	secondResp, err := ps.FetchCompletion(ctx, "google-tools", secondReq, &spec.FetchCompletionOptions{
		CompletionKey: testModelName,
		StreamHandler: func(ev spec.StreamEvent) error {
			switch ev.Kind {
			case spec.StreamContentKindThinking:
				if ev.Thinking != nil {
					fmt.Fprintf(os.Stderr, "\n\n#######[thinking 2] %s\n", ev.Thinking.Text)
				}
			case spec.StreamContentKindText:
				if ev.Text != nil {
					fmt.Fprintf(os.Stderr, "\n\n#######[text 2] %s\n", ev.Text.Text)
				}
			}
			return nil
		},
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, "second FetchCompletion error:", err)
		return
	}

	finalText := responseText(secondResp)
	if finalText != "" {
		fmt.Fprintln(os.Stderr, "\nFINAL assistant text:", finalText)
	}

	fmt.Println("OK")
}
Output:
OK
Example (GoogleGenerateContent_webSearchAndThinkingStreaming)

Example_googleGenerateContent_webSearchAndThinkingStreaming demonstrates:

  • server-side Google web search grounding
  • streaming text + thinking
  • normalized webSearch outputs synthesized from grounding metadata

Note: for Gemini GenerateContent, web search is not a client-side tool-output round trip like function tools. It is server-side grounding.

ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelInfo)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "google-search", &inference.AddProviderConfig{
	SDKType: spec.ProviderSDKTypeGoogleGenerateContent,
	Origin:  spec.DefaultGoogleGenerateContentOrigin,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Google GenAI provider:", err)
	return
}

apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
	apiKey = os.Getenv("GOOGLE_API_KEY")
}
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "GEMINI_API_KEY/GOOGLE_API_KEY not set; skipping live Google GenAI web-search example")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "google-search", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Google GenAI API key:", err)
	return
}

webSearchTool := spec.ToolChoice{
	Type:               spec.ToolTypeWebSearch,
	ID:                 "google-web-search",
	Name:               spec.DefaultWebSearchToolName,
	Description:        "Search the web for recent information.",
	WebSearchArguments: &spec.WebSearchToolChoiceItem{
		// Intentionally minimal here. The Gemini adapter currently exposes
		// web search primarily as an enable/disable capability.
	},
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "gemini-3-flash-preview",
		Stream:          true,
		MaxOutputLength: 512,
		SystemPrompt: "Use web search when helpful. Keep the final answer short. " +
			"If you are unsure, say so plainly.",
		Reasoning: &spec.ReasoningParam{
			Type:  spec.ReasoningTypeSingleWithLevels,
			Level: spec.ReasoningLevelLow,
		},
	},
	Inputs: []spec.InputUnion{
		newUserTextInput(
			"What is the latest stable Go release? If unknown, say unknown. then list features. Then analyze and give its benefit over last release.",
		),
	},
	ToolChoices: []spec.ToolChoice{webSearchTool},
	ToolPolicy: &spec.ToolPolicy{
		// Auto is the right mode for Gemini web search grounding.
		Mode: spec.ToolPolicyModeAuto,
	},
}

resp, err := ps.FetchCompletion(ctx, "google-search", req, &spec.FetchCompletionOptions{
	CompletionKey: "gemini-search",
	StreamHandler: func(ev spec.StreamEvent) error {
		switch ev.Kind {
		case spec.StreamContentKindThinking:
			if ev.Thinking != nil {
				fmt.Fprintf(os.Stderr, "\n\n#######[thinking] %s\n", ev.Thinking.Text)
			}
		case spec.StreamContentKindText:
			if ev.Text != nil {
				fmt.Fprintf(os.Stderr, "\n\n#######[text] %s\n", ev.Text.Text)
			}
		}
		return nil
	},
})
if err != nil {
	fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
	if resp != nil && resp.Error != nil {
		fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
	}
	return
}

fmt.Fprintln(os.Stderr, "\n\n--- normalized outputs ---")
for _, out := range resp.Outputs {
	switch out.Kind {
	case spec.OutputKindWebSearchToolCall:
		if out.WebSearchToolCall != nil {
			fmt.Fprintf(os.Stderr, "Web search call: %+v\n", out.WebSearchToolCall.WebSearchToolCallItems)
		}
	case spec.OutputKindWebSearchToolOutput:
		if out.WebSearchToolOutput != nil {
			for _, item := range out.WebSearchToolOutput.WebSearchToolOutputItems {
				if item.Kind == spec.WebSearchToolOutputKindSearch && item.SearchItem != nil {
					fmt.Fprintf(os.Stderr, "Search result: %s (%s)\n", item.SearchItem.Title, item.SearchItem.URL)
				}
			}
		}
	case spec.OutputKindReasoningMessage:
		if out.ReasoningMessage != nil && len(out.ReasoningMessage.Thinking) > 0 {
			fmt.Fprintln(os.Stderr, "Reasoning:", out.ReasoningMessage.Thinking[0])
		}
	case spec.OutputKindOutputMessage:
		if out.OutputMessage != nil {
			for _, c := range out.OutputMessage.Contents {
				if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
					fmt.Fprintln(os.Stderr, "Final answer:", c.TextItem.Text)
				}
			}
		}
	default:
	}
}

fmt.Println("OK")
Output:
OK
Example (OpenAIChat_basicConversation)

Example_openAIChat_basicConversation demonstrates a minimal non-streaming call to OpenAI's Chat Completions API.

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "openai-chat", &inference.AddProviderConfig{
	SDKType:                  spec.ProviderSDKTypeOpenAIChatCompletions,
	Origin:                   spec.DefaultOpenAIOrigin,
	ChatCompletionPathPrefix: spec.DefaultOpenAIChatCompletionsPrefix,
	APIKeyHeaderKey:          spec.DefaultAuthorizationHeaderKey,
	DefaultHeaders:           spec.OpenAIChatCompletionsDefaultHeaders,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding OpenAI Chat provider:", err)
	return
}

apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "OPENAI_API_KEY not set; skipping live OpenAI Chat call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "openai-chat", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
	return
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "gpt-4.1-mini",
		Stream:          false,
		MaxPromptLength: 4096,
		MaxOutputLength: 256,
		SystemPrompt:    "You are a concise assistant.",
	},
	Inputs: []spec.InputUnion{
		{
			Kind: spec.InputKindInputMessage,
			InputMessage: &spec.InputOutputContent{
				Role: spec.RoleUser,
				Contents: []spec.InputOutputContentItemUnion{
					{
						Kind: spec.ContentItemKindText,
						TextItem: &spec.ContentItemText{
							Text: "Say hello from OpenAI Chat Completions in one short sentence.",
						},
					},
				},
			},
		},
	},
}

resp, err := ps.FetchCompletion(ctx, "openai-chat", req, &spec.FetchCompletionOptions{CompletionKey: "gpt41"})
if err != nil {
	fmt.Fprintln(os.Stderr, "FetchCompletion error:", err)
	if resp != nil && resp.Error != nil {
		fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
	}
	return
}

for _, out := range resp.Outputs {
	if out.Kind != spec.OutputKindOutputMessage || out.OutputMessage == nil {
		continue
	}
	for _, c := range out.OutputMessage.Contents {
		if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
			fmt.Fprintln(os.Stderr, "OpenAI Chat assistant:", c.TextItem.Text)
		}
	}
}

fmt.Println("OK")
Output:
OK
Example (OpenAIChat_toolsAndJSONSchema)

Example_openAIChat_toolsAndJSONSchema demonstrates:

  • streaming text
  • JSON schema output (response_format=json_schema)
  • function tools
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "openai-chat", &inference.AddProviderConfig{
	SDKType:                  spec.ProviderSDKTypeOpenAIChatCompletions,
	Origin:                   spec.DefaultOpenAIOrigin,
	ChatCompletionPathPrefix: spec.DefaultOpenAIChatCompletionsPrefix,
	APIKeyHeaderKey:          spec.DefaultAuthorizationHeaderKey,
	DefaultHeaders:           spec.OpenAIChatCompletionsDefaultHeaders,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding OpenAI Chat provider:", err)
	return
}

apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "OPENAI_API_KEY not set; skipping live OpenAI Chat call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "openai-chat", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
	return
}

tools := []spec.ToolChoice{
	{
		Type:        spec.ToolTypeFunction,
		ID:          "math",
		Name:        "multiply",
		Description: "Multiply two integers.",
		Arguments: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"a": map[string]any{"type": "integer"},
				"b": map[string]any{"type": "integer"},
			},
			"required":             []any{"a", "b"},
			"additionalProperties": false,
		},
	},
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:         "gpt-4.1",
		Stream:       true,
		SystemPrompt: "answer directly",
		OutputParam: &spec.OutputParam{
			Format: &spec.OutputFormat{
				Kind: spec.OutputFormatKindJSONSchema,
				JSONSchemaParam: &spec.JSONSchemaParam{
					Name: "result",
					Schema: map[string]any{
						"type": "object",
						"properties": map[string]any{
							"answer": map[string]any{"type": "string"},
						},
						"required":             []any{"answer"},
						"additionalProperties": false,
					},
					Strict: true,
				},
			},
		},
	},
	Inputs: []spec.InputUnion{{
		Kind: spec.InputKindInputMessage,
		InputMessage: &spec.InputOutputContent{
			Role: spec.RoleUser,
			Contents: []spec.InputOutputContentItemUnion{{
				Kind: spec.ContentItemKindText,
				TextItem: &spec.ContentItemText{
					Text: "What is 6*7 and what's a recent Go release? (If unknown, say unknown.)",
				},
			}},
		},
	}},
	ToolChoices: tools,
	ToolPolicy: &spec.ToolPolicy{
		Mode:            spec.ToolPolicyModeAuto,
		DisableParallel: true,
	},
}

_, err = ps.FetchCompletion(ctx, "openai-chat", req, &spec.FetchCompletionOptions{
	CompletionKey: "gpt41",
	StreamHandler: func(ev spec.StreamEvent) error {
		if ev.Kind == spec.StreamContentKindText && ev.Text != nil {
			fmt.Fprint(os.Stderr, ev.Text.Text)
		}
		return nil
	},
})
if err != nil {
	fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
	return
}

fmt.Println("OK")
Output:
OK
Example (OpenAIResponses_basicConversation)

Example_openAIResponses_basicConversation demonstrates a minimal non-streaming call to OpenAI's Responses API using text-only input.

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

ps, err := newProviderSetWithDebug(slog.LevelDebug)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
	return
}

_, err = ps.AddProvider(ctx, "openai-responses", &inference.AddProviderConfig{
	SDKType: spec.ProviderSDKTypeOpenAIResponses,
	Origin:  spec.DefaultOpenAIOrigin,
	// Only used when Origin is overridden; kept here for clarity.
	ChatCompletionPathPrefix: "/v1/responses",
	APIKeyHeaderKey:          spec.DefaultAuthorizationHeaderKey,
})
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding OpenAI Responses provider:", err)
	return
}

apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
	fmt.Fprintln(os.Stderr, "OPENAI_API_KEY not set; skipping live OpenAI Responses call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, "openai-responses", apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
	return
}

req := &spec.FetchCompletionRequest{
	ModelParam: spec.ModelParam{
		Name:            "gpt-5-mini",
		Stream:          false,
		MaxPromptLength: 4096,
		MaxOutputLength: 256,
		SystemPrompt:    "You are a concise assistant.",
		Reasoning: &spec.ReasoningParam{
			Type:  spec.ReasoningTypeSingleWithLevels,
			Level: spec.ReasoningLevelLow,
		},
	},
	Inputs: []spec.InputUnion{
		{
			Kind: spec.InputKindInputMessage,
			InputMessage: &spec.InputOutputContent{
				Role: spec.RoleUser,
				Contents: []spec.InputOutputContentItemUnion{
					{
						Kind: spec.ContentItemKindText,
						TextItem: &spec.ContentItemText{
							Text: "Explain the difference between goroutines and OS threads in 2–3 sentences.",
						},
					},
				},
			},
		},
	},
}

resp, err := ps.FetchCompletion(
	ctx,
	"openai-responses",
	req,
	&spec.FetchCompletionOptions{CompletionKey: "gpt5mini"},
)
if err != nil {
	fmt.Fprintln(os.Stderr, "FetchCompletion error:", err)
	if resp != nil && resp.Error != nil {
		fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
	}
	return
}

for _, out := range resp.Outputs {
	if out.Kind != spec.OutputKindOutputMessage || out.OutputMessage == nil {
		continue
	}
	for _, c := range out.OutputMessage.Contents {
		if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
			fmt.Fprintln(os.Stderr, "OpenAI Responses assistant:", c.TextItem.Text)
		}
	}
}

fmt.Println("OK")
Output:
OK
Example (OpenAIResponses_toolsAndAttachments)

Example_openAIResponses_toolsAndAttachments demonstrates a more advanced Responses call that:

  • defines function and web-search tools,
  • sends text + image + file as input content,
  • enables streaming of both text and reasoning.

The example only attempts a live call when OPENAI_API_KEY is set. The image/file payloads are placeholders; for a real run, replace them with valid data or URLs.

package main

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"time"

	"github.com/flexigpt/inference-go"
	"github.com/flexigpt/inference-go/spec"
)

const sendFile = true

// Example_openAIResponses_toolsAndAttachments demonstrates a more advanced
// Responses call that:
//
//   - defines function and web-search tools,
//   - sends text + image + file as input content,
//   - enables streaming of both text and reasoning.
//
// The example only attempts a live call when OPENAI_API_KEY is set. The
// image/file payloads are placeholders; for a real run, replace them with
// valid data or URLs.
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
	defer cancel()

	ps, err := newProviderSetWithDebug(slog.LevelDebug)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating ProviderSetAPI:", err)
		return
	}

	_, err = ps.AddProvider(ctx, "openai-responses-extended", &inference.AddProviderConfig{
		SDKType:                  spec.ProviderSDKTypeOpenAIResponses,
		Origin:                   spec.DefaultOpenAIOrigin,
		ChatCompletionPathPrefix: "/v1/responses",
		APIKeyHeaderKey:          spec.DefaultAuthorizationHeaderKey,
	})
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding OpenAI Responses provider:", err)
		return
	}

	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		fmt.Fprintln(os.Stderr, "OPENAI_API_KEY not set; skipping extended OpenAI Responses example")
		fmt.Println("OK")
		return
	}
	if err := ps.SetProviderAPIKey(ctx, "openai-responses-extended", apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
		return
	}

	// Tool: summarize_document(document: string, focus: string).
	summarizeTool := spec.ToolChoice{
		Type:        spec.ToolTypeFunction,
		ID:          "summarize-document",
		Name:        "summarize_document",
		Description: "Summarize a document with an optional focus.",
		Arguments: map[string]any{
			"type": "object",
			"properties": map[string]any{
				"document": map[string]any{
					"type":        "string",
					"description": "Full text of the document to summarize.",
				},
				"focus": map[string]any{
					"type":        "string",
					"description": "Optional topic to focus on.",
				},
			},
			"required":             []any{"document"},
			"additionalProperties": false,
		},
	}

	// Web search tool: used for retrieving fresh information when needed.
	webSearchTool := spec.ToolChoice{
		Type:        spec.ToolTypeWebSearch,
		ID:          "web-search",
		Name:        "web_search",
		Description: "Search the web for recent information.",
		WebSearchArguments: &spec.WebSearchToolChoiceItem{
			MaxUses:           2,
			SearchContextSize: spec.WebSearchContextSizeMedium,
			AllowedDomains:    []string{}, // any domain
			UserLocation: &spec.WebSearchToolChoiceItemUserLocation{
				City:     "San Francisco",
				Country:  "US",
				Region:   "CA",
				Timezone: "America/Los_Angeles",
			},
		},
	}

	toolChoices := []spec.ToolChoice{summarizeTool, webSearchTool}

	// Placeholder image data (not a real image). In a real application, provide a valid base64-encoded image.
	// 1x1 transparent PNG.
	fakeImageData := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

	userMessage := spec.InputOutputContent{
		Role: spec.RoleUser,
		Contents: []spec.InputOutputContentItemUnion{
			{
				Kind: spec.ContentItemKindText,
				TextItem: &spec.ContentItemText{
					Text: "This is a test. Very briefly reply with attached PDF name and data and describe the image. Use tools where appropriate. Keep the final answer short.",
				},
			},
			{
				Kind: spec.ContentItemKindImage,
				ImageItem: &spec.ContentItemImage{
					ImageMIME: spec.DefaultImageDataMIME,
					ImageData: fakeImageData,
					ImageName: "example-image",
					Detail:    spec.ImageDetailLow,
					ImageURL:  "",
					ID:        "abc",
				},
			},
		},
	}
	if sendFile {
		// Placeholder PDF URL.
		fileURL := "https://www.w3schools.com/asp/text/textfile.txt"
		userMessage.Contents = append(userMessage.Contents, spec.InputOutputContentItemUnion{
			Kind: spec.ContentItemKindFile,
			FileItem: &spec.ContentItemFile{
				FileName: "dummy",
				FileMIME: "text/plain",
				FileURL:  fileURL,
			},
		})
	}
	req := &spec.FetchCompletionRequest{
		ModelParam: spec.ModelParam{
			Name:            "gpt-5-mini",
			Stream:          true,
			MaxPromptLength: 8192,
			MaxOutputLength: 8192,
			SystemPrompt: "You are a research assistant that first uses tools " +
				"when needed, then answers succinctly.",
			Reasoning: &spec.ReasoningParam{
				Type:  spec.ReasoningTypeSingleWithLevels,
				Level: spec.ReasoningLevelMedium,
			},
			OutputParam: &spec.OutputParam{
				// Responses maps this to params.Text.format + params.Text.verbosity.
				Verbosity: func() *spec.OutputVerbosity {
					v := spec.OutputVerbosityMedium
					return &v
				}(),
				Format: &spec.OutputFormat{
					Kind: spec.OutputFormatKindJSONSchema,
					JSONSchemaParam: &spec.JSONSchemaParam{
						Name: "final_answer",
						Schema: map[string]any{
							"type": "object",
							"properties": map[string]any{
								"image_description": map[string]any{"type": "string"},
								"file_name":         map[string]any{"type": "string"},
								"answer":            map[string]any{"type": "string"},
							},
							"required":             []any{"image_description", "answer", "file_name"},
							"additionalProperties": false,
						},
						Strict: true,
					},
				},
			},
		},
		Inputs: []spec.InputUnion{
			{
				Kind:         spec.InputKindInputMessage,
				InputMessage: &userMessage,
			},
		},
		ToolChoices: toolChoices,
		ToolPolicy: &spec.ToolPolicy{
			// Demonstrates policy plumbing.
			Mode:            spec.ToolPolicyModeAuto,
			DisableParallel: true,
		},
	}

	// Stream both text and reasoning to stdout.
	opts := &spec.FetchCompletionOptions{
		StreamHandler: func(ev spec.StreamEvent) error {
			switch ev.Kind {
			case spec.StreamContentKindText:
				if ev.Text != nil {
					fmt.Fprintln(os.Stderr, ev.Text.Text)
				}
			case spec.StreamContentKindThinking:
				if ev.Thinking != nil {
					// In a real app you might log this separately; here we
					// just prefix it.
					fmt.Fprintf(os.Stderr, "\n[thinking] %s \n", ev.Thinking.Text)
				}
			}
			return nil
		},
		StreamConfig: &spec.StreamConfig{
			// Use library defaults; override here if you want.
		},
		CompletionKey: "gpt5mini",
	}

	resp, err := ps.FetchCompletion(ctx, "openai-responses-extended", req, opts)
	if err != nil {
		fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
		if resp != nil && resp.Error != nil {
			fmt.Fprintln(os.Stderr, "Provider error:", resp.Error.Message)
		}
		return
	}

	fmt.Fprintln(os.Stderr, "\n\n--- normalized outputs ---")
	for _, out := range resp.Outputs {
		switch out.Kind {
		case spec.OutputKindFunctionToolCall:
			if out.FunctionToolCall != nil {
				fmt.Fprintf(os.Stderr, "Function tool call: %s(%s)\n",
					out.FunctionToolCall.Name,
					out.FunctionToolCall.Arguments,
				)
			}
		case spec.OutputKindWebSearchToolCall:
			if out.WebSearchToolCall != nil {
				fmt.Fprintf(os.Stderr, "Web search call: %+v\n", out.WebSearchToolCall.WebSearchToolCallItems)
			}
		case spec.OutputKindOutputMessage:
			if out.OutputMessage != nil {
				for _, c := range out.OutputMessage.Contents {
					if c.Kind == spec.ContentItemKindText && c.TextItem != nil {
						fmt.Fprintln(os.Stderr, "Final answer:", c.TextItem.Text)
					}
				}
			}
		case spec.OutputKindReasoningMessage:
			if out.ReasoningMessage != nil && len(out.ReasoningMessage.Summary) > 0 {
				fmt.Fprintln(os.Stderr, "Reasoning summary:", out.ReasoningMessage.Summary[0])
			}
		default:
		}
	}

	fmt.Println("OK")
}
Output:
OK

Jump to

Keyboard shortcuts

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