langchain-openai 0.2.0.dev2__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/PKG-INFO +2 -2
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/chat_models/azure.py +33 -331
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/chat_models/base.py +94 -30
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/embeddings/base.py +13 -9
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/pyproject.toml +3 -2
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/LICENSE +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/README.md +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/__init__.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/chat_models/__init__.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/embeddings/__init__.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/embeddings/azure.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/llms/__init__.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/llms/azure.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/llms/base.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/output_parsers/__init__.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/output_parsers/tools.py +0 -0
- {langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langchain-openai
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: An integration package connecting OpenAI and LangChain
|
|
5
5
|
Home-page: https://github.com/langchain-ai/langchain
|
|
6
6
|
License: MIT
|
|
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Requires-Dist: langchain-core (>=0.3
|
|
14
|
+
Requires-Dist: langchain-core (>=0.3,<0.4)
|
|
15
15
|
Requires-Dist: openai (>=1.40.0,<2.0.0)
|
|
16
16
|
Requires-Dist: tiktoken (>=0.7,<1)
|
|
17
17
|
Project-URL: Repository, https://github.com/langchain-ai/langchain
|
{langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/chat_models/azure.py
RENAMED
|
@@ -4,37 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
-
from
|
|
8
|
-
from typing import (
|
|
9
|
-
Any,
|
|
10
|
-
Callable,
|
|
11
|
-
Dict,
|
|
12
|
-
List,
|
|
13
|
-
Literal,
|
|
14
|
-
Optional,
|
|
15
|
-
Sequence,
|
|
16
|
-
Type,
|
|
17
|
-
TypedDict,
|
|
18
|
-
TypeVar,
|
|
19
|
-
Union,
|
|
20
|
-
overload,
|
|
21
|
-
)
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Type, TypedDict, TypeVar, Union
|
|
22
8
|
|
|
23
9
|
import openai
|
|
24
|
-
from langchain_core.language_models import LanguageModelInput
|
|
25
10
|
from langchain_core.language_models.chat_models import LangSmithParams
|
|
26
11
|
from langchain_core.messages import BaseMessage
|
|
27
|
-
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser
|
|
28
|
-
from langchain_core.output_parsers.base import OutputParserLike
|
|
29
|
-
from langchain_core.output_parsers.openai_tools import (
|
|
30
|
-
JsonOutputKeyToolsParser,
|
|
31
|
-
PydanticToolsParser,
|
|
32
|
-
)
|
|
33
12
|
from langchain_core.outputs import ChatResult
|
|
34
|
-
from langchain_core.runnables import Runnable, RunnableMap, RunnablePassthrough
|
|
35
|
-
from langchain_core.tools import BaseTool
|
|
36
13
|
from langchain_core.utils import from_env, secret_from_env
|
|
37
|
-
from langchain_core.utils.function_calling import convert_to_openai_tool
|
|
38
14
|
from langchain_core.utils.pydantic import is_basemodel_subclass
|
|
39
15
|
from pydantic import BaseModel, Field, SecretStr, model_validator
|
|
40
16
|
from typing_extensions import Self
|
|
@@ -538,6 +514,7 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
538
514
|
default_factory=from_env("OPENAI_API_TYPE", default="azure")
|
|
539
515
|
)
|
|
540
516
|
"""Legacy, for openai<1.0.0 support."""
|
|
517
|
+
|
|
541
518
|
validate_base_url: bool = True
|
|
542
519
|
"""If legacy arg openai_api_base is passed in, try to infer if it is a base_url or
|
|
543
520
|
azure_endpoint and update client params accordingly.
|
|
@@ -550,6 +527,28 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
550
527
|
Used for tracing and token counting. Does NOT affect completion.
|
|
551
528
|
"""
|
|
552
529
|
|
|
530
|
+
disabled_params: Optional[Dict[str, Any]] = Field(default=None)
|
|
531
|
+
"""Parameters of the OpenAI client or chat.completions endpoint that should be
|
|
532
|
+
disabled for the given model.
|
|
533
|
+
|
|
534
|
+
Should be specified as ``{"param": None | ['val1', 'val2']}`` where the key is the
|
|
535
|
+
parameter and the value is either None, meaning that parameter should never be
|
|
536
|
+
used, or it's a list of disabled values for the parameter.
|
|
537
|
+
|
|
538
|
+
For example, older models may not support the 'parallel_tool_calls' parameter at
|
|
539
|
+
all, in which case ``disabled_params={"parallel_tool_calls: None}`` can ben passed
|
|
540
|
+
in.
|
|
541
|
+
|
|
542
|
+
If a parameter is disabled then it will not be used by default in any methods, e.g.
|
|
543
|
+
in
|
|
544
|
+
:meth:`~langchain_openai.chat_models.azure.AzureChatOpenAI.with_structured_output`.
|
|
545
|
+
However this does not prevent a user from directly passed in the parameter during
|
|
546
|
+
invocation.
|
|
547
|
+
|
|
548
|
+
By default, unless ``model_name="gpt-4o"`` is specified, then
|
|
549
|
+
'parallel_tools_calls' will be disabled.
|
|
550
|
+
"""
|
|
551
|
+
|
|
553
552
|
@classmethod
|
|
554
553
|
def get_lc_namespace(cls) -> List[str]:
|
|
555
554
|
"""Get the namespace of the langchain object."""
|
|
@@ -574,6 +573,13 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
574
573
|
if self.n > 1 and self.streaming:
|
|
575
574
|
raise ValueError("n must be 1 when streaming.")
|
|
576
575
|
|
|
576
|
+
if self.disabled_params is None:
|
|
577
|
+
# As of 09-17-2024 'parallel_tool_calls' param is only supported for gpt-4o.
|
|
578
|
+
if self.model_name and self.model_name == "gpt-4o":
|
|
579
|
+
pass
|
|
580
|
+
else:
|
|
581
|
+
self.disabled_params = {"parallel_tool_calls": None}
|
|
582
|
+
|
|
577
583
|
# Check OPENAI_ORGANIZATION for backwards compatibility.
|
|
578
584
|
self.openai_organization = (
|
|
579
585
|
self.openai_organization
|
|
@@ -634,311 +640,6 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
634
640
|
self.async_client = self.root_async_client.chat.completions
|
|
635
641
|
return self
|
|
636
642
|
|
|
637
|
-
def bind_tools(
|
|
638
|
-
self,
|
|
639
|
-
tools: Sequence[Union[Dict[str, Any], Type, Callable, BaseTool]],
|
|
640
|
-
*,
|
|
641
|
-
tool_choice: Optional[
|
|
642
|
-
Union[dict, str, Literal["auto", "none", "required", "any"], bool]
|
|
643
|
-
] = None,
|
|
644
|
-
**kwargs: Any,
|
|
645
|
-
) -> Runnable[LanguageModelInput, BaseMessage]:
|
|
646
|
-
# As of 05/2024 Azure OpenAI doesn't support tool_choice="required".
|
|
647
|
-
# TODO: Update this condition once tool_choice="required" is supported.
|
|
648
|
-
if tool_choice in ("any", "required", True):
|
|
649
|
-
if len(tools) > 1:
|
|
650
|
-
raise ValueError(
|
|
651
|
-
f"Azure OpenAI does not currently support {tool_choice=}. Should "
|
|
652
|
-
f"be one of 'auto', 'none', or the name of the tool to call."
|
|
653
|
-
)
|
|
654
|
-
else:
|
|
655
|
-
tool_choice = convert_to_openai_tool(tools[0])["function"]["name"]
|
|
656
|
-
return super().bind_tools(tools, tool_choice=tool_choice, **kwargs)
|
|
657
|
-
|
|
658
|
-
# TODO: Fix typing.
|
|
659
|
-
@overload # type: ignore[override]
|
|
660
|
-
def with_structured_output(
|
|
661
|
-
self,
|
|
662
|
-
schema: Optional[_DictOrPydanticClass] = None,
|
|
663
|
-
*,
|
|
664
|
-
method: Literal["function_calling", "json_mode"] = "function_calling",
|
|
665
|
-
include_raw: Literal[True] = True,
|
|
666
|
-
**kwargs: Any,
|
|
667
|
-
) -> Runnable[LanguageModelInput, _AllReturnType]: ...
|
|
668
|
-
|
|
669
|
-
@overload
|
|
670
|
-
def with_structured_output(
|
|
671
|
-
self,
|
|
672
|
-
schema: Optional[_DictOrPydanticClass] = None,
|
|
673
|
-
*,
|
|
674
|
-
method: Literal["function_calling", "json_mode"] = "function_calling",
|
|
675
|
-
include_raw: Literal[False] = False,
|
|
676
|
-
**kwargs: Any,
|
|
677
|
-
) -> Runnable[LanguageModelInput, _DictOrPydantic]: ...
|
|
678
|
-
|
|
679
|
-
def with_structured_output(
|
|
680
|
-
self,
|
|
681
|
-
schema: Optional[_DictOrPydanticClass] = None,
|
|
682
|
-
*,
|
|
683
|
-
method: Literal["function_calling", "json_mode"] = "function_calling",
|
|
684
|
-
include_raw: bool = False,
|
|
685
|
-
**kwargs: Any,
|
|
686
|
-
) -> Runnable[LanguageModelInput, _DictOrPydantic]:
|
|
687
|
-
"""Model wrapper that returns outputs formatted to match the given schema.
|
|
688
|
-
|
|
689
|
-
Args:
|
|
690
|
-
schema:
|
|
691
|
-
The output schema. Can be passed in as:
|
|
692
|
-
- an OpenAI function/tool schema,
|
|
693
|
-
- a JSON Schema,
|
|
694
|
-
- a TypedDict class,
|
|
695
|
-
- or a Pydantic class.
|
|
696
|
-
If ``schema`` is a Pydantic class then the model output will be a
|
|
697
|
-
Pydantic instance of that class, and the model-generated fields will be
|
|
698
|
-
validated by the Pydantic class. Otherwise the model output will be a
|
|
699
|
-
dict and will not be validated. See :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`
|
|
700
|
-
for more on how to properly specify types and descriptions of
|
|
701
|
-
schema fields when specifying a Pydantic or TypedDict class.
|
|
702
|
-
method:
|
|
703
|
-
The method for steering model generation, either "function_calling"
|
|
704
|
-
or "json_mode". If "function_calling" then the schema will be converted
|
|
705
|
-
to an OpenAI function and the returned model will make use of the
|
|
706
|
-
function-calling API. If "json_mode" then OpenAI's JSON mode will be
|
|
707
|
-
used. Note that if using "json_mode" then you must include instructions
|
|
708
|
-
for formatting the output into the desired schema into the model call.
|
|
709
|
-
include_raw:
|
|
710
|
-
If False then only the parsed structured output is returned. If
|
|
711
|
-
an error occurs during model output parsing it will be raised. If True
|
|
712
|
-
then both the raw model response (a BaseMessage) and the parsed model
|
|
713
|
-
response will be returned. If an error occurs during output parsing it
|
|
714
|
-
will be caught and returned as well. The final output is always a dict
|
|
715
|
-
with keys "raw", "parsed", and "parsing_error".
|
|
716
|
-
|
|
717
|
-
Returns:
|
|
718
|
-
A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`.
|
|
719
|
-
|
|
720
|
-
If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs
|
|
721
|
-
an instance of ``schema`` (i.e., a Pydantic object).
|
|
722
|
-
|
|
723
|
-
Otherwise, if ``include_raw`` is False then Runnable outputs a dict.
|
|
724
|
-
|
|
725
|
-
If ``include_raw`` is True, then Runnable outputs a dict with keys:
|
|
726
|
-
- ``"raw"``: BaseMessage
|
|
727
|
-
- ``"parsed"``: None if there was a parsing error, otherwise the type depends on the ``schema`` as described above.
|
|
728
|
-
- ``"parsing_error"``: Optional[BaseException]
|
|
729
|
-
|
|
730
|
-
Example: schema=Pydantic class, method="function_calling", include_raw=False:
|
|
731
|
-
.. code-block:: python
|
|
732
|
-
|
|
733
|
-
from typing import Optional
|
|
734
|
-
|
|
735
|
-
from langchain_openai import AzureChatOpenAI
|
|
736
|
-
from pydantic import BaseModel, Field
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
class AnswerWithJustification(BaseModel):
|
|
740
|
-
'''An answer to the user question along with justification for the answer.'''
|
|
741
|
-
|
|
742
|
-
answer: str
|
|
743
|
-
# If we provide default values and/or descriptions for fields, these will be passed
|
|
744
|
-
# to the model. This is an important part of improving a model's ability to
|
|
745
|
-
# correctly return structured outputs.
|
|
746
|
-
justification: Optional[str] = Field(
|
|
747
|
-
default=None, description="A justification for the answer."
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
llm = AzureChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
|
|
752
|
-
structured_llm = llm.with_structured_output(AnswerWithJustification)
|
|
753
|
-
|
|
754
|
-
structured_llm.invoke(
|
|
755
|
-
"What weighs more a pound of bricks or a pound of feathers"
|
|
756
|
-
)
|
|
757
|
-
|
|
758
|
-
# -> AnswerWithJustification(
|
|
759
|
-
# answer='They weigh the same',
|
|
760
|
-
# justification='Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ.'
|
|
761
|
-
# )
|
|
762
|
-
|
|
763
|
-
Example: schema=Pydantic class, method="function_calling", include_raw=True:
|
|
764
|
-
.. code-block:: python
|
|
765
|
-
|
|
766
|
-
from langchain_openai import AzureChatOpenAI
|
|
767
|
-
from pydantic import BaseModel
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
class AnswerWithJustification(BaseModel):
|
|
771
|
-
'''An answer to the user question along with justification for the answer.'''
|
|
772
|
-
|
|
773
|
-
answer: str
|
|
774
|
-
justification: str
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
llm = AzureChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
|
|
778
|
-
structured_llm = llm.with_structured_output(
|
|
779
|
-
AnswerWithJustification, include_raw=True
|
|
780
|
-
)
|
|
781
|
-
|
|
782
|
-
structured_llm.invoke(
|
|
783
|
-
"What weighs more a pound of bricks or a pound of feathers"
|
|
784
|
-
)
|
|
785
|
-
# -> {
|
|
786
|
-
# 'raw': AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Ao02pnFYXD6GN1yzc0uXPsvF', 'function': {'arguments': '{"answer":"They weigh the same.","justification":"Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ."}', 'name': 'AnswerWithJustification'}, 'type': 'function'}]}),
|
|
787
|
-
# 'parsed': AnswerWithJustification(answer='They weigh the same.', justification='Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume or density of the objects may differ.'),
|
|
788
|
-
# 'parsing_error': None
|
|
789
|
-
# }
|
|
790
|
-
|
|
791
|
-
Example: schema=TypedDict class, method="function_calling", include_raw=False:
|
|
792
|
-
.. code-block:: python
|
|
793
|
-
|
|
794
|
-
# IMPORTANT: If you are using Python <=3.8, you need to import Annotated
|
|
795
|
-
# from typing_extensions, not from typing.
|
|
796
|
-
from typing_extensions import Annotated, TypedDict
|
|
797
|
-
|
|
798
|
-
from langchain_openai import AzureChatOpenAI
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
class AnswerWithJustification(TypedDict):
|
|
802
|
-
'''An answer to the user question along with justification for the answer.'''
|
|
803
|
-
|
|
804
|
-
answer: str
|
|
805
|
-
justification: Annotated[
|
|
806
|
-
Optional[str], None, "A justification for the answer."
|
|
807
|
-
]
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
llm = AzureChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
|
|
811
|
-
structured_llm = llm.with_structured_output(AnswerWithJustification)
|
|
812
|
-
|
|
813
|
-
structured_llm.invoke(
|
|
814
|
-
"What weighs more a pound of bricks or a pound of feathers"
|
|
815
|
-
)
|
|
816
|
-
# -> {
|
|
817
|
-
# 'answer': 'They weigh the same',
|
|
818
|
-
# 'justification': 'Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume and density of the two substances differ.'
|
|
819
|
-
# }
|
|
820
|
-
|
|
821
|
-
Example: schema=OpenAI function schema, method="function_calling", include_raw=False:
|
|
822
|
-
.. code-block:: python
|
|
823
|
-
|
|
824
|
-
from langchain_openai import AzureChatOpenAI
|
|
825
|
-
|
|
826
|
-
oai_schema = {
|
|
827
|
-
'name': 'AnswerWithJustification',
|
|
828
|
-
'description': 'An answer to the user question along with justification for the answer.',
|
|
829
|
-
'parameters': {
|
|
830
|
-
'type': 'object',
|
|
831
|
-
'properties': {
|
|
832
|
-
'answer': {'type': 'string'},
|
|
833
|
-
'justification': {'description': 'A justification for the answer.', 'type': 'string'}
|
|
834
|
-
},
|
|
835
|
-
'required': ['answer']
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
llm = AzureChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
|
|
840
|
-
structured_llm = llm.with_structured_output(oai_schema)
|
|
841
|
-
|
|
842
|
-
structured_llm.invoke(
|
|
843
|
-
"What weighs more a pound of bricks or a pound of feathers"
|
|
844
|
-
)
|
|
845
|
-
# -> {
|
|
846
|
-
# 'answer': 'They weigh the same',
|
|
847
|
-
# 'justification': 'Both a pound of bricks and a pound of feathers weigh one pound. The weight is the same, but the volume and density of the two substances differ.'
|
|
848
|
-
# }
|
|
849
|
-
|
|
850
|
-
Example: schema=Pydantic class, method="json_mode", include_raw=True:
|
|
851
|
-
.. code-block::
|
|
852
|
-
|
|
853
|
-
from langchain_openai import AzureChatOpenAI
|
|
854
|
-
from pydantic import BaseModel
|
|
855
|
-
|
|
856
|
-
class AnswerWithJustification(BaseModel):
|
|
857
|
-
answer: str
|
|
858
|
-
justification: str
|
|
859
|
-
|
|
860
|
-
llm = AzureChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
|
|
861
|
-
structured_llm = llm.with_structured_output(
|
|
862
|
-
AnswerWithJustification,
|
|
863
|
-
method="json_mode",
|
|
864
|
-
include_raw=True
|
|
865
|
-
)
|
|
866
|
-
|
|
867
|
-
structured_llm.invoke(
|
|
868
|
-
"Answer the following question. "
|
|
869
|
-
"Make sure to return a JSON blob with keys 'answer' and 'justification'.\n\n"
|
|
870
|
-
"What's heavier a pound of bricks or a pound of feathers?"
|
|
871
|
-
)
|
|
872
|
-
# -> {
|
|
873
|
-
# 'raw': AIMessage(content='{\n "answer": "They are both the same weight.",\n "justification": "Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight." \n}'),
|
|
874
|
-
# 'parsed': AnswerWithJustification(answer='They are both the same weight.', justification='Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight.'),
|
|
875
|
-
# 'parsing_error': None
|
|
876
|
-
# }
|
|
877
|
-
|
|
878
|
-
Example: schema=None, method="json_mode", include_raw=True:
|
|
879
|
-
.. code-block::
|
|
880
|
-
|
|
881
|
-
structured_llm = llm.with_structured_output(method="json_mode", include_raw=True)
|
|
882
|
-
|
|
883
|
-
structured_llm.invoke(
|
|
884
|
-
"Answer the following question. "
|
|
885
|
-
"Make sure to return a JSON blob with keys 'answer' and 'justification'.\n\n"
|
|
886
|
-
"What's heavier a pound of bricks or a pound of feathers?"
|
|
887
|
-
)
|
|
888
|
-
# -> {
|
|
889
|
-
# 'raw': AIMessage(content='{\n "answer": "They are both the same weight.",\n "justification": "Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight." \n}'),
|
|
890
|
-
# 'parsed': {
|
|
891
|
-
# 'answer': 'They are both the same weight.',
|
|
892
|
-
# 'justification': 'Both a pound of bricks and a pound of feathers weigh one pound. The difference lies in the volume and density of the materials, not the weight.'
|
|
893
|
-
# },
|
|
894
|
-
# 'parsing_error': None
|
|
895
|
-
# }
|
|
896
|
-
""" # noqa: E501
|
|
897
|
-
if kwargs:
|
|
898
|
-
raise ValueError(f"Received unsupported arguments {kwargs}")
|
|
899
|
-
is_pydantic_schema = _is_pydantic_class(schema)
|
|
900
|
-
if method == "function_calling":
|
|
901
|
-
if schema is None:
|
|
902
|
-
raise ValueError(
|
|
903
|
-
"schema must be specified when method is 'function_calling'. "
|
|
904
|
-
"Received None."
|
|
905
|
-
)
|
|
906
|
-
tool_name = convert_to_openai_tool(schema)["function"]["name"]
|
|
907
|
-
llm = self.bind_tools([schema], tool_choice=tool_name)
|
|
908
|
-
if is_pydantic_schema:
|
|
909
|
-
output_parser: OutputParserLike = PydanticToolsParser(
|
|
910
|
-
tools=[schema], # type: ignore[list-item]
|
|
911
|
-
first_tool_only=True, # type: ignore[list-item]
|
|
912
|
-
)
|
|
913
|
-
else:
|
|
914
|
-
output_parser = JsonOutputKeyToolsParser(
|
|
915
|
-
key_name=tool_name, first_tool_only=True
|
|
916
|
-
)
|
|
917
|
-
elif method == "json_mode":
|
|
918
|
-
llm = self.bind(response_format={"type": "json_object"})
|
|
919
|
-
output_parser = (
|
|
920
|
-
PydanticOutputParser(pydantic_object=schema) # type: ignore[arg-type]
|
|
921
|
-
if is_pydantic_schema
|
|
922
|
-
else JsonOutputParser()
|
|
923
|
-
)
|
|
924
|
-
else:
|
|
925
|
-
raise ValueError(
|
|
926
|
-
f"Unrecognized method argument. Expected one of 'function_calling' or "
|
|
927
|
-
f"'json_mode'. Received: '{method}'"
|
|
928
|
-
)
|
|
929
|
-
|
|
930
|
-
if include_raw:
|
|
931
|
-
parser_assign = RunnablePassthrough.assign(
|
|
932
|
-
parsed=itemgetter("raw") | output_parser, parsing_error=lambda _: None
|
|
933
|
-
)
|
|
934
|
-
parser_none = RunnablePassthrough.assign(parsed=lambda _: None)
|
|
935
|
-
parser_with_fallback = parser_assign.with_fallbacks(
|
|
936
|
-
[parser_none], exception_key="parsing_error"
|
|
937
|
-
)
|
|
938
|
-
return RunnableMap(raw=llm) | parser_with_fallback
|
|
939
|
-
else:
|
|
940
|
-
return llm | output_parser
|
|
941
|
-
|
|
942
643
|
@property
|
|
943
644
|
def _identifying_params(self) -> Dict[str, Any]:
|
|
944
645
|
"""Get the identifying parameters."""
|
|
@@ -980,6 +681,8 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
980
681
|
response: Union[dict, openai.BaseModel],
|
|
981
682
|
generation_info: Optional[Dict] = None,
|
|
982
683
|
) -> ChatResult:
|
|
684
|
+
chat_result = super()._create_chat_result(response, generation_info)
|
|
685
|
+
|
|
983
686
|
if not isinstance(response, dict):
|
|
984
687
|
response = response.model_dump()
|
|
985
688
|
for res in response["choices"]:
|
|
@@ -988,7 +691,6 @@ class AzureChatOpenAI(BaseChatOpenAI):
|
|
|
988
691
|
"Azure has not provided the response due to a content filter "
|
|
989
692
|
"being triggered"
|
|
990
693
|
)
|
|
991
|
-
chat_result = super()._create_chat_result(response, generation_info)
|
|
992
694
|
|
|
993
695
|
if "model" in response:
|
|
994
696
|
model = response["model"]
|
|
@@ -33,6 +33,7 @@ from urllib.parse import urlparse
|
|
|
33
33
|
|
|
34
34
|
import openai
|
|
35
35
|
import tiktoken
|
|
36
|
+
from langchain_core._api.deprecation import deprecated
|
|
36
37
|
from langchain_core.callbacks import (
|
|
37
38
|
AsyncCallbackManagerForLLMRun,
|
|
38
39
|
CallbackManagerForLLMRun,
|
|
@@ -65,7 +66,6 @@ from langchain_core.messages import (
|
|
|
65
66
|
from langchain_core.messages.ai import UsageMetadata
|
|
66
67
|
from langchain_core.messages.tool import tool_call_chunk
|
|
67
68
|
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser
|
|
68
|
-
from langchain_core.output_parsers.base import OutputParserLike
|
|
69
69
|
from langchain_core.output_parsers.openai_tools import (
|
|
70
70
|
JsonOutputKeyToolsParser,
|
|
71
71
|
PydanticToolsParser,
|
|
@@ -332,6 +332,33 @@ def _convert_chunk_to_generation_chunk(
|
|
|
332
332
|
return generation_chunk
|
|
333
333
|
|
|
334
334
|
|
|
335
|
+
def _update_token_usage(
|
|
336
|
+
overall_token_usage: Union[int, dict], new_usage: Union[int, dict]
|
|
337
|
+
) -> Union[int, dict]:
|
|
338
|
+
# Token usage is either ints or dictionaries
|
|
339
|
+
# `reasoning_tokens` is nested inside `completion_tokens_details`
|
|
340
|
+
if isinstance(new_usage, int):
|
|
341
|
+
if not isinstance(overall_token_usage, int):
|
|
342
|
+
raise ValueError(
|
|
343
|
+
f"Got different types for token usage: "
|
|
344
|
+
f"{type(new_usage)} and {type(overall_token_usage)}"
|
|
345
|
+
)
|
|
346
|
+
return new_usage + overall_token_usage
|
|
347
|
+
elif isinstance(new_usage, dict):
|
|
348
|
+
if not isinstance(overall_token_usage, dict):
|
|
349
|
+
raise ValueError(
|
|
350
|
+
f"Got different types for token usage: "
|
|
351
|
+
f"{type(new_usage)} and {type(overall_token_usage)}"
|
|
352
|
+
)
|
|
353
|
+
return {
|
|
354
|
+
k: _update_token_usage(overall_token_usage.get(k, 0), v)
|
|
355
|
+
for k, v in new_usage.items()
|
|
356
|
+
}
|
|
357
|
+
else:
|
|
358
|
+
warnings.warn(f"Unexpected type for token usage: {type(new_usage)}")
|
|
359
|
+
return new_usage
|
|
360
|
+
|
|
361
|
+
|
|
335
362
|
class _FunctionCall(TypedDict):
|
|
336
363
|
name: str
|
|
337
364
|
|
|
@@ -427,6 +454,23 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
427
454
|
making requests to OpenAI compatible APIs, such as vLLM."""
|
|
428
455
|
include_response_headers: bool = False
|
|
429
456
|
"""Whether to include response headers in the output message response_metadata."""
|
|
457
|
+
disabled_params: Optional[Dict[str, Any]] = Field(default=None)
|
|
458
|
+
"""Parameters of the OpenAI client or chat.completions endpoint that should be
|
|
459
|
+
disabled for the given model.
|
|
460
|
+
|
|
461
|
+
Should be specified as ``{"param": None | ['val1', 'val2']}`` where the key is the
|
|
462
|
+
parameter and the value is either None, meaning that parameter should never be
|
|
463
|
+
used, or it's a list of disabled values for the parameter.
|
|
464
|
+
|
|
465
|
+
For example, older models may not support the 'parallel_tool_calls' parameter at
|
|
466
|
+
all, in which case ``disabled_params={"parallel_tool_calls: None}`` can ben passed
|
|
467
|
+
in.
|
|
468
|
+
|
|
469
|
+
If a parameter is disabled then it will not be used by default in any methods, e.g.
|
|
470
|
+
in :meth:`~langchain_openai.chat_models.base.ChatOpenAI.with_structured_output`.
|
|
471
|
+
However this does not prevent a user from directly passed in the parameter during
|
|
472
|
+
invocation.
|
|
473
|
+
"""
|
|
430
474
|
|
|
431
475
|
model_config = ConfigDict(populate_by_name=True)
|
|
432
476
|
|
|
@@ -545,7 +589,9 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
545
589
|
if token_usage is not None:
|
|
546
590
|
for k, v in token_usage.items():
|
|
547
591
|
if k in overall_token_usage:
|
|
548
|
-
overall_token_usage[k]
|
|
592
|
+
overall_token_usage[k] = _update_token_usage(
|
|
593
|
+
overall_token_usage[k], v
|
|
594
|
+
)
|
|
549
595
|
else:
|
|
550
596
|
overall_token_usage[k] = v
|
|
551
597
|
if system_fingerprint is None:
|
|
@@ -741,7 +787,7 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
741
787
|
)
|
|
742
788
|
return
|
|
743
789
|
if self.include_response_headers:
|
|
744
|
-
raw_response = self.async_client.with_raw_response.create(**payload)
|
|
790
|
+
raw_response = await self.async_client.with_raw_response.create(**payload)
|
|
745
791
|
response = raw_response.parse()
|
|
746
792
|
base_generation_info = {"headers": dict(raw_response.headers)}
|
|
747
793
|
else:
|
|
@@ -938,6 +984,11 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
938
984
|
num_tokens += 3
|
|
939
985
|
return num_tokens
|
|
940
986
|
|
|
987
|
+
@deprecated(
|
|
988
|
+
since="0.2.1",
|
|
989
|
+
alternative="langchain_openai.chat_models.base.ChatOpenAI.bind_tools",
|
|
990
|
+
removal="0.3.0",
|
|
991
|
+
)
|
|
941
992
|
def bind_functions(
|
|
942
993
|
self,
|
|
943
994
|
functions: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]],
|
|
@@ -1005,22 +1056,18 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
1005
1056
|
|
|
1006
1057
|
Assumes model is compatible with OpenAI tool-calling API.
|
|
1007
1058
|
|
|
1008
|
-
.. versionchanged:: 0.1.21
|
|
1009
|
-
|
|
1010
|
-
Support for ``strict`` argument added.
|
|
1011
|
-
|
|
1012
1059
|
Args:
|
|
1013
1060
|
tools: A list of tool definitions to bind to this chat model.
|
|
1014
1061
|
Supports any tool definition handled by
|
|
1015
1062
|
:meth:`langchain_core.utils.function_calling.convert_to_openai_tool`.
|
|
1016
|
-
tool_choice: Which tool to require the model to call.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1063
|
+
tool_choice: Which tool to require the model to call. Options are:
|
|
1064
|
+
|
|
1065
|
+
- str of the form ``"<<tool_name>>"``: calls <<tool_name>> tool.
|
|
1066
|
+
- ``"auto"``: automatically selects a tool (including no tool).
|
|
1067
|
+
- ``"none"``: does not call a tool.
|
|
1068
|
+
- ``"any"`` or ``"required"`` or ``True``: force at least one tool to be called.
|
|
1069
|
+
- dict of the form ``{"type": "function", "function": {"name": <<tool_name>>}}``: calls <<tool_name>> tool.
|
|
1070
|
+
- ``False`` or ``None``: no effect, default OpenAI behavior.
|
|
1024
1071
|
strict: If True, model output is guaranteed to exactly match the JSON Schema
|
|
1025
1072
|
provided in the tool definition. If True, the input schema will be
|
|
1026
1073
|
validated according to
|
|
@@ -1028,11 +1075,13 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
1028
1075
|
If False, input schema will not be validated and model output will not
|
|
1029
1076
|
be validated.
|
|
1030
1077
|
If None, ``strict`` argument will not be passed to the model.
|
|
1078
|
+
kwargs: Any additional parameters are passed directly to
|
|
1079
|
+
:meth:`~langchain_openai.chat_models.base.ChatOpenAI.bind`.
|
|
1031
1080
|
|
|
1032
|
-
|
|
1081
|
+
.. versionchanged:: 0.1.21
|
|
1082
|
+
|
|
1083
|
+
Support for ``strict`` argument added.
|
|
1033
1084
|
|
|
1034
|
-
kwargs: Any additional parameters are passed directly to
|
|
1035
|
-
``self.bind(**kwargs)``.
|
|
1036
1085
|
""" # noqa: E501
|
|
1037
1086
|
|
|
1038
1087
|
formatted_tools = [
|
|
@@ -1369,14 +1418,13 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
1369
1418
|
"Received None."
|
|
1370
1419
|
)
|
|
1371
1420
|
tool_name = convert_to_openai_tool(schema)["function"]["name"]
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
tool_choice=tool_name,
|
|
1375
|
-
parallel_tool_calls=False,
|
|
1376
|
-
strict=strict,
|
|
1421
|
+
bind_kwargs = self._filter_disabled_params(
|
|
1422
|
+
tool_choice=tool_name, parallel_tool_calls=False, strict=strict
|
|
1377
1423
|
)
|
|
1424
|
+
|
|
1425
|
+
llm = self.bind_tools([schema], **bind_kwargs)
|
|
1378
1426
|
if is_pydantic_schema:
|
|
1379
|
-
output_parser:
|
|
1427
|
+
output_parser: Runnable = PydanticToolsParser(
|
|
1380
1428
|
tools=[schema], # type: ignore[list-item]
|
|
1381
1429
|
first_tool_only=True, # type: ignore[list-item]
|
|
1382
1430
|
)
|
|
@@ -1400,11 +1448,12 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
1400
1448
|
strict = strict if strict is not None else True
|
|
1401
1449
|
response_format = _convert_to_openai_response_format(schema, strict=strict)
|
|
1402
1450
|
llm = self.bind(response_format=response_format)
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1451
|
+
if is_pydantic_schema:
|
|
1452
|
+
output_parser = _oai_structured_outputs_parser.with_types(
|
|
1453
|
+
output_type=cast(type, schema)
|
|
1454
|
+
)
|
|
1455
|
+
else:
|
|
1456
|
+
output_parser = JsonOutputParser()
|
|
1408
1457
|
else:
|
|
1409
1458
|
raise ValueError(
|
|
1410
1459
|
f"Unrecognized method argument. Expected one of 'function_calling' or "
|
|
@@ -1423,6 +1472,21 @@ class BaseChatOpenAI(BaseChatModel):
|
|
|
1423
1472
|
else:
|
|
1424
1473
|
return llm | output_parser
|
|
1425
1474
|
|
|
1475
|
+
def _filter_disabled_params(self, **kwargs: Any) -> Dict[str, Any]:
|
|
1476
|
+
if not self.disabled_params:
|
|
1477
|
+
return kwargs
|
|
1478
|
+
filtered = {}
|
|
1479
|
+
for k, v in kwargs.items():
|
|
1480
|
+
# Skip param
|
|
1481
|
+
if k in self.disabled_params and (
|
|
1482
|
+
self.disabled_params[k] is None or v in self.disabled_params[k]
|
|
1483
|
+
):
|
|
1484
|
+
continue
|
|
1485
|
+
# Keep param
|
|
1486
|
+
else:
|
|
1487
|
+
filtered[k] = v
|
|
1488
|
+
return filtered
|
|
1489
|
+
|
|
1426
1490
|
|
|
1427
1491
|
class ChatOpenAI(BaseChatOpenAI):
|
|
1428
1492
|
"""OpenAI chat model integration.
|
|
@@ -2081,7 +2145,7 @@ def _oai_structured_outputs_parser(ai_msg: AIMessage) -> PydanticBaseModel:
|
|
|
2081
2145
|
else:
|
|
2082
2146
|
raise ValueError(
|
|
2083
2147
|
"Structured Output response does not have a 'parsed' field nor a 'refusal' "
|
|
2084
|
-
"field."
|
|
2148
|
+
"field. Received message:\n\n{ai_msg}"
|
|
2085
2149
|
)
|
|
2086
2150
|
|
|
2087
2151
|
|
|
@@ -254,14 +254,14 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
|
|
254
254
|
retry_max_seconds: int = 20
|
|
255
255
|
"""Max number of seconds to wait between retries"""
|
|
256
256
|
http_client: Union[Any, None] = None
|
|
257
|
-
"""Optional httpx.Client. Only used for sync invocations. Must specify
|
|
257
|
+
"""Optional httpx.Client. Only used for sync invocations. Must specify
|
|
258
258
|
http_async_client as well if you'd like a custom client for async invocations.
|
|
259
259
|
"""
|
|
260
260
|
http_async_client: Union[Any, None] = None
|
|
261
|
-
"""Optional httpx.AsyncClient. Only used for async invocations. Must specify
|
|
261
|
+
"""Optional httpx.AsyncClient. Only used for async invocations. Must specify
|
|
262
262
|
http_client as well if you'd like a custom client for sync invocations."""
|
|
263
263
|
check_embedding_ctx_length: bool = True
|
|
264
|
-
"""Whether to check the token length of inputs and automatically split inputs
|
|
264
|
+
"""Whether to check the token length of inputs and automatically split inputs
|
|
265
265
|
longer than embedding_ctx_length."""
|
|
266
266
|
|
|
267
267
|
model_config = ConfigDict(
|
|
@@ -558,7 +558,7 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
|
|
558
558
|
return [e if e is not None else await empty_embedding() for e in embeddings]
|
|
559
559
|
|
|
560
560
|
def embed_documents(
|
|
561
|
-
self, texts: List[str], chunk_size:
|
|
561
|
+
self, texts: List[str], chunk_size: int | None = None
|
|
562
562
|
) -> List[List[float]]:
|
|
563
563
|
"""Call out to OpenAI's embedding endpoint for embedding search docs.
|
|
564
564
|
|
|
@@ -570,10 +570,13 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
|
|
570
570
|
Returns:
|
|
571
571
|
List of embeddings, one for each text.
|
|
572
572
|
"""
|
|
573
|
+
chunk_size_ = chunk_size or self.chunk_size
|
|
573
574
|
if not self.check_embedding_ctx_length:
|
|
574
575
|
embeddings: List[List[float]] = []
|
|
575
|
-
for
|
|
576
|
-
response = self.client.create(
|
|
576
|
+
for i in range(0, len(texts), self.chunk_size):
|
|
577
|
+
response = self.client.create(
|
|
578
|
+
input=texts[i : i + chunk_size_], **self._invocation_params
|
|
579
|
+
)
|
|
577
580
|
if not isinstance(response, dict):
|
|
578
581
|
response = response.dict()
|
|
579
582
|
embeddings.extend(r["embedding"] for r in response["data"])
|
|
@@ -585,7 +588,7 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
|
|
585
588
|
return self._get_len_safe_embeddings(texts, engine=engine)
|
|
586
589
|
|
|
587
590
|
async def aembed_documents(
|
|
588
|
-
self, texts: List[str], chunk_size:
|
|
591
|
+
self, texts: List[str], chunk_size: int | None = None
|
|
589
592
|
) -> List[List[float]]:
|
|
590
593
|
"""Call out to OpenAI's embedding endpoint async for embedding search docs.
|
|
591
594
|
|
|
@@ -597,11 +600,12 @@ class OpenAIEmbeddings(BaseModel, Embeddings):
|
|
|
597
600
|
Returns:
|
|
598
601
|
List of embeddings, one for each text.
|
|
599
602
|
"""
|
|
603
|
+
chunk_size_ = chunk_size or self.chunk_size
|
|
600
604
|
if not self.check_embedding_ctx_length:
|
|
601
605
|
embeddings: List[List[float]] = []
|
|
602
|
-
for
|
|
606
|
+
for i in range(0, len(texts), chunk_size_):
|
|
603
607
|
response = await self.async_client.create(
|
|
604
|
-
input=
|
|
608
|
+
input=texts[i : i + chunk_size_], **self._invocation_params
|
|
605
609
|
)
|
|
606
610
|
if not isinstance(response, dict):
|
|
607
611
|
response = response.dict()
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "langchain-openai"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "An integration package connecting OpenAI and LangChain"
|
|
9
9
|
authors = []
|
|
10
10
|
readme = "README.md"
|
|
@@ -23,7 +23,7 @@ ignore_missing_imports = true
|
|
|
23
23
|
|
|
24
24
|
[tool.poetry.dependencies]
|
|
25
25
|
python = ">=3.9,<4.0"
|
|
26
|
-
langchain-core =
|
|
26
|
+
langchain-core = "^0.3"
|
|
27
27
|
openai = "^1.40.0"
|
|
28
28
|
tiktoken = ">=0.7,<1"
|
|
29
29
|
|
|
@@ -41,6 +41,7 @@ omit = [ "tests/*",]
|
|
|
41
41
|
addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5 --cov=langchain_openai"
|
|
42
42
|
markers = [ "requires: mark tests as requiring a specific library", "asyncio: mark tests as requiring asyncio", "compile: mark placeholder test used to compile integration tests without running them", "scheduled: mark tests to run in scheduled testing",]
|
|
43
43
|
asyncio_mode = "auto"
|
|
44
|
+
filterwarnings = [ "ignore::langchain_core._api.beta_decorator.LangChainBetaWarning",]
|
|
44
45
|
|
|
45
46
|
[tool.poetry.group.test]
|
|
46
47
|
optional = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/chat_models/__init__.py
RENAMED
|
File without changes
|
{langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/embeddings/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/output_parsers/__init__.py
RENAMED
|
File without changes
|
{langchain_openai-0.2.0.dev2 → langchain_openai-0.2.1}/langchain_openai/output_parsers/tools.py
RENAMED
|
File without changes
|
|
File without changes
|