From 683a210f0a4d0642a1ed373f899e2d6468cc8fe6 Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Thu, 30 Jan 2025 06:59:36 +0500 Subject: [PATCH 1/6] [chore]: Structure Support for Deepseek --- .../langchain_deepseek/chat_models.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index 01bd674ac7a11..275565fce3ea2 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -218,3 +218,76 @@ def _create_chat_result( ) return rtn + + def with_structured_output( + self, + schema: Optional[_DictOrPydanticClass] = None, + *, + method: Literal[ + "function_calling", "json_object" + ] = "function_calling", + include_raw: bool = False, + strict: Optional[bool] = None, + **kwargs: Any, + ) -> Runnable[LanguageModelInput, _DictOrPydantic]: + if kwargs: + raise ValueError(f"Received unsupported arguments {kwargs}") + if strict is not None and method == "json_mode": + raise ValueError( + "Argument `strict` is not supported with `method`='json_mode'" + ) + is_pydantic_schema = _is_pydantic_class(schema) + + if method == "function_calling": + if schema is None: + raise ValueError( + "schema must be specified when method is not 'json_mode'. " + "Received None." + ) + tool_name = convert_to_openai_tool(schema)["function"]["name"] + bind_kwargs = self._filter_disabled_params( + tool_choice=tool_name, + parallel_tool_calls=False, + strict=strict, + structured_output_format={ + "kwargs": {"method": method}, + "schema": schema, + }, + ) + + llm = self.bind_tools([schema], **bind_kwargs) + if is_pydantic_schema: + output_parser: Runnable = PydanticToolsParser( + tools=[schema], # type: ignore[list-item] + first_tool_only=True, # type: ignore[list-item] + ) + else: + output_parser = JsonOutputKeyToolsParser( + key_name=tool_name, first_tool_only=True + ) + elif method == "json_object": + llm = self.bind( + response_format={"type": "json_object"} + ) + output_parser = ( + PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type] + if is_pydantic_schema + else JsonOutputParser() + ) + else: + raise ValueError( + f"Unrecognized method argument. Expected one of 'function_calling' or " + f"'json_mode'. Received: '{method}'" + ) + + if include_raw: + parser_assign = RunnablePassthrough.assign( + parsed=itemgetter("raw") | output_parser, parsing_error=lambda _: None + ) + parser_none = RunnablePassthrough.assign(parsed=lambda _: None) + parser_with_fallback = parser_assign.with_fallbacks( + [parser_none], exception_key="parsing_error" + ) + return RunnableMap(raw=llm) | parser_with_fallback + else: + return llm | output_parser From cb02461d50efc48867e40ae58059d55759bbada4 Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Fri, 31 Jan 2025 05:46:37 +0500 Subject: [PATCH 2/6] [chore]: Added Structured Output for deepseek --- .../langchain_deepseek/chat_models.py | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index 275565fce3ea2..612ddc7b2d0ad 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -1,13 +1,36 @@ """DeepSeek chat models.""" -from typing import Dict, Optional, Union +from operator import itemgetter +from typing import Dict, Literal, Optional, Type, TypeVar, Union import openai +from langchain_core.language_models import LanguageModelInput +from langchain_core.output_parsers import ( + JsonOutputKeyToolsParser, + JsonOutputParser, + PydanticToolsParser, +) from langchain_core.outputs import ChatResult +from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough from langchain_core.utils import from_env, secret_from_env +from langchain_core.utils.function_calling import convert_to_openai_tool +from langchain_core.utils.pydantic import ( + BaseModel, + is_basemodel_subclass, +) from langchain_openai.chat_models.base import BaseChatOpenAI from pydantic import ConfigDict, Field, SecretStr, model_validator -from typing_extensions import Self +from typing_extensions import Any, Self + +_BM = TypeVar("_BM", bound=BaseModel) + +_DictOrPydanticClass = Union[Dict[str, Any], Type[_BM], Type] +_DictOrPydantic = Union[Dict, _BM] + + +def _is_pydantic_class(obj: Any) -> bool: + return isinstance(obj, type) and is_basemodel_subclass(obj) + DEFAULT_API_BASE = "https://api.deepseek.com/v1" @@ -218,44 +241,21 @@ def _create_chat_result( ) return rtn - + def with_structured_output( self, schema: Optional[_DictOrPydanticClass] = None, *, - method: Literal[ - "function_calling", "json_object" - ] = "function_calling", + method: Literal["function_calling", "json_object"] = "function_calling", include_raw: bool = False, - strict: Optional[bool] = None, **kwargs: Any, ) -> Runnable[LanguageModelInput, _DictOrPydantic]: if kwargs: raise ValueError(f"Received unsupported arguments {kwargs}") - if strict is not None and method == "json_mode": - raise ValueError( - "Argument `strict` is not supported with `method`='json_mode'" - ) is_pydantic_schema = _is_pydantic_class(schema) - if method == "function_calling": - if schema is None: - raise ValueError( - "schema must be specified when method is not 'json_mode'. " - "Received None." - ) tool_name = convert_to_openai_tool(schema)["function"]["name"] - bind_kwargs = self._filter_disabled_params( - tool_choice=tool_name, - parallel_tool_calls=False, - strict=strict, - structured_output_format={ - "kwargs": {"method": method}, - "schema": schema, - }, - ) - - llm = self.bind_tools([schema], **bind_kwargs) + llm = self.bind_tools([schema]) if is_pydantic_schema: output_parser: Runnable = PydanticToolsParser( tools=[schema], # type: ignore[list-item] @@ -266,18 +266,12 @@ def with_structured_output( key_name=tool_name, first_tool_only=True ) elif method == "json_object": - llm = self.bind( - response_format={"type": "json_object"} - ) - output_parser = ( - PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type] - if is_pydantic_schema - else JsonOutputParser() - ) + llm = self.bind(response_format={"type": "json_object"}) + output_parser = JsonOutputParser() else: raise ValueError( f"Unrecognized method argument. Expected one of 'function_calling' or " - f"'json_mode'. Received: '{method}'" + f"'json_object'. Received: '{method}'" ) if include_raw: From f336afd8fd0e2d3469c7a46db66e6eb9e5dd62da Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Fri, 31 Jan 2025 05:51:48 +0500 Subject: [PATCH 3/6] [chore] --- libs/partners/deepseek/langchain_deepseek/chat_models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index 612ddc7b2d0ad..e7f571000cf73 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -254,6 +254,11 @@ def with_structured_output( raise ValueError(f"Received unsupported arguments {kwargs}") is_pydantic_schema = _is_pydantic_class(schema) if method == "function_calling": + if schema is None: + raise ValueError( + "schema must be specified when method is not 'json_object'. " + "Received None." + ) tool_name = convert_to_openai_tool(schema)["function"]["name"] llm = self.bind_tools([schema]) if is_pydantic_schema: From b5adca8023f1507a7241f75607cf572f0a4122a4 Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Fri, 31 Jan 2025 05:57:08 +0500 Subject: [PATCH 4/6] [chore] --- libs/partners/deepseek/langchain_deepseek/chat_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index e7f571000cf73..4318aa5ec1824 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -248,6 +248,7 @@ def with_structured_output( *, method: Literal["function_calling", "json_object"] = "function_calling", include_raw: bool = False, + strict: Optional[bool] = None, **kwargs: Any, ) -> Runnable[LanguageModelInput, _DictOrPydantic]: if kwargs: From 4c4ae4ca0cf051b50147cf3deb67a2e265caea6a Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Fri, 31 Jan 2025 21:58:55 +0500 Subject: [PATCH 5/6] [fix] --- libs/partners/deepseek/langchain_deepseek/chat_models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index 4318aa5ec1824..40e5b6a3295c3 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -246,7 +246,7 @@ def with_structured_output( self, schema: Optional[_DictOrPydanticClass] = None, *, - method: Literal["function_calling", "json_object"] = "function_calling", + method: Literal['function_calling', 'json_mode', 'json_schema'] = 'function_calling', include_raw: bool = False, strict: Optional[bool] = None, **kwargs: Any, @@ -257,7 +257,7 @@ def with_structured_output( if method == "function_calling": if schema is None: raise ValueError( - "schema must be specified when method is not 'json_object'. " + "schema must be specified when method is 'function_calling'. " "Received None." ) tool_name = convert_to_openai_tool(schema)["function"]["name"] @@ -271,7 +271,7 @@ def with_structured_output( output_parser = JsonOutputKeyToolsParser( key_name=tool_name, first_tool_only=True ) - elif method == "json_object": + elif method in ["json_mode", "json_schema"]: llm = self.bind(response_format={"type": "json_object"}) output_parser = JsonOutputParser() else: From 34251dcfc9515f17a41d3a80b743e5e3f3ea8a8f Mon Sep 17 00:00:00 2001 From: keenborder786 <21110290@lums.edu.pk> Date: Sat, 1 Feb 2025 16:16:34 +0500 Subject: [PATCH 6/6] [fix] --- libs/partners/deepseek/langchain_deepseek/chat_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/partners/deepseek/langchain_deepseek/chat_models.py b/libs/partners/deepseek/langchain_deepseek/chat_models.py index 40e5b6a3295c3..90b010fb913f3 100644 --- a/libs/partners/deepseek/langchain_deepseek/chat_models.py +++ b/libs/partners/deepseek/langchain_deepseek/chat_models.py @@ -246,7 +246,9 @@ def with_structured_output( self, schema: Optional[_DictOrPydanticClass] = None, *, - method: Literal['function_calling', 'json_mode', 'json_schema'] = 'function_calling', + method: Literal[ + "function_calling", "json_mode", "json_schema" + ] = "function_calling", include_raw: bool = False, strict: Optional[bool] = None, **kwargs: Any,