integration

package
v0.22.5 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 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)
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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderAnthropic,
	modelpreset.PresetClaudeHaiku45,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic preset 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, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

modelParam := mp.ModelParam
modelParam.Stream = false
modelParam.MaxPromptLength = min(modelParam.MaxPromptLength, 4096)
modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 2048)
modelParam.SystemPrompt = "You are a concise, helpful assistant."

opts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: modelParam,
	Inputs: []spec.InputUnion{
		newUserTextInput("Say hello from Anthropic in one short sentence."),
	},
}, opts)
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
}

fmt.Fprintln(os.Stderr, "Anthropic assistant:", responseText(resp))
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

For Anthropic, the tool_result must immediately follow the assistant tool-use turn as the next user turn. The Anthropic adapter normalizes this ordering.

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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderAnthropic,
	modelpreset.PresetClaudeSonnet46,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic preset 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, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

tool := newEchoToolChoice(anthropicEchoToolID, anthropicEchoToolName)

initialUser := newUserTextInput(
	fmt.Sprintf(
		`Use the %s tool with text %q. Do not answer yet; just call the tool.`,
		anthropicEchoToolName,
		"anthropic tool round trip",
	),
)

firstParam := mp.ModelParam
firstParam.MaxOutputLength = min(firstParam.MaxOutputLength, 512)
firstParam.Reasoning = nil
firstParam.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.",
}, " ")

firstOpts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

firstResp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam:  firstParam,
	Inputs:      []spec.InputUnion{initialUser},
	ToolChoices: []spec.ToolChoice{tool},
	ToolPolicy: &spec.ToolPolicy{
		Mode: spec.ToolPolicyModeTool,
		AllowedTools: []spec.AllowedTool{
			{ToolChoiceID: tool.ID},
		},
		DisableParallel: true,
	},
}, firstOpts)
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))

secondParam := mp.ModelParam
secondParam.MaxOutputLength = min(secondParam.MaxOutputLength, 2048)
secondParam.SystemPrompt = strings.Join([]string{
	"You have now received the tool result.",
	"Answer briefly in plain text.",
	"Do not call any tool again.",
}, " ")

secondOpts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

secondResp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: secondParam,
	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,
	},
}, secondOpts)
if err != nil {
	fmt.Fprintln(os.Stderr, "second FetchCompletion error:", err)
	return
}

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

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

Example_anthropic_toolsAndThinkingStreaming demonstrates:

  • catalog-based Anthropic provider setup
  • preset model defaults
  • preset capability resolver
  • 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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderAnthropic,
	modelpreset.PresetClaudeSonnet46,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Anthropic preset 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, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Anthropic API key:", err)
	return
}

modelParam := mp.ModelParam
modelParam.Stream = true
modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 1024)
modelParam.SystemPrompt = "Use tools when helpful. Keep the final answer short."
modelParam.Reasoning = &spec.ReasoningParam{
	Type:  spec.ReasoningTypeSingleWithLevels,
	Level: spec.ReasoningLevelMedium,
}
modelParam.OutputParam = &spec.OutputParam{
	Format: &spec.OutputFormat{
		Kind: spec.OutputFormatKindJSONSchema,
		JSONSchemaParam: &spec.JSONSchemaParam{
			Name: toolJSONSchemaName,
			Schema: map[string]any{
				toolJSONKeyType: toolJSONValueObject,
				toolJSONKeyProperties: map[string]any{
					"summary": map[string]any{
						toolJSONKeyType: toolJSONValueString,
					},
					"source_used": map[string]any{
						toolJSONKeyType: toolJSONValueBoolean,
					},
				},
				toolJSONKeyRequired:             []any{"summary", "source_used"},
				toolJSONKeyAdditionalProperties: false,
			},
		},
	},
}

tools := []spec.ToolChoice{
	{
		Type:        spec.ToolTypeFunction,
		ID:          anthropicExtractKeyPointsToolID,
		Name:        anthropicExtractKeyPointsToolName,
		Description: anthropicExtractKeyPointsToolDescription,
		Arguments: map[string]any{
			toolJSONKeyType: toolJSONValueObject,
			toolJSONKeyProperties: map[string]any{
				toolJSONKeyText: map[string]any{toolJSONKeyType: toolJSONValueString},
			},
			toolJSONKeyRequired:             []any{toolJSONKeyText},
			toolJSONKeyAdditionalProperties: false,
		},
	},
	{
		Type: spec.ToolTypeWebSearch,
		ID:   anthropicWebSearchToolID,
		Name: spec.DefaultWebSearchToolName,
		WebSearchArguments: &spec.WebSearchToolChoiceItem{
			MaxUses:           1,
			SearchContextSize: spec.WebSearchContextSizeMedium,
		},
	},
}

opts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}
opts.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
}

_, err = ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: modelParam,
	Inputs: []spec.InputUnion{
		newUserTextInput("What is the latest stable Go version? If unknown, say so."),
	},
	ToolChoices: tools,
	ToolPolicy: &spec.ToolPolicy{
		Mode:            spec.ToolPolicyModeAuto,
		DisableParallel: true,
	},
}, opts)
if err != nil {
	fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
	return
}

fmt.Println("OK")
Output:
OK
Example (GoogleGenerateContent_basicConversation)
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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderGoogleGemini,
	modelpreset.PresetGemini25Flash,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding Google Gemini preset 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 Gemini call")
	fmt.Println("OK")
	return
}
if err := ps.SetProviderAPIKey(ctx, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting Google Gemini API key:", err)
	return
}

modelParam := mp.ModelParam
modelParam.Stream = false
modelParam.MaxPromptLength = min(modelParam.MaxPromptLength, 4096)
modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 2048)
modelParam.SystemPrompt = "You are a concise, helpful assistant."

opts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: modelParam,
	Inputs: []spec.InputUnion{
		newUserTextInput("Say hello from Google Gemini in one short sentence."),
	},
}, opts)
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
}

fmt.Fprintln(os.Stderr, "Gemini assistant:", responseText(resp))
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/modelpreset"
	"github.com/flexigpt/inference-go/spec"
)

const (
	googleToolsRoundTripToolID   = "echo-tool"
	googleToolsRoundTripToolName = "echo_text"
)

// 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(), 600*time.Second)
	defer cancel()

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

	pp, mp, err := addCatalogModelProvider(
		ctx,
		ps,
		modelpreset.ProviderGoogleGemini,
		modelpreset.PresetGemini25Flash,
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding Google Gemini preset 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 Gemini tool example")
		fmt.Println("OK")
		return
	}
	if err := ps.SetProviderAPIKey(ctx, pp.Name, apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting Google Gemini API key:", err)
		return
	}

	tool := newEchoToolChoice(googleToolsRoundTripToolID, googleToolsRoundTripToolName)

	initialUser := newUserTextInput(
		fmt.Sprintf(
			`Think briefly, then call the %s tool with text %q. Do not answer until after the tool result.`,
			googleToolsRoundTripToolName,
			"google function tool round trip",
		),
	)

	firstParam := mp.ModelParam
	firstParam.Stream = true
	firstParam.MaxOutputLength = min(firstParam.MaxOutputLength, 4096)
	firstParam.Reasoning = &spec.ReasoningParam{
		Type:   spec.ReasoningTypeHybridWithTokens,
		Tokens: 2048,
	}
	firstParam.SystemPrompt = "You are validating a Gemini function tool round trip. " +
		"When the tool is forced, emit only the tool call in the first response."

	firstOpts, err := presetFetchOptions(ctx, ps, pp, mp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
		return
	}
	firstOpts.StreamHandler = func(ev spec.StreamEvent) error {
		switch ev.Kind {
		case spec.StreamContentKindThinking:
			if ev.Thinking != nil {
				fmt.Fprintf(os.Stderr, "\n[thinking 1] %s\n", ev.Thinking.Text)
			}
		case spec.StreamContentKindText:
			if ev.Text != nil {
				fmt.Fprintf(os.Stderr, "\n[text 1] %s\n", ev.Text.Text)
			}
		}
		return nil
	}

	firstResp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
		ModelParam:  firstParam,
		Inputs:      []spec.InputUnion{initialUser},
		ToolChoices: []spec.ToolChoice{tool},
		ToolPolicy: &spec.ToolPolicy{
			Mode: spec.ToolPolicyModeTool,
			AllowedTools: []spec.AllowedTool{
				{ToolChoiceID: tool.ID},
			},
			DisableParallel: true,
		},
	}, firstOpts)
	if err != nil {
		fmt.Fprintln(os.Stderr, "first FetchCompletion error:", err)
		return
	}

	if firstText := responseText(firstResp); 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, "tool 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, "tool 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,
	})

	secondParam := mp.ModelParam
	secondParam.Stream = true
	secondParam.MaxOutputLength = min(secondParam.MaxOutputLength, 4096)
	secondParam.Reasoning = &spec.ReasoningParam{
		Type:   spec.ReasoningTypeHybridWithTokens,
		Tokens: 2048,
	}
	secondParam.SystemPrompt = "You have now received the tool result. " +
		"Answer briefly and do not call any tool again."

	secondOpts, err := presetFetchOptions(ctx, ps, pp, mp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
		return
	}
	secondOpts.StreamHandler = func(ev spec.StreamEvent) error {
		switch ev.Kind {
		case spec.StreamContentKindThinking:
			if ev.Thinking != nil {
				fmt.Fprintf(os.Stderr, "\n[thinking 2] %s\n", ev.Thinking.Text)
			}
		case spec.StreamContentKindText:
			if ev.Text != nil {
				fmt.Fprintf(os.Stderr, "\n[text 2] %s\n", ev.Text.Text)
			}
		}
		return nil
	}

	secondResp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
		ModelParam:  secondParam,
		Inputs:      append(history, newUserTextInput("Now finish in one short sentence.")),
		ToolChoices: []spec.ToolChoice{tool},
		ToolPolicy: &spec.ToolPolicy{
			Mode: spec.ToolPolicyModeNone,
		},
	}, secondOpts)
	if err != nil {
		fmt.Fprintln(os.Stderr, "second FetchCompletion error:", err)
		return
	}

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

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

Example_googleGenerateContent_webSearchAndThinkingStreaming demonstrates:

  • catalog-based Google Gemini provider setup
  • preset capability resolver
  • server-side Google web search grounding
  • streaming text + thinking
  • normalized webSearch outputs synthesized from grounding metadata

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

package main

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

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

const (
	googleWebSearchToolID          = "google-web-search"
	googleWebSearchToolDescription = "Search the web for recent information."
)

// Example_googleGenerateContent_webSearchAndThinkingStreaming demonstrates:
//
//   - catalog-based Google Gemini provider setup
//   - preset capability resolver
//   - server-side Google web search grounding
//   - streaming text + thinking
//   - normalized webSearch outputs synthesized from grounding metadata
//
// For Gemini GenerateContent, web search is server-side grounding, not a
// client-side tool-output round trip like function tools.
func main() {
	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
	}

	pp, mp, err := addCatalogModelProvider(
		ctx,
		ps,
		modelpreset.ProviderGoogleGemini,
		modelpreset.PresetGemini35Flash,
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding Google Gemini preset 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 Gemini web-search example")
		fmt.Println("OK")
		return
	}
	if err := ps.SetProviderAPIKey(ctx, pp.Name, apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting Google Gemini API key:", err)
		return
	}

	modelParam := mp.ModelParam
	modelParam.Stream = true
	modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 512)
	modelParam.SystemPrompt = "Use web search when helpful. Keep the final answer short. " +
		"If you are unsure, say so plainly."
	modelParam.Reasoning = &spec.ReasoningParam{
		Type:   spec.ReasoningTypeHybridWithTokens,
		Tokens: 1024,
	}

	webSearchTool := spec.ToolChoice{
		Type:               spec.ToolTypeWebSearch,
		ID:                 googleWebSearchToolID,
		Name:               spec.DefaultWebSearchToolName,
		Description:        googleWebSearchToolDescription,
		WebSearchArguments: &spec.WebSearchToolChoiceItem{
			// Intentionally minimal. The Gemini adapter exposes web search
			// primarily as an enable/disable grounding capability.
		},
	}

	opts, err := presetFetchOptions(ctx, ps, pp, mp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
		return
	}
	opts.StreamHandler = func(ev spec.StreamEvent) error {
		switch ev.Kind {
		case spec.StreamContentKindThinking:
			if ev.Thinking != nil {
				fmt.Fprintf(os.Stderr, "\n[thinking] %s\n", ev.Thinking.Text)
			}
		case spec.StreamContentKindText:
			if ev.Text != nil {
				fmt.Fprintf(os.Stderr, "\n[text] %s\n", ev.Text.Text)
			}
		}
		return nil
	}

	resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
		ModelParam: modelParam,
		Inputs: []spec.InputUnion{
			newUserTextInput(
				"What is the latest stable Go release? If unknown, say unknown. Then list notable features and why they matter.",
			),
		},
		ToolChoices: []spec.ToolChoice{webSearchTool},
		ToolPolicy: &spec.ToolPolicy{
			Mode: spec.ToolPolicyModeAuto,
		},
	}, 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--- 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)
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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderOpenAIChat,
	modelpreset.PresetGPT41Mini,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding OpenAI Chat preset 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, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
	return
}

modelParam := mp.ModelParam
modelParam.Stream = false
modelParam.MaxPromptLength = min(modelParam.MaxPromptLength, 4096)
modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 4096)
modelParam.SystemPrompt = "You are a concise assistant."

opts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: modelParam,
	Inputs: []spec.InputUnion{
		newUserTextInput("Say hello from OpenAI Chat Completions in one short sentence."),
	},
}, opts)
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
}

fmt.Fprintln(os.Stderr, "OpenAI Chat assistant:", responseText(resp))
fmt.Println("OK")
Output:
OK
Example (OpenAIChat_toolsAndJSONSchema)

Example_openAIChat_toolsAndJSONSchema demonstrates:

  • catalog-based OpenAI Chat provider setup
  • preset model defaults
  • preset capability resolver
  • streaming text
  • JSON schema output
  • function tools
package main

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

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

const (
	openAIChatMathToolID          = "math"
	openAIChatMathToolName        = "multiply"
	openAIChatMathToolDescription = "Multiply two integers."
)

// Example_openAIChat_toolsAndJSONSchema demonstrates:
//
//   - catalog-based OpenAI Chat provider setup
//   - preset model defaults
//   - preset capability resolver
//   - streaming text
//   - JSON schema output
//   - function tools
func main() {
	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
	}

	pp, mp, err := addCatalogModelProvider(
		ctx,
		ps,
		modelpreset.ProviderOpenAIChat,
		modelpreset.PresetGPT41,
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding OpenAI Chat preset 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, pp.Name, apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
		return
	}

	modelParam := mp.ModelParam
	modelParam.Stream = true
	modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 1024)
	modelParam.SystemPrompt = "Answer directly."
	modelParam.OutputParam = &spec.OutputParam{
		Format: &spec.OutputFormat{
			Kind: spec.OutputFormatKindJSONSchema,
			JSONSchemaParam: &spec.JSONSchemaParam{
				Name: "result",
				Schema: map[string]any{
					toolJSONKeyType: toolJSONValueObject,
					toolJSONKeyProperties: map[string]any{
						toolJSONSchemaName: map[string]any{toolJSONKeyType: toolJSONValueString},
					},
					toolJSONKeyRequired:             []any{toolJSONSchemaName},
					toolJSONKeyAdditionalProperties: false,
				},
				Strict: true,
			},
		},
	}

	tools := []spec.ToolChoice{
		{
			Type:        spec.ToolTypeFunction,
			ID:          openAIChatMathToolID,
			Name:        openAIChatMathToolName,
			Description: openAIChatMathToolDescription,
			Arguments: map[string]any{
				toolJSONKeyType: toolJSONValueObject,
				toolJSONKeyProperties: map[string]any{
					"a": map[string]any{toolJSONKeyType: "integer"},
					"b": map[string]any{toolJSONKeyType: "integer"},
				},
				toolJSONKeyRequired:             []any{"a", "b"},
				toolJSONKeyAdditionalProperties: false,
			},
		},
	}

	opts, err := presetFetchOptions(ctx, ps, pp, mp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
		return
	}
	opts.StreamHandler = func(ev spec.StreamEvent) error {
		if ev.Kind == spec.StreamContentKindText && ev.Text != nil {
			fmt.Fprint(os.Stderr, ev.Text.Text)
		}
		return nil
	}

	_, err = ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
		ModelParam: modelParam,
		Inputs: []spec.InputUnion{
			newUserTextInput("What is 6*7? Use the multiply tool if useful."),
		},
		ToolChoices: tools,
		ToolPolicy: &spec.ToolPolicy{
			Mode:            spec.ToolPolicyModeAuto,
			DisableParallel: true,
		},
	}, opts)
	if err != nil {
		fmt.Fprintln(os.Stderr, "\nFetchCompletion error:", err)
		return
	}

	fmt.Println("OK")
}
Output:
OK
Example (OpenAIResponses_basicConversation)
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
}

pp, mp, err := addCatalogModelProvider(
	ctx,
	ps,
	modelpreset.ProviderOpenAIResponses,
	modelpreset.PresetGPT5Mini,
)
if err != nil {
	fmt.Fprintln(os.Stderr, "error adding OpenAI Responses preset 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, pp.Name, apiKey); err != nil {
	fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
	return
}

modelParam := mp.ModelParam
modelParam.Stream = false
modelParam.MaxPromptLength = min(modelParam.MaxPromptLength, 4096)
modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 4096)
modelParam.SystemPrompt = "You are a concise assistant."
modelParam.Reasoning = &spec.ReasoningParam{
	Type:  spec.ReasoningTypeSingleWithLevels,
	Level: spec.ReasoningLevelLow,
}

opts, err := presetFetchOptions(ctx, ps, pp, mp)
if err != nil {
	fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
	return
}

resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
	ModelParam: modelParam,
	Inputs: []spec.InputUnion{
		newUserTextInput("Explain the difference between goroutines and OS threads in 2-3 sentences."),
	},
}, opts)
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
}

fmt.Fprintln(os.Stderr, "OpenAI Responses assistant:", responseText(resp))
fmt.Println("OK")
Output:
OK
Example (OpenAIResponses_toolsAndAttachments)

Example_openAIResponses_toolsAndAttachments demonstrates a more advanced Responses call:

  • catalog-based OpenAI Responses provider setup
  • preset model defaults
  • preset capability resolver
  • function and web-search tools
  • text + image + file input content
  • streaming text and reasoning

The example only attempts a live call when OPENAI_API_KEY is set.

package main

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

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

const (
	openAIResponsesSummarizeToolID          = "summarize-document"
	openAIResponsesSummarizeToolName        = "summarize_document"
	openAIResponsesSummarizeToolDescription = "Summarize a document with an optional focus."

	openAIResponsesWebSearchToolID          = "web-search"
	openAIResponsesWebSearchToolName        = "web_search"
	openAIResponsesWebSearchToolDescription = "Search the web for recent information."
)

const sendOpenAIResponsesExampleFile = true

// Example_openAIResponses_toolsAndAttachments demonstrates a more advanced
// Responses call:
//
//   - catalog-based OpenAI Responses provider setup
//   - preset model defaults
//   - preset capability resolver
//   - function and web-search tools
//   - text + image + file input content
//   - streaming text and reasoning
//
// The example only attempts a live call when OPENAI_API_KEY is set.
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
	}

	pp, mp, err := addCatalogModelProvider(
		ctx,
		ps,
		modelpreset.ProviderOpenAIResponses,
		modelpreset.PresetGPT5Mini,
	)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error adding OpenAI Responses preset 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, pp.Name, apiKey); err != nil {
		fmt.Fprintln(os.Stderr, "error setting OpenAI API key:", err)
		return
	}

	summarizeTool := spec.ToolChoice{
		Type:        spec.ToolTypeFunction,
		ID:          openAIResponsesSummarizeToolID,
		Name:        openAIResponsesSummarizeToolName,
		Description: openAIResponsesSummarizeToolDescription,
		Arguments: map[string]any{
			toolJSONKeyType: toolJSONValueObject,
			toolJSONKeyProperties: map[string]any{
				"document": map[string]any{
					toolJSONKeyType: toolJSONValueString,
					"description":   "Full text of the document to summarize.",
				},
				"focus": map[string]any{
					toolJSONKeyType: toolJSONValueString,
					"description":   "Optional topic to focus on.",
				},
			},
			toolJSONKeyRequired:             []any{"document"},
			toolJSONKeyAdditionalProperties: false,
		},
	}

	webSearchTool := spec.ToolChoice{
		Type:        spec.ToolTypeWebSearch,
		ID:          openAIResponsesWebSearchToolID,
		Name:        openAIResponsesWebSearchToolName,
		Description: openAIResponsesWebSearchToolDescription,
		WebSearchArguments: &spec.WebSearchToolChoiceItem{
			MaxUses:           2,
			SearchContextSize: spec.WebSearchContextSizeMedium,
			UserLocation: &spec.WebSearchToolChoiceItemUserLocation{
				City:     "San Francisco",
				Country:  "US",
				Region:   "CA",
				Timezone: "America/Los_Angeles",
			},
		},
	}

	// 1x1 transparent PNG.
	fakeImageData := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

	userMessage := spec.InputOutputContent{
		Role: spec.RoleUser,
		Contents: []spec.InputOutputContentItemUnion{
			{
				Kind: spec.ContentItemKindText,
				TextItem: &spec.ContentItemText{
					Text: "Briefly describe the image and attached file. 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,
				},
			},
		},
	}

	if sendOpenAIResponsesExampleFile {
		userMessage.Contents = append(userMessage.Contents, spec.InputOutputContentItemUnion{
			Kind: spec.ContentItemKindFile,
			FileItem: &spec.ContentItemFile{
				FileName: "example.txt",
				FileMIME: "text/plain",
				FileURL:  "https://www.w3schools.com/asp/text/textfile.txt",
			},
		})
	}

	modelParam := mp.ModelParam
	modelParam.Stream = true
	modelParam.MaxPromptLength = min(modelParam.MaxPromptLength, 8192)
	modelParam.MaxOutputLength = min(modelParam.MaxOutputLength, 8192)
	modelParam.SystemPrompt = "You are a research assistant that first uses tools when needed, then answers succinctly."
	modelParam.Reasoning = &spec.ReasoningParam{
		Type:  spec.ReasoningTypeSingleWithLevels,
		Level: spec.ReasoningLevelMedium,
	}
	modelParam.OutputParam = &spec.OutputParam{
		Verbosity: new(spec.OutputVerbosityMedium),
		Format: &spec.OutputFormat{
			Kind: spec.OutputFormatKindJSONSchema,
			JSONSchemaParam: &spec.JSONSchemaParam{
				Name: "final_answer",
				Schema: map[string]any{
					toolJSONKeyType: toolJSONValueObject,
					toolJSONKeyProperties: map[string]any{
						"image_description": map[string]any{toolJSONKeyType: toolJSONValueString},
						"file_name":         map[string]any{toolJSONKeyType: toolJSONValueString},
						"answer":            map[string]any{toolJSONKeyType: toolJSONValueString},
					},
					toolJSONKeyRequired: []any{
						"image_description",
						"file_name",
						"answer",
					},
					toolJSONKeyAdditionalProperties: false,
				},
				Strict: true,
			},
		},
	}

	opts, err := presetFetchOptions(ctx, ps, pp, mp)
	if err != nil {
		fmt.Fprintln(os.Stderr, "error creating preset capability resolver:", err)
		return
	}
	opts.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 {
				fmt.Fprintf(os.Stderr, "\n[thinking] %s\n", ev.Thinking.Text)
			}
		}
		return nil
	}
	opts.StreamConfig = &spec.StreamConfig{}

	resp, err := ps.FetchCompletion(ctx, pp.Name, &spec.FetchCompletionRequest{
		ModelParam: modelParam,
		Inputs: []spec.InputUnion{
			{
				Kind:         spec.InputKindInputMessage,
				InputMessage: &userMessage,
			},
		},
		ToolChoices: []spec.ToolChoice{summarizeTool, webSearchTool},
		ToolPolicy: &spec.ToolPolicy{
			Mode:            spec.ToolPolicyModeAuto,
			DisableParallel: true,
		},
	}, 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--- 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