Skip to content

Add structured output support to Ollama plugin#4884

Open
HVbajoria wants to merge 9 commits intofirebase:mainfrom
HVbajoria:structured-output-format-ollama
Open

Add structured output support to Ollama plugin#4884
HVbajoria wants to merge 9 commits intofirebase:mainfrom
HVbajoria:structured-output-format-ollama

Conversation

@HVbajoria
Copy link

Add Structured Output Support to Ollama Plugin (Go)

Fixes #4788

Overview

This PR adds structured output support to the Ollama plugin in Genkit Go, enabling developers to enforce JSON schemas on model responses. This leverages Ollama's native structured output capability via the format parameter as documented at https://docs.ollama.com/capabilities/structured-outputs.

Changes

Core Implementation

Modified go/plugins/ollama/ollama.go:

  • Added schema detection logic in generator.generate() method
  • Implemented JSON schema serialization with error handling
  • Set format parameter for both chat and generate endpoints
  • Supports both schema-based and schema-less JSON output modes
  • Maintains full backward compatibility with existing behavior

Key Features:

  • When ModelRequest.Output.Schema is provided, the schema is serialized to JSON and passed to Ollama's format parameter
  • When ModelRequest.Output.Format is set to "json" without a schema, enables generic JSON output mode
  • Requests without schemas continue to work unchanged (backward compatible)
  • Works with both streaming and non-streaming requests

Testing

Modified go/plugins/ollama/ollama_test.go:

  • Added comprehensive unit tests for schema serialization and format parameter setting
  • Added property-based tests using gopter library (100+ iterations per property)
  • Tests validate 10 correctness properties covering all requirements
  • Tests verify backward compatibility and error handling

Added go/plugins/ollama/ollama_integration_test.go:

  • Integration tests with live Ollama instance
  • Tests both chat and generate endpoints with structured output
  • Tests streaming structured output
  • Tests schema-less JSON mode
  • Tests error scenarios

Documentation

Added go/plugins/ollama/README.md:

  • Comprehensive documentation of structured output feature
  • Usage examples for both schema-based and schema-less modes
  • Configuration guide and limitations

Added go/plugins/ollama/INTEGRATION_TESTS.md:

  • Guide for running integration tests
  • Prerequisites and setup instructions
  • Expected behavior documentation

Examples

Added go/samples/ollama-structured/main.go:

  • Demonstrates schema-based structured output
  • Shows how to define Go structs and convert to JSON schemas
  • Example of parsing structured responses

Added go/samples/ollama-json-mode/main.go:

  • Demonstrates schema-less JSON output mode
  • Shows generic JSON response handling

Dependencies

Modified go/go.mod and go/go.sum:

  • Added github.com/leanovate/gopter v0.2.11 for property-based testing

Testing

All tests pass successfully:

# Unit tests
cd go/plugins/ollama
go test -v

# Integration tests (requires running Ollama instance)
cd go/plugins/ollama
go test -v -tags=integration

# Run examples
cd go/samples/ollama-structured
go run main.go

cd go/samples/ollama-json-mode
go run main.go

Backward Compatibility

This implementation maintains full backward compatibility:

  • Requests without Output config work unchanged
  • Requests with empty Schema work unchanged
  • No breaking changes to existing API
  • All existing tests continue to pass

Usage Example

import (
    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/plugins/ollama"
)

// Define a schema
schema := map[string]any{
    "type": "object",
    "properties": map[string]any{
        "name": map[string]any{"type": "string"},
        "age":  map[string]any{"type": "number"},
    },
    "required": []string{"name", "age"},
}

// Make a request with structured output
response, err := ai.Generate(ctx, model, ai.WithTextPrompt("Tell me about a person"),
    ai.WithOutputConfig(&ai.ModelOutputConfig{
        Schema: schema,
    }))

Checklist

  • Code follows project style guidelines
  • All tests pass
  • Added comprehensive unit tests
  • Added property-based tests
  • Added integration tests
  • Added documentation
  • Added usage examples
  • Maintains backward compatibility
  • Signed CLA (if required)

Related Documentation

@HVbajoria HVbajoria requested a review from apascal07 as a code owner March 4, 2026 00:22
@github-actions github-actions bot added docs Improvements or additions to documentation go labels Mar 4, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Ollama plugin for Genkit Go by introducing robust structured output capabilities. This allows models to return responses strictly conforming to a provided JSON schema, greatly improving the reliability and ease of parsing model outputs. The changes are thoroughly tested and documented, providing clear guidance and examples for developers to leverage this new feature effectively.

Highlights

  • Structured Output Support: The Ollama plugin now supports structured output, allowing developers to enforce JSON schemas on model responses using Ollama's native format parameter.
  • Flexible JSON Output: Both schema-based and schema-less JSON output modes are supported, providing flexibility for strict data requirements or generic JSON responses.
  • Comprehensive Testing: New unit tests, property-based tests (using gopter), and integration tests (requiring a live Ollama instance) have been added to ensure correctness, backward compatibility, and error handling.
  • Enhanced Documentation and Examples: New documentation and usage examples have been added to guide developers on implementing structured output and schema-less JSON modes.
  • Backward Compatibility: The implementation maintains full backward compatibility, ensuring existing requests without output configurations continue to function as before.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • go/go.mod
    • Added github.com/leanovate/gopter for property-based testing.
  • go/go.sum
    • Updated Go module checksums to reflect new and updated dependencies.
  • go/plugins/ollama/INTEGRATION_TESTS.md
    • Documented how to run Ollama integration tests, including prerequisites, environment variables, test coverage, troubleshooting, and CI/CD integration.
  • go/plugins/ollama/README.md
    • Updated usage instructions to include structured output and schema-less JSON mode, along with new examples and limitations.
  • go/plugins/ollama/ollama.go
    • Implemented logic to detect ModelRequest.Output.Schema or ModelRequest.Output.Format and set the Format parameter in Ollama API requests for structured output.
  • go/plugins/ollama/ollama_integration_test.go
    • Added comprehensive integration tests for schema-based chat/generate, schema-less JSON, streaming with schemas, and error handling against a live Ollama instance.
  • go/plugins/ollama/ollama_test.go
    • Expanded unit tests to cover schema detection, serialization, and backward compatibility, and introduced property-based tests for various structured output scenarios.
  • go/samples/ollama-json-mode/main.go
    • Provided a new example demonstrating how to request and parse generic JSON output from Ollama models.
  • go/samples/ollama-structured/main.go
    • Provided a new example demonstrating how to define a Go struct, convert it to a JSON schema, and use it to enforce structured output from Ollama models.
Activity
  • All unit tests, property-based tests, and integration tests have passed successfully.
  • New documentation files (INTEGRATION_TESTS.md, README.md) and example files (ollama-json-mode/main.go, ollama-structured/main.go) were added.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces structured output support to the Ollama plugin, a valuable feature that leverages Ollama's native format parameter for JSON output. However, it also introduces a critical Denial of Service (DoS) vulnerability. The current implementation uses incorrect type assertions, attempting to assert interface variables containing value types as pointers, which will cause an immediate runtime panic in Go and crash the request handler when structured output is requested. While the feature is well-tested with unit, property-based, and integration tests, and includes clear documentation, the type assertion issue needs to be addressed to prevent the DoS vulnerability. Additionally, there is a suggestion for code simplification in the tests.

@HVbajoria
Copy link
Author

/gemini review

@HVbajoria
Copy link
Author

HI @apascal07 ,

Tried resolving the issues as marked by Gemini.
Could you please help with a re-run.

Happy to learn while innovating.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces structured output support for the Ollama plugin, a valuable feature that includes core logic changes, extensive unit and integration tests, new examples, and documentation. However, a critical security concern has been identified: the JSON schemas are being double-encoded as a string when passed to the Ollama API due to incorrect type definitions in the request structs. This issue will likely cause Ollama to ignore schema enforcement, bypassing the intended security and integrity benefits of structured output. Additionally, my review suggests improving test quality by addressing duplicated logic and fixing a compilation issue in the integration tests, along with a minor refactoring in the core logic to enhance clarity and maintainability.

Comment on lines +292 to +303
schemaJSON, err := json.Marshal(input.Output.Schema)
if err != nil {
// Return error before making HTTP request if schema serialization fails
return nil, fmt.Errorf("failed to serialize output schema: %v", err)
}

// Set the format field on the appropriate request type (chat or generate endpoint)
if isChatModel {
payload.(*ollamaChatRequest).Format = string(schemaJSON)
} else {
payload.(*ollamaModelRequest).Format = string(schemaJSON)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

This section of code has a critical issue: the JSON schema is being double-encoded when passed to the Ollama API, which will likely cause Ollama to ignore schema enforcement, bypassing the intended security and integrity benefits of structured output. The Format field in ollamaChatRequest and ollamaModelRequest (lines 143 and 153) is currently a string. When input.Output.Schema is marshaled to a JSON string (line 292) and assigned to this field, it results in double-encoding (e.g., "format": "{\"type\":\"object\",...}") when the request payload is marshaled for the API call. Ollama expects a JSON Schema object, not an escaped string.

To resolve this, the Format field in both ollamaChatRequest and ollamaModelRequest structs should be changed to any or json.RawMessage. Then, assign the input.Output.Schema map directly to the Format field instead of marshaling it to a string first.

Additionally, the logic for setting the Format field is currently repeated for both chat and generate models. This can be refactored for better clarity and maintainability once the type issue is addressed.

HVbajoria and others added 2 commits March 4, 2026 06:27
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@HVbajoria
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds structured output support to the Ollama plugin, which is a great enhancement. The implementation is well-structured, and I appreciate the comprehensive set of unit, property-based, and integration tests, as well as the new documentation and examples. I have a couple of suggestions: one critical fix for a missing import in the integration tests that causes a compilation error, and a minor refactoring suggestion to improve code clarity and reduce duplication in the payload building logic. Overall, this is a high-quality contribution.

HVbajoria and others added 2 commits March 4, 2026 17:50
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@HVbajoria
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds structured output support to the Ollama plugin, which is a great enhancement. The implementation is well-structured, with the logic for payload creation refactored into a separate buildPayload function. The addition of comprehensive unit, property-based, and integration tests is excellent and ensures the new functionality is robust and backward compatible. The new documentation and examples are also clear and helpful. I've found a couple of minor issues: a formatting problem in ollama.go and a missing import in the new integration test file that will cause a compilation failure. Once these are addressed, this PR will be in great shape.

HVbajoria and others added 2 commits March 4, 2026 18:09
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@HVbajoria
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is a high-quality pull request that adds significant value by implementing structured output support for the Ollama plugin. The code is well-structured, particularly with the refactoring of payload creation into its own function. The addition of comprehensive documentation, examples, and an exceptionally thorough test suite—including unit, integration, and property-based tests—is commendable and greatly enhances the robustness and usability of the new feature. My feedback includes a couple of minor suggestions to further improve code robustness in the implementation and tests. Overall, excellent work.

@HVbajoria
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces structured output support for the Ollama plugin, which is a significant and valuable feature. The implementation is well-structured, with a clear separation of concerns in building the request payload. The addition of comprehensive unit, property-based, and integration tests ensures the feature is robust and backward compatible. The documentation and examples are thorough and will be very helpful for users. I have one suggestion to improve the reliability of an integration test case.

Comment on lines +602 to +644
t.Run("Ollama API error response", func(t *testing.T) {
// Use a valid model but with a very short timeout to potentially trigger errors
modelName := getChatModel()
checkModelAvailable(t, serverAddress, modelName)

shortTimeoutOllama := &Ollama{
ServerAddress: serverAddress,
Timeout: 1, // 1 second timeout
}
shortTimeoutOllama.Init(ctx)

model := shortTimeoutOllama.DefineModel(g, ModelDefinition{
Name: modelName,
Type: "chat",
}, nil)

// Make a request that might timeout
request := &ai.ModelRequest{
Messages: []*ai.Message{
{
Role: ai.RoleUser,
Content: []*ai.Part{
ai.NewTextPart("Write a very long essay about the history of computing with at least 5000 words."),
},
},
},
}

_, err := model.Generate(ctx, request, nil)
// This might or might not error depending on model speed
// If it errors, verify the error is properly formatted
if err != nil {
errMsg := err.Error()
t.Logf("Received error (expected with short timeout): %v", err)

// Verify error message is descriptive
if errMsg == "" {
t.Error("Error message is empty")
}
} else {
t.Log("Request completed within timeout (no error to test)")
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test case Ollama API error response is non-deterministic because it relies on a request timeout, which may not occur consistently across different environments or under varying system loads. This can lead to flaky tests.

To make error handling tests more reliable, it's better to test against a deterministic failure. For instance, you could test how the client handles a connection error by attempting to connect to an invalid server address. This would provide consistent and reliable testing of the error propagation logic.

Here's a suggestion to replace the current test case with one that tests a connection error:

	t.Run("Invalid server address connection error", func(t *testing.T) {
		// Use a port that is unlikely to be in use.
		invalidAddressOllama := &Ollama{
			ServerAddress: "http://localhost:99999",
			Timeout:       2,
		}
		invalidAddressOllama.Init(ctx)

		model := invalidAddressOllama.DefineModel(g, ModelDefinition{
			Name: "any-model",
			Type: "chat",
		}, nil)

		request := &ai.ModelRequest{
			Messages: []*ai.Message{
				{
					Role: ai.RoleUser,
					Content: []*ai.Part{
						ai.NewTextPart("Hello"),
					},
				},
			},
		}

		_, err := model.Generate(ctx, request, nil)
		if err == nil {
			t.Fatal("Expected an error for an invalid server address, but got nil")
		}

		if !strings.Contains(err.Error(), "failed to send request") {
			t.Errorf("Expected error message to contain 'failed to send request', but got: %v", err)
		}
		t.Logf("Successfully received expected connection error: %v", err)
	})

@HVbajoria
Copy link
Author

Hi @apascal07 ,

Tried resolving all the issues as highlighted by Gemini.

Not sure if last one should be implemented.

Could you please assist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation go

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Go] Add structured output support in Ollama plugin

1 participant