From c45dc1cafb7873fa9b99b0e3c0787e26aef1a3c4 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 29 Oct 2025 12:25:11 -0700 Subject: [PATCH 1/5] Fix with_structured_output json_schema method Fix json_schema method in the with_structured_output function. --- libs/oci/README.md | 16 ++++++++++ .../chat_models/oci_generative_ai.py | 29 ++++++++++++------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/libs/oci/README.md b/libs/oci/README.md index 2266d10..6b7c5f1 100644 --- a/libs/oci/README.md +++ b/libs/oci/README.md @@ -54,6 +54,22 @@ embeddings = OCIGenAIEmbeddings() embeddings.embed_query("What is the meaning of life?") ``` +### 4. Use Structured Output +`ChatOCIGenAI` supports structured output. + +```python +from langchain_oci import ChatOCIGenAI +from pydantic import BaseModel + +class Joke(BaseModel): + setup: str + punchline: str + +llm = ChatOCIGenAI() +structured_llm = llm.with_structured_output(Joke) +structured_llm.invoke("Tell me a joke about programming") +``` + ## OCI Data Science Model Deployment Examples diff --git a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py index 9362c60..11df0fd 100644 --- a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py +++ b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py @@ -1129,7 +1129,7 @@ def with_structured_output( *, method: Literal[ "function_calling", "json_schema", "json_mode" - ] = "function_calling", + ] = "json_schema", include_raw: bool = False, **kwargs: Any, ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]: @@ -1201,19 +1201,26 @@ def with_structured_output( else JsonOutputParser() ) elif method == "json_schema": - response_format = ( - dict( - schema.model_json_schema().items() # type: ignore[union-attr] - ) + from oci.generative_ai_inference import models + + json_schema_dict = ( + schema.model_json_schema() # type: ignore[union-attr] if is_pydantic_schema else schema ) - llm_response_format: Dict[Any, Any] = {"type": "JSON_OBJECT"} - llm_response_format["schema"] = { - k: v - for k, v in response_format.items() # type: ignore[union-attr] - } - llm = self.bind(response_format=llm_response_format) + + response_json_schema = models.ResponseJsonSchema( + name=json_schema_dict.get("title", "response"), + description=json_schema_dict.get("description", ""), + schema=json_schema_dict, + is_strict=True + ) + + response_format_obj = models.JsonSchemaResponseFormat( + json_schema=response_json_schema + ) + + llm = self.bind(response_format=response_format_obj) if is_pydantic_schema: output_parser = PydanticOutputParser(pydantic_object=schema) else: From 8994198e67344f643541ec7b00797eb755f2a966 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Wed, 29 Oct 2025 13:25:17 -0700 Subject: [PATCH 2/5] Remove import and define an alias for the class in the init Remove import and define an alias for the class in the init --- .../langchain_oci/chat_models/oci_generative_ai.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py index 11df0fd..c930215 100644 --- a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py +++ b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py @@ -213,6 +213,9 @@ def __init__(self) -> None: "SYSTEM": models.CohereSystemMessage, "TOOL": models.CohereToolMessage, } + + self.oci_response_json_schema = models.ResponseJsonSchema + self.oci_json_schema_response_format = models.JsonSchemaResponseFormat self.chat_api_format = models.BaseChatRequest.API_FORMAT_COHERE def chat_response_to_text(self, response: Any) -> str: @@ -582,6 +585,10 @@ def __init__(self) -> None: self.oci_tool_call = models.FunctionCall self.oci_tool_message = models.ToolMessage + # Response format models + self.oci_response_json_schema = models.ResponseJsonSchema + self.oci_json_schema_response_format = models.JsonSchemaResponseFormat + self.chat_api_format = models.BaseChatRequest.API_FORMAT_GENERIC def chat_response_to_text(self, response: Any) -> str: @@ -1201,22 +1208,20 @@ def with_structured_output( else JsonOutputParser() ) elif method == "json_schema": - from oci.generative_ai_inference import models - json_schema_dict = ( schema.model_json_schema() # type: ignore[union-attr] if is_pydantic_schema else schema ) - response_json_schema = models.ResponseJsonSchema( + response_json_schema = self._provider.oci_response_json_schema( name=json_schema_dict.get("title", "response"), description=json_schema_dict.get("description", ""), schema=json_schema_dict, is_strict=True ) - response_format_obj = models.JsonSchemaResponseFormat( + response_format_obj = self._provider.oci_json_schema_response_format( json_schema=response_json_schema ) From 045c3f78a0bd8e9fc0ee1fc7673ab81c390e5814 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Thu, 30 Oct 2025 11:17:57 -0700 Subject: [PATCH 3/5] Set default structured output method to function_calling Changed the default method for structured output in ChatOCIGenAI from 'json_schema' to 'function_calling'. Updated documentation to clarify the default and suggest alternatives if it fails. --- libs/oci/README.md | 3 ++- libs/oci/langchain_oci/chat_models/oci_generative_ai.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libs/oci/README.md b/libs/oci/README.md index 6b7c5f1..7fe4046 100644 --- a/libs/oci/README.md +++ b/libs/oci/README.md @@ -55,7 +55,8 @@ embeddings.embed_query("What is the meaning of life?") ``` ### 4. Use Structured Output -`ChatOCIGenAI` supports structured output. +`ChatOCIGenAI` supports structured output. +The default method is "function_calling". If default method fails, try "json_schema" or "json_mode". ```python from langchain_oci import ChatOCIGenAI diff --git a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py index c930215..a61553d 100644 --- a/libs/oci/langchain_oci/chat_models/oci_generative_ai.py +++ b/libs/oci/langchain_oci/chat_models/oci_generative_ai.py @@ -1136,7 +1136,7 @@ def with_structured_output( *, method: Literal[ "function_calling", "json_schema", "json_mode" - ] = "json_schema", + ] = "function_calling", include_raw: bool = False, **kwargs: Any, ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]: @@ -1150,14 +1150,14 @@ def with_structured_output( `method` is "function_calling" and `schema` is a dict, then the dict must match the OCI Generative AI function-calling spec. method: - The method for steering model generation, either "function_calling" - or "json_mode" or "json_schema. If "function_calling" then the schema + The method for steering model generation, either "function_calling" (default method) + or "json_mode" or "json_schema". If "function_calling" then the schema will be converted to an OCI function and the returned model will make use of the function-calling API. If "json_mode" then Cohere's JSON mode will be used. Note that if using "json_mode" then you must include instructions for formatting the output into the desired schema into the model call. If "json_schema" then it allows the user to pass a json schema (or pydantic) - to the model for structured output. This is the default method. + to the model for structured output. include_raw: If False then only the parsed structured output is returned. If an error occurs during model output parsing it will be raised. If True From fe3c8aa6cdbcacaadd4625d85bd1027de2bd272f Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Thu, 30 Oct 2025 11:27:12 -0700 Subject: [PATCH 4/5] Fix assertions in unit tests to match the new json_schema method in with_structured_output --- .../unit_tests/chat_models/test_oci_generative_ai.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py index b7e19da..b152cb0 100644 --- a/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py +++ b/libs/oci/tests/unit_tests/chat_models/test_oci_generative_ai.py @@ -14,7 +14,7 @@ class MockResponseDict(dict): def __getattr__(self, val): # type: ignore[no-untyped-def] - return self[val] + return self.get(val) class MockToolCall(dict): @@ -473,10 +473,10 @@ class WeatherResponse(BaseModel): llm = ChatOCIGenAI(model_id="cohere.command-latest", client=oci_gen_ai_client) def mocked_response(*args, **kwargs): # type: ignore[no-untyped-def] - # Verify that response_format contains the schema + # Verify that response_format is a JsonSchemaResponseFormat object request = args[0] - assert request.chat_request.response_format["type"] == "JSON_OBJECT" - assert "schema" in request.chat_request.response_format + assert hasattr(request.chat_request, 'response_format') + assert request.chat_request.response_format is not None return MockResponseDict( { From f4c2e9b0ecb9236c4b43d711ce330022cde68d05 Mon Sep 17 00:00:00 2001 From: paxiaatucsdedu Date: Thu, 30 Oct 2025 12:54:56 -0700 Subject: [PATCH 5/5] Clarify with_structured_output methods in README Clarify with_structured_output methods in README --- libs/oci/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/oci/README.md b/libs/oci/README.md index 60f9d5b..91b4069 100644 --- a/libs/oci/README.md +++ b/libs/oci/README.md @@ -63,7 +63,8 @@ embeddings.embed_query("What is the meaning of life?") ### 4. Use Structured Output `ChatOCIGenAI` supports structured output. -The default method is "function_calling". If default method fails, try "json_schema" or "json_mode". + +**Note:** The default method is `function_calling`. If default method returns `None` (e.g. for Gemini models), try `json_schema` or `json_mode`. ```python from langchain_oci import ChatOCIGenAI