Skip to content

fix(wren-ai-service): support Ollama-hosted LLM models and fix JSON field instructions for Wren Engine compatibility - #2791

Open
sohail-eng wants to merge 1 commit into
Canner:legacy/v1from
sohail-eng:fix/wren-ai-service/ollama-support
Open

sohail-eng wants to merge 1 commit into
Canner:legacy/v1from
sohail-eng:fix/wren-ai-service/ollama-support

Conversation

@sohail-eng

Copy link
Copy Markdown

Summary

This PR adds support for Ollama-hosted LLM models (e.g. gpt-oss, nemotron) to the Wren AI service, and fixes JSON field SQL instructions for Wren Engine (DataFusion) compatibility.

Changes

wren-ai-service/src/providers/llm/litellm.py

  • Strip response_format for Ollama: Ollama reasoning models do not support OpenAI's response_format: json_schema structured output. When api_base contains "ollama", the response_format parameter is removed before the LLM call.
  • Extract expected JSON keys: Before stripping response_format, the expected keys are extracted from the json_schema definition so they can be validated post-hoc.
  • Retry loop (3 attempts): After each LLM call, the response content is validated for: (1) non-empty content, (2) valid JSON, (3) all expected keys present. If validation fails, the request is retried up to 3 times.
  • Meta sanitization: meta dict is sanitized via json.loads(json.dumps(meta, default=str)) to handle non-serializable values (e.g. None integers) that cause langfuse UpdateGenerationBody validation errors.

wren-ai-service/src/pipelines/retrieval/db_schema_retrieval.py

  • Graceful fallback on LLM parse failure: Added try/except around orjson.loads(filter_columns_in_tables["replies"][0])["results"] catching KeyError, JSONDecodeError, and TypeError. Falls back to unpruned db schemas when the LLM returns non-JSON or unexpected content.

wren-ai-service/src/pipelines/generation/utils/sql.py

  • Replace BigQuery-specific LAX functions with CAST: The _DEFAULT_JSON_FIELD_INSTRUCTIONS prompt previously instructed the LLM to use LAX_STRING, LAX_BOOL, LAX_FLOAT64, LAX_INT64 — these are BigQuery-specific functions not supported by the Wren Engine (DataFusion). Replaced with DataFusion-compatible CAST(... AS VARCHAR), CAST(... AS BOOLEAN), CAST(... AS DOUBLE), CAST(... AS BIGINT).

docker/.env.example

  • Added OLLAMA_API_KEY and GEMINI_API_KEY vendor key entries.

docker/config.example.yaml

  • Added commented examples for Ollama LLM model (openai/gpt-oss:120b-cloud via https://ollama.com/v1) and Gemini embedder (gemini/gemini-embedding-001).

docker/README.md

  • Added "How to start with Ollama" section with step-by-step setup instructions and notes on reasoning model behavior.

Test Results

Tested end-to-end with:

  • LLM: openai/gpt-oss:120b-cloud via https://ollama.com/v1
  • Embedder: gemini/gemini-embedding-001 (3072 dimensions)
  • Schema: 191 Qdrant documents + 95 table descriptions indexed
  • Multiple questions answered with valid SQL generation; no lax_string or structured output errors.

Notes

  • Ollama does not provide a /v1/embeddings endpoint, so a separate embedder (e.g. Gemini) is required.
  • max_completion_tokens should be set high (e.g. 32768) to accommodate reasoning tokens.
  • enable_column_pruning: false is recommended when the LLM's column pruning responses are unreliable.

…ield instructions for Wren Engine compatibility

- litellm.py: Strip response_format json_schema when Ollama API base detected; extract expected keys from schema and retry up to 3 times if LLM returns invalid/empty JSON or missing keys; sanitize meta via json.loads(json.dumps(..., default=str)) for langfuse compatibility
- db_schema_retrieval.py: Add try/except fallback on orjson.loads filter_columns_in_tables results to handle non-JSON LLM responses gracefully
- sql.py: Replace BigQuery-specific LAX_STRING/LAX_BOOL/LAX_FLOAT64/LAX_INT64 with DataFusion-compatible CAST expressions in _DEFAULT_JSON_FIELD_INSTRUCTIONS
- docker/.env.example: Add OLLAMA_API_KEY and GEMINI_API_KEY vendor keys
- docker/config.example.yaml: Add commented Ollama LLM and Gemini embedder examples
- docker/README.md: Add 'How to start with Ollama' section with setup instructions

Tested end-to-end with openai/gpt-oss:120b-cloud via https://ollama.com/v1 and gemini/gemini-embedding-001 embedder.
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e719db30-6712-431a-9fae-c63b750133b7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 8, 2026
@sohail-eng

Copy link
Copy Markdown
Author

Summary

Hi @goldmedal @yichieh-lu @chilijung 👋

I've opened this PR to add Ollama-hosted LLM model support to Wren AI, and to fix a JSON field SQL instruction issue that was causing runtime errors with the Wren Engine.

What this PR does

1. Ollama LLM Support (litellm.py)

  • Ollama reasoning models (e.g. gpt-oss:120b-cloud) don't support OpenAI's response_format: json_schema structured output. This PR detects Ollama API bases and strips that parameter automatically.
  • Since structured output is stripped, a retry loop (3 attempts) was added to validate LLM responses: checks for non-empty content, valid JSON, and all expected keys present (extracted from the original json_schema).
  • Meta dict is sanitized via json.loads(json.dumps(meta, default=str)) to fix langfuse UpdateGenerationBody validation errors caused by None token counts from Ollama.

2. DB Schema Retrieval Fallback (db_schema_retrieval.py)

  • Without structured output enforcement, the LLM sometimes returns non-JSON content for column pruning. Added try/except around orjson.loads(...)["results"] with a fallback to unpruned db schemas, preventing pipeline crashes.

3. JSON Field SQL Instructions Fix (sql.py)

  • The _DEFAULT_JSON_FIELD_INSTRUCTIONS prompt was teaching the LLM to use LAX_STRING, LAX_BOOL, LAX_FLOAT64, LAX_INT64 — these are BigQuery-specific functions not supported by the Wren Engine (DataFusion). This caused Invalid function 'lax_string' errors at runtime.
  • Replaced with DataFusion-compatible CAST(... AS VARCHAR), CAST(... AS BOOLEAN), CAST(... AS DOUBLE), CAST(... AS BIGINT). Verified working with the Wren Engine directly.

4. Documentation (docker/)

  • .env.example: Added OLLAMA_API_KEY and GEMINI_API_KEY entries
  • config.example.yaml: Added commented Ollama LLM + Gemini embedder examples
  • README.md: Added "How to start with Ollama" setup guide

Why this matters

  • Community demand: Ollama is one of the most popular ways to self-host LLMs. Supporting it out-of-the-box makes Wren AI more accessible.
  • Bug fix: The LAX_* functions in the JSON instructions cause runtime errors for ALL users with JSON columns — not just Ollama users. This fix benefits everyone.
  • Non-invasive: The Ollama detection only activates when api_base contains "ollama". All existing OpenAI/other provider paths are unchanged.

Test results

Tested end-to-end with openai/gpt-oss:120b-cloud via https://ollama.com/v1 and gemini/gemini-embedding-001 embedder (3072 dim). 191 Qdrant docs + 95 table descriptions indexed. Multiple questions answered with valid SQL; no lax_string or structured output errors.

Notes

  • Couldn't add the module/ai-service label (requires admin rights) — could a maintainer add it?
  • Happy to make any adjustments based on your feedback!

@goldmedal
goldmedal requested review from cyyeh and yichieh-lu and removed request for cyyeh October 13, 2026 02:52
@cyyeh
cyyeh requested a lite review from Copilot October 13, 2026 03:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The retry flow can repeat streamed responses, and malformed pruning replies can still fail or leave SQL generation without schemas.

5 open findings
What changed in this PR

This PR adds Ollama-hosted LLM setup to the Wren AI service and updates JSON-field SQL guidance for Wren Engine.

Changes:

  • Adds Ollama configuration examples and setup instructions, including a separate Gemini embedder.
  • Adds LLM response validation and a schema-retrieval fallback, and replaces BigQuery-specific JSON conversion guidance with CAST.
File Description
wren-ai-service/​src/​providers/​llm/​litellm.py Adds Ollama response handling, retries, and metadata sanitization.
wren-ai-service/​src/​pipelines/​retrieval/​db_schema_retrieval.py Adds a fallback for failed pruning responses.
wren-ai-service/​src/​pipelines/​generation/​utils/​sql.py Updates JSON-field SQL instructions to use CAST.
docker/​README.md Documents Ollama setup.
docker/​config.example.yaml Adds Ollama and Gemini configuration examples.
docker/​.env.example Adds example API-key variables.

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

**generation_kwargs,
)

_max_content_retries = 3
Comment on lines +349 to +353
try:
columns_and_tables_needed = orjson.loads(
filter_columns_in_tables["replies"][0]
)["results"]
except (KeyError, orjson.JSONDecodeError, TypeError):
filter_columns_in_tables["replies"][0]
)["results"]
except (KeyError, orjson.JSONDecodeError, TypeError):
retrieval_results = check_using_db_schemas_without_pruning["db_schemas"]
try:
_msg.meta = _json.loads(_json.dumps(_msg.meta, default=str))
except (TypeError, ValueError):
_msg.meta = _json.loads(_json.dumps(_msg.meta, default=str, ignore_nan=True))
Comment on lines +184 to +187
if _expected_keys and isinstance(_parsed, dict):
if not _expected_keys.issubset(_parsed.keys()):
_all_valid = False
break

This branch has not been deployed

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

Labels

documentation Improvements or additions to documentation legacy-v1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants