Source code for langchain_huggingface.chat_models.huggingface
"""Hugging Face Chat Wrapper."""fromdataclassesimportdataclassfromtypingimport(Any,Callable,Dict,List,Literal,Optional,Sequence,Type,Union,cast,)fromlangchain_core.callbacks.managerimport(AsyncCallbackManagerForLLMRun,CallbackManagerForLLMRun,)fromlangchain_core.language_modelsimportLanguageModelInputfromlangchain_core.language_models.chat_modelsimportBaseChatModelfromlangchain_core.messagesimport(AIMessage,BaseMessage,ChatMessage,HumanMessage,SystemMessage,ToolMessage,)fromlangchain_core.outputsimportChatGeneration,ChatResult,LLMResultfromlangchain_core.pydantic_v1importroot_validatorfromlangchain_core.runnablesimportRunnablefromlangchain_core.toolsimportBaseToolfromlangchain_core.utils.function_callingimportconvert_to_openai_toolfromlangchain_huggingface.llms.huggingface_endpointimportHuggingFaceEndpointfromlangchain_huggingface.llms.huggingface_pipelineimportHuggingFacePipelineDEFAULT_SYSTEM_PROMPT="""You are a helpful, respectful, and honest assistant."""
[docs]@dataclassclassTGI_RESPONSE:"""Response from the TextGenInference API."""choices:List[Any]usage:Dict
[docs]@dataclassclassTGI_MESSAGE:"""Message to send to the TextGenInference API."""role:strcontent:strtool_calls:List[Dict]
def_convert_message_to_chat_message(message:BaseMessage,)->Dict:ifisinstance(message,ChatMessage):returndict(role=message.role,content=message.content)elifisinstance(message,HumanMessage):returndict(role="user",content=message.content)elifisinstance(message,AIMessage):if"tool_calls"inmessage.additional_kwargs:tool_calls=[{"function":{"name":tc["function"]["name"],"arguments":tc["function"]["arguments"],}}fortcinmessage.additional_kwargs["tool_calls"]]else:tool_calls=Nonereturn{"role":"assistant","content":message.content,"tool_calls":tool_calls,}elifisinstance(message,SystemMessage):returndict(role="system",content=message.content)elifisinstance(message,ToolMessage):return{"role":"tool","content":message.content,"name":message.name,}else:raiseValueError(f"Got unknown type {message}")def_convert_TGI_message_to_LC_message(_message:TGI_MESSAGE,)->BaseMessage:role=_message.roleassertrole=="assistant",f"Expected role to be 'assistant', got {role}"content=cast(str,_message.content)ifcontentisNone:content=""additional_kwargs:Dict={}iftool_calls:=_message.tool_calls:if"arguments"intool_calls[0]["function"]:functions_string=str(tool_calls[0]["function"].pop("arguments"))corrected_functions=functions_string.replace("'",'"')tool_calls[0]["function"]["arguments"]=corrected_functionsadditional_kwargs["tool_calls"]=tool_callsreturnAIMessage(content=content,additional_kwargs=additional_kwargs)def_is_huggingface_hub(llm:Any)->bool:try:fromlangchain_community.llms.huggingface_hubimport(# type: ignore[import-not-found]HuggingFaceHub,)returnisinstance(llm,HuggingFaceHub)exceptImportError:# if no langchain community, it is not a HuggingFaceHubreturnFalsedef_is_huggingface_textgen_inference(llm:Any)->bool:try:fromlangchain_community.llms.huggingface_text_gen_inferenceimport(# type: ignore[import-not-found]HuggingFaceTextGenInference,)returnisinstance(llm,HuggingFaceTextGenInference)exceptImportError:# if no langchain community, it is not a HuggingFaceTextGenInferencereturnFalsedef_is_huggingface_endpoint(llm:Any)->bool:returnisinstance(llm,HuggingFaceEndpoint)def_is_huggingface_pipeline(llm:Any)->bool:returnisinstance(llm,HuggingFacePipeline)
[docs]classChatHuggingFace(BaseChatModel):"""Hugging Face LLM's as ChatModels. Works with `HuggingFaceTextGenInference`, `HuggingFaceEndpoint`, `HuggingFaceHub`, and `HuggingFacePipeline` LLMs. Upon instantiating this class, the model_id is resolved from the url provided to the LLM, and the appropriate tokenizer is loaded from the HuggingFace Hub. Setup: Install ``langchain-huggingface`` and ensure your Hugging Face token is saved. .. code-block:: bash pip install langchain-huggingface .. code-block:: python from huggingface_hub import login login() # You will be prompted for your HF key, which will then be saved locally Key init args — completion params: llm: `HuggingFaceTextGenInference`, `HuggingFaceEndpoint`, `HuggingFaceHub`, or 'HuggingFacePipeline' LLM to be used. Key init args — client params: custom_get_token_ids: Optional[Callable[[str], List[int]]] Optional encoder to use for counting tokens. metadata: Optional[Dict[str, Any]] Metadata to add to the run trace. tags: Optional[List[str]] Tags to add to the run trace. tokenizer: Any verbose: bool Whether to print out response text. See full list of supported init args and their descriptions in the params section. Instantiate: .. code-block:: python from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace llm = HuggingFaceEndpoint( repo_id="microsoft/Phi-3-mini-4k-instruct", task="text-generation", max_new_tokens=512, do_sample=False, repetition_penalty=1.03, ) chat = ChatHuggingFace(llm=llm, verbose=True) Invoke: .. code-block:: python messages = [ ("system", "You are a helpful translator. Translate the user sentence to French."), ("human", "I love programming."), ] chat(...).invoke(messages) .. code-block:: python AIMessage(content='Je ai une passion pour le programme.\n\nIn French, we use "ai" for masculine subjects and "a" for feminine subjects. Since "programming" is gender-neutral in English, we will go with the masculine "programme".\n\nConfirmation: "J\'aime le programme." is more commonly used. The sentence above is technically accurate, but less commonly used in spoken French as "ai" is used less frequently in everyday speech.', response_metadata={'token_usage': ChatCompletionOutputUsage (completion_tokens=100, prompt_tokens=55, total_tokens=155), 'model': '', 'finish_reason': 'length'}, id='run-874c24b7-0272-4c99-b259-5d6d7facbc56-0') Stream: .. code-block:: python for chunk in chat.stream(messages): print(chunk) .. code-block:: python content='Je ai une passion pour le programme.\n\nIn French, we use "ai" for masculine subjects and "a" for feminine subjects. Since "programming" is gender-neutral in English, we will go with the masculine "programme".\n\nConfirmation: "J\'aime le programme." is more commonly used. The sentence above is technically accurate, but less commonly used in spoken French as "ai" is used less frequently in everyday speech.' response_metadata={'token_usage': ChatCompletionOutputUsage (completion_tokens=100, prompt_tokens=55, total_tokens=155), 'model': '', 'finish_reason': 'length'} id='run-7d7b1967-9612-4f9a-911a-b2b5ca85046a-0' Async: .. code-block:: python await chat.ainvoke(messages) .. code-block:: python AIMessage(content='Je déaime le programming.\n\nLittérale : Je (j\'aime) déaime (le) programming.\n\nNote: "Programming" in French is "programmation". But here, I used "programming" instead of "programmation" because the user said "I love programming" instead of "I love programming (in French)", which would be "J\'aime la programmation". By translating the sentence literally, I preserved the original meaning of the user\'s sentence.', id='run-fd850318-e299-4735-b4c6-3496dc930b1d-0') Tool calling: .. code-block:: python from langchain_core.pydantic_v1 import BaseModel, Field class GetWeather(BaseModel): '''Get the current weather in a given location''' location: str = Field(..., description="The city and state, e.g. San Francisco, CA") class GetPopulation(BaseModel): '''Get the current population in a given location''' location: str = Field(..., description="The city and state, e.g. San Francisco, CA") chat_with_tools = chat.bind_tools([GetWeather, GetPopulation]) ai_msg = chat_with_tools.invoke("Which city is hotter today and which is bigger: LA or NY?") ai_msg.tool_calls .. code-block:: python [{'name': 'GetPopulation', 'args': {'location': 'Los Angeles, CA'}, 'id': '0'}] Response metadata .. code-block:: python ai_msg = chat.invoke(messages) ai_msg.response_metadata .. code-block:: python {'token_usage': ChatCompletionOutputUsage(completion_tokens=100, prompt_tokens=8, total_tokens=108), 'model': '', 'finish_reason': 'length'} """# noqa: E501llm:Any"""LLM, must be of type HuggingFaceTextGenInference, HuggingFaceEndpoint, HuggingFaceHub, or HuggingFacePipeline."""# TODO: Is system_message used anywhere?system_message:SystemMessage=SystemMessage(content=DEFAULT_SYSTEM_PROMPT)tokenizer:Any=Nonemodel_id:Optional[str]=Nonedef__init__(self,**kwargs:Any):super().__init__(**kwargs)fromtransformersimportAutoTokenizer# type: ignore[import]self._resolve_model_id()self.tokenizer=(AutoTokenizer.from_pretrained(self.model_id)ifself.tokenizerisNoneelseself.tokenizer)@root_validator(pre=False,skip_on_failure=True)defvalidate_llm(cls,values:dict)->dict:if(not_is_huggingface_hub(values["llm"])andnot_is_huggingface_textgen_inference(values["llm"])andnot_is_huggingface_endpoint(values["llm"])andnot_is_huggingface_pipeline(values["llm"])):raiseTypeError("Expected llm to be one of HuggingFaceTextGenInference, ""HuggingFaceEndpoint, HuggingFaceHub, HuggingFacePipeline "f"received {type(values['llm'])}")returnvaluesdef_create_chat_result(self,response:TGI_RESPONSE)->ChatResult:generations=[]finish_reason=response.choices[0].finish_reasongen=ChatGeneration(message=_convert_TGI_message_to_LC_message(response.choices[0].message),generation_info={"finish_reason":finish_reason},)generations.append(gen)token_usage=response.usagemodel_object=self.llm.inference_server_urlllm_output={"token_usage":token_usage,"model":model_object}returnChatResult(generations=generations,llm_output=llm_output)def_generate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,**kwargs:Any,)->ChatResult:if_is_huggingface_textgen_inference(self.llm):message_dicts=self._create_message_dicts(messages,stop)answer=self.llm.client.chat(messages=message_dicts,**kwargs)returnself._create_chat_result(answer)elif_is_huggingface_endpoint(self.llm):message_dicts=self._create_message_dicts(messages,stop)answer=self.llm.client.chat_completion(messages=message_dicts,**kwargs)returnself._create_chat_result(answer)else:llm_input=self._to_chat_prompt(messages)llm_result=self.llm._generate(prompts=[llm_input],stop=stop,run_manager=run_manager,**kwargs)returnself._to_chat_result(llm_result)asyncdef_agenerate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,**kwargs:Any,)->ChatResult:if_is_huggingface_textgen_inference(self.llm):message_dicts=self._create_message_dicts(messages,stop)answer=awaitself.llm.async_client.chat(messages=message_dicts,**kwargs)returnself._create_chat_result(answer)else:llm_input=self._to_chat_prompt(messages)llm_result=awaitself.llm._agenerate(prompts=[llm_input],stop=stop,run_manager=run_manager,**kwargs)returnself._to_chat_result(llm_result)def_to_chat_prompt(self,messages:List[BaseMessage],)->str:"""Convert a list of messages into a prompt format expected by wrapped LLM."""ifnotmessages:raiseValueError("At least one HumanMessage must be provided!")ifnotisinstance(messages[-1],HumanMessage):raiseValueError("Last message must be a HumanMessage!")messages_dicts=[self._to_chatml_format(m)forminmessages]returnself.tokenizer.apply_chat_template(messages_dicts,tokenize=False,add_generation_prompt=True)def_to_chatml_format(self,message:BaseMessage)->dict:"""Convert LangChain message to ChatML format."""ifisinstance(message,SystemMessage):role="system"elifisinstance(message,AIMessage):role="assistant"elifisinstance(message,HumanMessage):role="user"else:raiseValueError(f"Unknown message type: {type(message)}")return{"role":role,"content":message.content}@staticmethoddef_to_chat_result(llm_result:LLMResult)->ChatResult:chat_generations=[]forginllm_result.generations[0]:chat_generation=ChatGeneration(message=AIMessage(content=g.text),generation_info=g.generation_info)chat_generations.append(chat_generation)returnChatResult(generations=chat_generations,llm_output=llm_result.llm_output)def_resolve_model_id(self)->None:"""Resolve the model_id from the LLM's inference_server_url"""fromhuggingface_hubimportlist_inference_endpoints# type: ignore[import]if_is_huggingface_hub(self.llm)or(hasattr(self.llm,"repo_id")andself.llm.repo_id):self.model_id=self.llm.repo_idreturnelif_is_huggingface_textgen_inference(self.llm):endpoint_url:Optional[str]=self.llm.inference_server_urlelif_is_huggingface_pipeline(self.llm):self.model_id=self.llm.model_idreturnelse:endpoint_url=self.llm.endpoint_urlavailable_endpoints=list_inference_endpoints("*")forendpointinavailable_endpoints:ifendpoint.url==endpoint_url:self.model_id=endpoint.repositoryifnotself.model_id:raiseValueError("Failed to resolve model_id:"f"Could not find model id for inference server: {endpoint_url}""Make sure that your Hugging Face token has access to the endpoint.")
[docs]defbind_tools(self,tools:Sequence[Union[Dict[str,Any],Type,Callable,BaseTool]],*,tool_choice:Optional[Union[dict,str,Literal["auto","none"],bool]]=None,**kwargs:Any,)->Runnable[LanguageModelInput,BaseMessage]:"""Bind tool-like objects to this chat model. Assumes model is compatible with OpenAI tool-calling API. Args: tools: A list of tool definitions to bind to this chat model. Supports any tool definition handled by :meth:`langchain_core.utils.function_calling.convert_to_openai_tool`. tool_choice: Which tool 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), or a dict of the form: {"type": "function", "function": {"name": <<tool_name>>}}. **kwargs: Any additional parameters to pass to the :class:`~langchain.runnable.Runnable` constructor. """formatted_tools=[convert_to_openai_tool(tool)fortoolintools]iftool_choiceisnotNoneandtool_choice:iflen(formatted_tools)!=1:raiseValueError("When specifying `tool_choice`, you must provide exactly one "f"tool. Received {len(formatted_tools)} tools.")ifisinstance(tool_choice,str):iftool_choicenotin("auto","none"):tool_choice={"type":"function","function":{"name":tool_choice},}elifisinstance(tool_choice,bool):tool_choice=formatted_tools[0]elifisinstance(tool_choice,dict):if(formatted_tools[0]["function"]["name"]!=tool_choice["function"]["name"]):raiseValueError(f"Tool choice {tool_choice} was specified, but the only "f"provided tool was {formatted_tools[0]['function']['name']}.")else:raiseValueError(f"Unrecognized tool_choice type. Expected str, bool or dict. "f"Received: {tool_choice}")kwargs["tool_choice"]=tool_choicereturnsuper().bind(tools=formatted_tools,**kwargs)