langchain - ✅(Solved) Fix openrouter json_schema is not enforced, llm doesn't know about the schema [2 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36067Fetched 2026-04-08 00:57:19
View on GitHub
Comments
2
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
labeled ×3commented ×2cross-referenced ×2issue_type_added ×1

The schema is not enforced, and it seems like LLM doesn't know about it

Error Message

import os from langchain_openrouter import ChatOpenRouter from pydantic import BaseModel, ConfigDict, Field

os.environ["OPENROUTER_API_KEY"] = "x"

schema = { "title": "ProductInfo", "description": "Structured product data extracted from user text", "type": "object", "properties": { "name": { "type": "string", "description": "Product name" }, "price_pln": { "type": "number", "description": "Price in PLN" }, "color": { "type": "string", "description": "Product color" }, "volume_ml": { "type": "integer", "description": "Volume in milliliters" }, "dishwasher_safe": { "type": "boolean", "description": "Whether the product is dishwasher safe" } }, "required": ["name", "price_pln"], "additionalProperties": False }

same with pydantic model - langchain_core.exceptions.OutputParserException: Failed to parse ProductInfo from completion {"product_name": "Red ceramic mug", "capacity_ml": 350, "dishwasher_safe": true, "price": 24.99, "currency": "PLN"}.

class ProductInfo(BaseModel): model_config = ConfigDict(extra="forbid")

name: str = Field(description="Product name")
price_pln: float = Field(description="Price in PLN")
color: str | None = Field(default=None, description="Product color")
volume_ml: int | None = Field(default=None, description="Volume in milliliters")
dishwasher_safe: bool | None = Field(
    default=None,
    description="Whether the product is dishwasher safe",
)

llm = ChatOpenRouter( model="openrouter/hunter-alpha", temperature=0, max_retries=2, )

structured_llm = llm.with_structured_output( schema, method="json_schema", strict=True, )

result = structured_llm.invoke( "Extract product data from: Red ceramic mug, 350 ml, dishwasher-safe, price 24.99 PLN." )

print(result)

{'product_name': 'Red ceramic mug', 'capacity': '350 ml', 'features': ['dishwasher-safe'], 'price': {'amount': 24.99, 'currency': 'PLN'}}

Root Cause

The schema is not enforced, and it seems like LLM doesn't know about it

Fix Action

Fix / Workaround

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Other Dependencies

aiohttp: 3.13.3 dataclasses-json: 0.6.7 httpx: 0.28.1 httpx-sse: 0.4.3 jsonpatch: 1.33 numpy: 2.4.3 openai: 2.25.0 openrouter: 0.7.11 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pydantic-settings: 2.13.1 pytest: 9.0.2 PyYAML: 6.0.3 pyyaml: 6.0.3 requests: 2.32.5 requests-toolbelt: 1.0.0 sqlalchemy: 2.0.48 SQLAlchemy: 2.0.48 tenacity: 9.1.4 tiktoken: 0.12.0 typing-extensions: 4.15.0 uuid-utils: 0.14.1 websockets: 16.0 xxhash: 3.6.0 zstandard: 0.25.0

PR fix notes

PR #36119: fix(openrouter): pass strict to convert_to_json_schema in json_schema structured output

Description (problem / solution / changelog)

  • When with_structured_output(method="json_schema", strict=True) was called, convert_to_json_schema was invoked without the strict argument, so "additionalProperties": false was never added to the schema. The OpenRouter API (and OpenAI-compatible structured output in general) requires this field for schema enforcement to take effect — without it the model treats the schema as a hint rather than a constraint, which is why field names were ignored.

    • Fix: forward strict to convert_to_json_schema(schema, strict=strict) on line 862. This is a single-character change that makes the schema sent to the API match what the provider enforces.
    • Updated test_with_structured_output_strict_json_schema and test_with_structured_output_json_schema_strict_reaches_sdk to assert additionalProperties: false is present in the schema inside response_format when strict=True.

    Fixes #36067.

    Changes

    • langchain_openrouter/chat_models.py: convert_to_json_schema(schema) → convert_to_json_schema(schema, strict=strict)
    • tests/unit_tests/test_chat_models.py: add additionalProperties: false assertion to strict-mode tests

    Test plan

    • uv run --group test pytest tests/unit_tests/test_chat_models.py — 201 passed
    • strict=True now produces {"additionalProperties": false, ...} in the schema field
    • strict=None (default) is unchanged — no additionalProperties injected, consistent with optional enforcement

Changed files

  • libs/partners/openrouter/langchain_openrouter/chat_models.py (modified, +1/-1)
  • libs/partners/openrouter/tests/unit_tests/test_chat_models.py (modified, +75/-1)

PR #2: Dev

Description (problem / solution / changelog)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Added a new CLI command to generate customizable daily news briefs with provider selection (Groq/Ollama/OpenRouter), location/topic focus, save/caching options, and verbose output
    • Automated multi-stage analysis across world, national, and local scopes with structured brief generation in markdown
  • Chores

    • Project config updated with packaging/entry-point and runtime dependencies
    • Added an example .env file and updated ignore rules to exclude secrets and common output files
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • .env.example (added, +3/-0)
  • .gitignore (modified, +7/-0)
  • daily_brief/__init__.py (added, +0/-0)
  • daily_brief/cli/__init__.py (added, +0/-0)
  • daily_brief/cli/main.py (added, +55/-0)
  • daily_brief/llm/__init__.py (added, +1/-0)
  • daily_brief/llm/build_graph.py (added, +72/-0)
  • daily_brief/llm/get_model.py (added, +24/-0)
  • daily_brief/llm/state.py (added, +19/-0)
  • daily_brief/nodes/__init__.py (added, +1/-0)
  • daily_brief/nodes/briefing_generator.py (added, +180/-0)
  • daily_brief/nodes/cross_level_connector.py (added, +109/-0)
  • daily_brief/nodes/new_gatherer.py (added, +121/-0)
  • daily_brief/nodes/scope_node.py (added, +120/-0)
  • daily_brief/nodes/story_analyzer.py (added, +133/-0)
  • daily_brief/utils/__init__.py (added, +0/-0)
  • daily_brief/utils/console.py (added, +3/-0)
  • data/tavily2_ollama_world_Iran.json (added, +93/-0)
  • pyproject.toml (modified, +25/-1)
  • uv.lock (added, +1580/-0)

Code Example

import os
from langchain_openrouter import ChatOpenRouter
from pydantic import BaseModel, ConfigDict, Field

os.environ["OPENROUTER_API_KEY"] = "x"

schema = {
    "title": "ProductInfo",
    "description": "Structured product data extracted from user text",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Product name"
        },
        "price_pln": {
            "type": "number",
            "description": "Price in PLN"
        },
        "color": {
            "type": "string",
            "description": "Product color"
        },
        "volume_ml": {
            "type": "integer",
            "description": "Volume in milliliters"
        },
        "dishwasher_safe": {
            "type": "boolean",
            "description": "Whether the product is dishwasher safe"
        }
    },
    "required": ["name", "price_pln"],
    "additionalProperties": False
}
# same with pydantic model - langchain_core.exceptions.OutputParserException: Failed to parse ProductInfo from completion {"product_name": "Red ceramic mug", "capacity_ml": 350, "dishwasher_safe": true, "price": 24.99, "currency": "PLN"}.

class ProductInfo(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Product name")
    price_pln: float = Field(description="Price in PLN")
    color: str | None = Field(default=None, description="Product color")
    volume_ml: int | None = Field(default=None, description="Volume in milliliters")
    dishwasher_safe: bool | None = Field(
        default=None,
        description="Whether the product is dishwasher safe",
    )

llm = ChatOpenRouter(
    model="openrouter/hunter-alpha",
    temperature=0,
    max_retries=2,
)

structured_llm = llm.with_structured_output(
    schema,
    method="json_schema",
    strict=True,
)

result = structured_llm.invoke(
    "Extract product data from: Red ceramic mug, 350 ml, dishwasher-safe, price 24.99 PLN."
)

print(result)
# {'product_name': 'Red ceramic mug', 'capacity': '350 ml', 'features': ['dishwasher-safe'], 'price': {'amount': 24.99, 'currency': 'PLN'}}

---
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

No response

Reproduction Steps / Example Code (Python)

import os
from langchain_openrouter import ChatOpenRouter
from pydantic import BaseModel, ConfigDict, Field

os.environ["OPENROUTER_API_KEY"] = "x"

schema = {
    "title": "ProductInfo",
    "description": "Structured product data extracted from user text",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Product name"
        },
        "price_pln": {
            "type": "number",
            "description": "Price in PLN"
        },
        "color": {
            "type": "string",
            "description": "Product color"
        },
        "volume_ml": {
            "type": "integer",
            "description": "Volume in milliliters"
        },
        "dishwasher_safe": {
            "type": "boolean",
            "description": "Whether the product is dishwasher safe"
        }
    },
    "required": ["name", "price_pln"],
    "additionalProperties": False
}
# same with pydantic model - langchain_core.exceptions.OutputParserException: Failed to parse ProductInfo from completion {"product_name": "Red ceramic mug", "capacity_ml": 350, "dishwasher_safe": true, "price": 24.99, "currency": "PLN"}.

class ProductInfo(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str = Field(description="Product name")
    price_pln: float = Field(description="Price in PLN")
    color: str | None = Field(default=None, description="Product color")
    volume_ml: int | None = Field(default=None, description="Volume in milliliters")
    dishwasher_safe: bool | None = Field(
        default=None,
        description="Whether the product is dishwasher safe",
    )

llm = ChatOpenRouter(
    model="openrouter/hunter-alpha",
    temperature=0,
    max_retries=2,
)

structured_llm = llm.with_structured_output(
    schema,
    method="json_schema",
    strict=True,
)

result = structured_llm.invoke(
    "Extract product data from: Red ceramic mug, 350 ml, dishwasher-safe, price 24.99 PLN."
)

print(result)
# {'product_name': 'Red ceramic mug', 'capacity': '350 ml', 'features': ['dishwasher-safe'], 'price': {'amount': 24.99, 'currency': 'PLN'}}

Error Message and Stack Trace (if applicable)

Description

The schema is not enforced, and it seems like LLM doesn't know about it

System Info

System Information

OS: Darwin OS Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:43 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T8132 Python Version: 3.14.2 (main, Jan 14 2026, 23:37:46) [Clang 21.1.4 ]

Package Information

langchain_core: 1.2.17 langchain_community: 0.4.1 langsmith: 0.7.12 langchain_classic: 1.0.2 langchain_openai: 1.1.10 langchain_openrouter: 0.1.0 langchain_text_splitters: 1.1.1 langgraph_sdk: 0.3.9

Optional packages not installed

deepagents deepagents-cli

Other Dependencies

aiohttp: 3.13.3 dataclasses-json: 0.6.7 httpx: 0.28.1 httpx-sse: 0.4.3 jsonpatch: 1.33 numpy: 2.4.3 openai: 2.25.0 openrouter: 0.7.11 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pydantic-settings: 2.13.1 pytest: 9.0.2 PyYAML: 6.0.3 pyyaml: 6.0.3 requests: 2.32.5 requests-toolbelt: 1.0.0 sqlalchemy: 2.0.48 SQLAlchemy: 2.0.48 tenacity: 9.1.4 tiktoken: 0.12.0 typing-extensions: 4.15.0 uuid-utils: 0.14.1 websockets: 16.0 xxhash: 3.6.0 zstandard: 0.25.0

extent analysis

Fix Plan

To enforce the schema and make the LLM aware of it, we need to adjust the invoke method to include the schema in the prompt. We can do this by modifying the prompt to include a description of the expected output format.

Step-by-Step Solution

  1. Modify the prompt: Update the invoke method to include a description of the expected output format in the prompt.
  2. Update the schema enforcement: Use the method="json_schema" parameter in the with_structured_output method to enforce the schema.

Example Code

import os
from langchain_openrouter import ChatOpenRouter

os.environ["OPENROUTER_API_KEY"] = "x"

schema = {
    "title": "ProductInfo",
    "description": "Structured product data extracted from user text",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Product name"
        },
        "price_pln": {
            "type": "number",
            "description": "Price in PLN"
        },
        "color": {
            "type": "string",
            "description": "Product color"
        },
        "volume_ml": {
            "type": "integer",
            "description": "Volume in milliliters"
        },
        "dishwasher_safe": {
            "type": "boolean",
            "description": "Whether the product is dishwasher safe"
        }
    },
    "required": ["name", "price_pln"],
    "additionalProperties": False
}

llm = ChatOpenRouter(
    model="openrouter/hunter-alpha",
    temperature=0,
    max_retries=2,
)

structured_llm = llm.with_structured_output(
    schema,
    method="json_schema",
    strict=True,
)

prompt = "Extract product data from the following text and return it in the format: {name: string, price_pln: number, color: string, volume_ml: integer, dishwasher_safe: boolean}. Text: Red ceramic mug, 350 ml, dishwasher-safe, price 24.99 PLN."
result = structured_llm.invoke(prompt)

print(result)

Verification

To verify that the fix worked, check the output of the invoke method. It should now conform to the specified schema.

Extra Tips

  • Make sure to update the prompt variable to include a description of the expected output format.
  • Use the method="json_schema" parameter in the with_structured_output method to enforce the schema.
  • Adjust the schema variable to match the expected output format.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING