Source code for langchain_community.chat_models.openai
"""OpenAI chat wrapper."""from__future__importannotationsimportloggingimportosimportsysimportwarningsfromtypingimport(TYPE_CHECKING,Any,AsyncIterator,Callable,Dict,Iterator,List,Mapping,Optional,Sequence,Tuple,Type,Union,)fromlangchain_core._api.deprecationimportdeprecatedfromlangchain_core.callbacksimport(AsyncCallbackManagerForLLMRun,CallbackManagerForLLMRun,)fromlangchain_core.language_modelsimportLanguageModelInputfromlangchain_core.language_models.chat_modelsimport(BaseChatModel,agenerate_from_stream,generate_from_stream,)fromlangchain_core.language_models.llmsimportcreate_base_retry_decoratorfromlangchain_core.messagesimport(AIMessageChunk,BaseMessage,BaseMessageChunk,ChatMessageChunk,FunctionMessageChunk,HumanMessageChunk,SystemMessageChunk,ToolMessageChunk,)fromlangchain_core.outputsimportChatGeneration,ChatGenerationChunk,ChatResultfromlangchain_core.runnablesimportRunnablefromlangchain_core.toolsimportBaseToolfromlangchain_core.utilsimport(get_from_dict_or_env,get_pydantic_field_names,pre_init,)frompydanticimportBaseModel,ConfigDict,Field,model_validatorfromlangchain_community.adapters.openaiimport(convert_dict_to_message,convert_message_to_dict,)fromlangchain_community.utils.openaiimportis_openai_v1ifTYPE_CHECKING:importtiktokenlogger=logging.getLogger(__name__)def_import_tiktoken()->Any:try:importtiktokenexceptImportError:raiseImportError("Could not import tiktoken python package. ""This is needed in order to calculate get_token_ids. ""Please install it with `pip install tiktoken`.")returntiktokendef_create_retry_decorator(llm:ChatOpenAI,run_manager:Optional[Union[AsyncCallbackManagerForLLMRun,CallbackManagerForLLMRun]]=None,)->Callable[[Any],Any]:importopenaierrors=[openai.error.Timeout,# type: ignore[attr-defined]openai.error.APIError,# type: ignore[attr-defined]openai.error.APIConnectionError,# type: ignore[attr-defined]openai.error.RateLimitError,# type: ignore[attr-defined]openai.error.ServiceUnavailableError,# type: ignore[attr-defined]]returncreate_base_retry_decorator(error_types=errors,max_retries=llm.max_retries,run_manager=run_manager)
[docs]asyncdefacompletion_with_retry(llm:ChatOpenAI,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,**kwargs:Any,)->Any:"""Use tenacity to retry the async completion call."""ifis_openai_v1():returnawaitllm.async_client.create(**kwargs)retry_decorator=_create_retry_decorator(llm,run_manager=run_manager)@retry_decoratorasyncdef_completion_with_retry(**kwargs:Any)->Any:# Use OpenAI's async api https://github.com/openai/openai-python#async-apireturnawaitllm.client.acreate(**kwargs)returnawait_completion_with_retry(**kwargs)
def_convert_delta_to_message_chunk(_dict:Mapping[str,Any],default_class:Type[BaseMessageChunk])->BaseMessageChunk:role=_dict.get("role")content=_dict.get("content")or""additional_kwargs:Dict={}if_dict.get("function_call"):function_call=dict(_dict["function_call"])if"name"infunction_callandfunction_call["name"]isNone:function_call["name"]=""additional_kwargs["function_call"]=function_callif_dict.get("tool_calls"):additional_kwargs["tool_calls"]=_dict["tool_calls"]ifrole=="user"ordefault_class==HumanMessageChunk:returnHumanMessageChunk(content=content)elifrole=="assistant"ordefault_class==AIMessageChunk:returnAIMessageChunk(content=content,additional_kwargs=additional_kwargs)elifrole=="system"ordefault_class==SystemMessageChunk:returnSystemMessageChunk(content=content)elifrole=="function"ordefault_class==FunctionMessageChunk:returnFunctionMessageChunk(content=content,name=_dict["name"])elifrole=="tool"ordefault_class==ToolMessageChunk:returnToolMessageChunk(content=content,tool_call_id=_dict["tool_call_id"])elifroleordefault_class==ChatMessageChunk:returnChatMessageChunk(content=content,role=role)# type: ignore[arg-type]else:returndefault_class(content=content)# type: ignore[call-arg]def_update_token_usage(overall_token_usage:Union[int,dict],new_usage:Union[int,dict])->Union[int,dict]:# Token usage is either ints or dictionaries# `reasoning_tokens` is nested inside `completion_tokens_details`ifisinstance(new_usage,int):ifnotisinstance(overall_token_usage,int):raiseValueError(f"Got different types for token usage: "f"{type(new_usage)} and {type(overall_token_usage)}")returnnew_usage+overall_token_usageelifisinstance(new_usage,dict):ifnotisinstance(overall_token_usage,dict):raiseValueError(f"Got different types for token usage: "f"{type(new_usage)} and {type(overall_token_usage)}")return{k:_update_token_usage(overall_token_usage.get(k,0),v)fork,vinnew_usage.items()}else:warnings.warn(f"Unexpected type for token usage: {type(new_usage)}")returnnew_usage
[docs]@deprecated(since="0.0.10",removal="1.0",alternative_import="langchain_openai.ChatOpenAI")classChatOpenAI(BaseChatModel):"""`OpenAI` Chat large language models API. To use, you should have the ``openai`` python package installed, and the environment variable ``OPENAI_API_KEY`` set with your API key. Any parameters that are valid to be passed to the openai.create call can be passed in, even if not explicitly saved on this class. Example: .. code-block:: python from langchain_community.chat_models import ChatOpenAI openai = ChatOpenAI(model="gpt-3.5-turbo") """@propertydeflc_secrets(self)->Dict[str,str]:return{"openai_api_key":"OPENAI_API_KEY"}@classmethoddefget_lc_namespace(cls)->List[str]:"""Get the namespace of the langchain object."""return["langchain","chat_models","openai"]@propertydeflc_attributes(self)->Dict[str,Any]:attributes:Dict[str,Any]={}ifself.openai_organization:attributes["openai_organization"]=self.openai_organizationifself.openai_api_base:attributes["openai_api_base"]=self.openai_api_baseifself.openai_proxy:attributes["openai_proxy"]=self.openai_proxyreturnattributes@classmethoddefis_lc_serializable(cls)->bool:"""Return whether this model can be serialized by Langchain."""returnTrueclient:Any=Field(default=None,exclude=True)#: :meta private:async_client:Any=Field(default=None,exclude=True)#: :meta private:model_name:str=Field(default="gpt-3.5-turbo",alias="model")"""Model name to use."""temperature:float=0.7"""What sampling temperature to use."""model_kwargs:Dict[str,Any]=Field(default_factory=dict)"""Holds any model parameters valid for `create` call not explicitly specified."""# When updating this to use a SecretStr# Check for classes that derive from this class (as some of them# may assume openai_api_key is a str)openai_api_key:Optional[str]=Field(default=None,alias="api_key")"""Automatically inferred from env var `OPENAI_API_KEY` if not provided."""openai_api_base:Optional[str]=Field(default=None,alias="base_url")"""Base URL path for API requests, leave blank if not using a proxy or service emulator."""openai_organization:Optional[str]=Field(default=None,alias="organization")"""Automatically inferred from env var `OPENAI_ORG_ID` if not provided."""# to support explicit proxy for OpenAIopenai_proxy:Optional[str]=Nonerequest_timeout:Union[float,Tuple[float,float],Any,None]=Field(default=None,alias="timeout")"""Timeout for requests to OpenAI completion API. Can be float, httpx.Timeout or None."""max_retries:int=Field(default=2)"""Maximum number of retries to make when generating."""streaming:bool=False"""Whether to stream the results or not."""n:int=1"""Number of chat completions to generate for each prompt."""max_tokens:Optional[int]=None"""Maximum number of tokens to generate."""tiktoken_model_name:Optional[str]=None"""The model name to pass to tiktoken when using this class. Tiktoken is used to count the number of tokens in documents to constrain them to be under a certain limit. By default, when set to None, this will be the same as the embedding model name. However, there are some cases where you may want to use this Embedding class with a model name not supported by tiktoken. This can include when using Azure embeddings or when using one of the many model providers that expose an OpenAI-like API but with different models. In those cases, in order to avoid erroring when tiktoken is called, you can specify a model name to use here."""default_headers:Union[Mapping[str,str],None]=Nonedefault_query:Union[Mapping[str,object],None]=None# Configure a custom httpx client. See the# [httpx documentation](https://www.python-httpx.org/api/#client) for more details.http_client:Union[Any,None]=None"""Optional httpx.Client."""model_config=ConfigDict(populate_by_name=True,)@model_validator(mode="before")@classmethoddefbuild_extra(cls,values:Dict[str,Any])->Any:"""Build extra kwargs from additional params that were passed in."""all_required_field_names=get_pydantic_field_names(cls)extra=values.get("model_kwargs",{})forfield_nameinlist(values):iffield_nameinextra:raiseValueError(f"Found {field_name} supplied twice.")iffield_namenotinall_required_field_names:logger.warning(f"""WARNING! {field_name} is not default parameter.{field_name} was transferred to model_kwargs. Please confirm that {field_name} is what you intended.""")extra[field_name]=values.pop(field_name)invalid_model_kwargs=all_required_field_names.intersection(extra.keys())ifinvalid_model_kwargs:raiseValueError(f"Parameters {invalid_model_kwargs} should be specified explicitly. "f"Instead they were passed in as part of `model_kwargs` parameter.")values["model_kwargs"]=extrareturnvalues
[docs]@pre_initdefvalidate_environment(cls,values:Dict)->Dict:"""Validate that api key and python package exists in environment."""ifvalues["n"]<1:raiseValueError("n must be at least 1.")ifvalues["n"]>1andvalues["streaming"]:raiseValueError("n must be 1 when streaming.")values["openai_api_key"]=get_from_dict_or_env(values,"openai_api_key","OPENAI_API_KEY")# Check OPENAI_ORGANIZATION for backwards compatibility.values["openai_organization"]=(values["openai_organization"]oros.getenv("OPENAI_ORG_ID")oros.getenv("OPENAI_ORGANIZATION"))values["openai_api_base"]=values["openai_api_base"]oros.getenv("OPENAI_API_BASE")values["openai_proxy"]=get_from_dict_or_env(values,"openai_proxy","OPENAI_PROXY",default="",)try:importopenaiexceptImportError:raiseImportError("Could not import openai python package. ""Please install it with `pip install openai`.")ifis_openai_v1():client_params={"api_key":values["openai_api_key"],"organization":values["openai_organization"],"base_url":values["openai_api_base"],"timeout":values["request_timeout"],"max_retries":values["max_retries"],"default_headers":values["default_headers"],"default_query":values["default_query"],"http_client":values["http_client"],}ifnotvalues.get("client"):values["client"]=openai.OpenAI(**client_params).chat.completionsifnotvalues.get("async_client"):values["async_client"]=openai.AsyncOpenAI(**client_params).chat.completionselifnotvalues.get("client"):values["client"]=openai.ChatCompletion# type: ignore[attr-defined]else:passreturnvalues
@propertydef_default_params(self)->Dict[str,Any]:"""Get the default parameters for calling OpenAI API."""params={"model":self.model_name,"stream":self.streaming,"n":self.n,"temperature":self.temperature,**self.model_kwargs,}ifself.max_tokensisnotNone:params["max_tokens"]=self.max_tokensifself.request_timeoutisnotNoneandnotis_openai_v1():params["request_timeout"]=self.request_timeoutreturnparams
[docs]defcompletion_with_retry(self,run_manager:Optional[CallbackManagerForLLMRun]=None,**kwargs:Any)->Any:"""Use tenacity to retry the completion call."""ifis_openai_v1():returnself.client.create(**kwargs)retry_decorator=_create_retry_decorator(self,run_manager=run_manager)@retry_decoratordef_completion_with_retry(**kwargs:Any)->Any:returnself.client.create(**kwargs)return_completion_with_retry(**kwargs)
def_combine_llm_outputs(self,llm_outputs:List[Optional[dict]])->dict:overall_token_usage:dict={}system_fingerprint=Noneforoutputinllm_outputs:ifoutputisNone:# Happens in streamingcontinuetoken_usage=output["token_usage"]iftoken_usageisnotNone:fork,vintoken_usage.items():ifkinoverall_token_usage:overall_token_usage[k]=_update_token_usage(overall_token_usage[k],v)else:overall_token_usage[k]=vifsystem_fingerprintisNone:system_fingerprint=output.get("system_fingerprint")combined={"token_usage":overall_token_usage,"model_name":self.model_name}ifsystem_fingerprint:combined["system_fingerprint"]=system_fingerprintreturncombineddef_stream(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,**kwargs:Any,)->Iterator[ChatGenerationChunk]:message_dicts,params=self._create_message_dicts(messages,stop)params={**params,**kwargs,"stream":True}default_chunk_class=AIMessageChunkforchunkinself.completion_with_retry(messages=message_dicts,run_manager=run_manager,**params):ifnotisinstance(chunk,dict):chunk=chunk.dict()iflen(chunk["choices"])==0:continuechoice=chunk["choices"][0]ifchoice["delta"]isNone:continuechunk=_convert_delta_to_message_chunk(choice["delta"],default_chunk_class)finish_reason=choice.get("finish_reason")generation_info=(dict(finish_reason=finish_reason)iffinish_reasonisnotNoneelseNone)default_chunk_class=chunk.__class__cg_chunk=ChatGenerationChunk(message=chunk,generation_info=generation_info)ifrun_manager:run_manager.on_llm_new_token(cg_chunk.text,chunk=cg_chunk)yieldcg_chunkdef_generate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,stream:Optional[bool]=None,**kwargs:Any,)->ChatResult:should_stream=streamifstreamisnotNoneelseself.streamingifshould_stream:stream_iter=self._stream(messages,stop=stop,run_manager=run_manager,**kwargs)returngenerate_from_stream(stream_iter)message_dicts,params=self._create_message_dicts(messages,stop)params={**params,**({"stream":stream}ifstreamisnotNoneelse{}),**kwargs,}response=self.completion_with_retry(messages=message_dicts,run_manager=run_manager,**params)returnself._create_chat_result(response)def_create_message_dicts(self,messages:List[BaseMessage],stop:Optional[List[str]])->Tuple[List[Dict[str,Any]],Dict[str,Any]]:params=self._client_paramsifstopisnotNone:if"stop"inparams:raiseValueError("`stop` found in both the input and default params.")params["stop"]=stopmessage_dicts=[convert_message_to_dict(m)forminmessages]returnmessage_dicts,paramsdef_create_chat_result(self,response:Union[dict,BaseModel])->ChatResult:generations=[]ifnotisinstance(response,dict):response=response.dict()forresinresponse["choices"]:message=convert_dict_to_message(res["message"])generation_info=dict(finish_reason=res.get("finish_reason"))if"logprobs"inres:generation_info["logprobs"]=res["logprobs"]gen=ChatGeneration(message=message,generation_info=generation_info,)generations.append(gen)token_usage=response.get("usage",{})llm_output={"token_usage":token_usage,"model_name":self.model_name,"system_fingerprint":response.get("system_fingerprint",""),}returnChatResult(generations=generations,llm_output=llm_output)asyncdef_astream(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,**kwargs:Any,)->AsyncIterator[ChatGenerationChunk]:message_dicts,params=self._create_message_dicts(messages,stop)params={**params,**kwargs,"stream":True}default_chunk_class=AIMessageChunkasyncforchunkinawaitacompletion_with_retry(self,messages=message_dicts,run_manager=run_manager,**params):ifnotisinstance(chunk,dict):chunk=chunk.dict()iflen(chunk["choices"])==0:continuechoice=chunk["choices"][0]ifchoice["delta"]isNone:continuechunk=_convert_delta_to_message_chunk(choice["delta"],default_chunk_class)finish_reason=choice.get("finish_reason")generation_info=(dict(finish_reason=finish_reason)iffinish_reasonisnotNoneelseNone)default_chunk_class=chunk.__class__cg_chunk=ChatGenerationChunk(message=chunk,generation_info=generation_info)ifrun_manager:awaitrun_manager.on_llm_new_token(token=cg_chunk.text,chunk=cg_chunk)yieldcg_chunkasyncdef_agenerate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,stream:Optional[bool]=None,**kwargs:Any,)->ChatResult:should_stream=streamifstreamisnotNoneelseself.streamingifshould_stream:stream_iter=self._astream(messages,stop=stop,run_manager=run_manager,**kwargs)returnawaitagenerate_from_stream(stream_iter)message_dicts,params=self._create_message_dicts(messages,stop)params={**params,**({"stream":stream}ifstreamisnotNoneelse{}),**kwargs,}response=awaitacompletion_with_retry(self,messages=message_dicts,run_manager=run_manager,**params)returnself._create_chat_result(response)@propertydef_identifying_params(self)->Dict[str,Any]:"""Get the identifying parameters."""return{**{"model_name":self.model_name},**self._default_params}@propertydef_client_params(self)->Dict[str,Any]:"""Get the parameters used for the openai client."""openai_creds:Dict[str,Any]={"model":self.model_name,}ifnotis_openai_v1():openai_creds.update({"api_key":self.openai_api_key,"api_base":self.openai_api_base,"organization":self.openai_organization,})ifself.openai_proxy:importopenaiopenai.proxy={"http":self.openai_proxy,"https":self.openai_proxy}# type: ignore[attr-defined]return{**self._default_params,**openai_creds}def_get_invocation_params(self,stop:Optional[List[str]]=None,**kwargs:Any)->Dict[str,Any]:"""Get the parameters used to invoke the model."""return{"model":self.model_name,**super()._get_invocation_params(stop=stop),**self._default_params,**kwargs,}@propertydef_llm_type(self)->str:"""Return type of chat model."""return"openai-chat"def_get_encoding_model(self)->Tuple[str,tiktoken.Encoding]:tiktoken_=_import_tiktoken()ifself.tiktoken_model_nameisnotNone:model=self.tiktoken_model_nameelse:model=self.model_nameifmodel=="gpt-3.5-turbo":# gpt-3.5-turbo may change over time.# Returning num tokens assuming gpt-3.5-turbo-0301.model="gpt-3.5-turbo-0301"elifmodel=="gpt-4":# gpt-4 may change over time.# Returning num tokens assuming gpt-4-0314.model="gpt-4-0314"# Returns the number of tokens used by a list of messages.try:encoding=tiktoken_.encoding_for_model(model)exceptKeyError:logger.warning("Warning: model not found. Using cl100k_base encoding.")model="cl100k_base"encoding=tiktoken_.get_encoding(model)returnmodel,encoding
[docs]defget_token_ids(self,text:str)->List[int]:"""Get the tokens present in the text with tiktoken package."""# tiktoken NOT supported for Python 3.7 or belowifsys.version_info[1]<=7:returnsuper().get_token_ids(text)_,encoding_model=self._get_encoding_model()returnencoding_model.encode(text)
[docs]defget_num_tokens_from_messages(self,messages:List[BaseMessage],tools:Optional[Sequence[Union[Dict[str,Any],Type,Callable,BaseTool]]]=None,)->int:"""Calculate num tokens for gpt-3.5-turbo and gpt-4 with tiktoken package. Official documentation: https://github.com/openai/openai-cookbook/blob/ main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb"""iftoolsisnotNone:warnings.warn("Counting tokens in tool schemas is not yet supported. Ignoring tools.")ifsys.version_info[1]<=7:returnsuper().get_num_tokens_from_messages(messages)model,encoding=self._get_encoding_model()ifmodel.startswith("gpt-3.5-turbo-0301"):# every message follows <im_start>{role/name}\n{content}<im_end>\ntokens_per_message=4# if there's a name, the role is omittedtokens_per_name=-1elifmodel.startswith("gpt-3.5-turbo")ormodel.startswith("gpt-4"):tokens_per_message=3tokens_per_name=1else:raiseNotImplementedError(f"get_num_tokens_from_messages() is not presently implemented "f"for model {model}.""See https://github.com/openai/openai-python/blob/main/chatml.md for ""information on how messages are converted to tokens.")num_tokens=0messages_dict=[convert_message_to_dict(m)forminmessages]formessageinmessages_dict:num_tokens+=tokens_per_messageforkey,valueinmessage.items():# Cast str(value) in case the message value is not a string# This occurs with function messagesnum_tokens+=len(encoding.encode(str(value)))ifkey=="name":num_tokens+=tokens_per_name# every reply is primed with <im_start>assistantnum_tokens+=3returnnum_tokens
[docs]defbind_functions(self,functions:Sequence[Union[Dict[str,Any],Type[BaseModel],Callable]],function_call:Optional[str]=None,**kwargs:Any,)->Runnable[LanguageModelInput,BaseMessage]:"""Bind functions (and other objects) to this chat model. Args: functions: A list of function definitions to bind to this chat model. Can be a dictionary, pydantic model, or callable. Pydantic models and callables will be automatically converted to their schema dictionary representation. function_call: Which function to require the model to call. Must be the name of the single provided function or "auto" to automatically determine which function to call (if any). kwargs: Any additional parameters to pass to the :class:`~langchain.runnable.Runnable` constructor. """fromlangchain.chains.openai_functions.baseimportconvert_to_openai_functionformatted_functions=[convert_to_openai_function(fn)forfninfunctions]iffunction_callisnotNone:iflen(formatted_functions)!=1:raiseValueError("When specifying `function_call`, you must provide exactly one ""function.")ifformatted_functions[0]["name"]!=function_call:raiseValueError(f"Function call {function_call} was specified, but the only "f"provided function was {formatted_functions[0]['name']}.")function_call_={"name":function_call}kwargs={**kwargs,"function_call":function_call_}returnsuper().bind(functions=formatted_functions,**kwargs,)