Source code for langchain_google_genai.chat_models
from__future__importannotationsimportasyncioimportbase64importjsonimportloggingimportosimportuuidimportwarningsfromioimportBytesIOfromoperatorimportitemgetterfromtypingimport(Any,AsyncIterator,Callable,Dict,Iterator,List,Mapping,Optional,Sequence,Tuple,Type,Union,cast,)fromurllib.parseimporturlparseimportgoogle.api_core# TODO: remove ignore once the google package is published with typesimportproto# type: ignore[import]importrequestsfromgoogle.ai.generativelanguage_v1beta.typesimport(Blob,Candidate,Content,FileData,FunctionCall,FunctionResponse,GenerateContentRequest,GenerateContentResponse,GenerationConfig,Part,SafetySetting,ToolConfig,VideoMetadata,)fromgoogle.generativeai.typesimportToolasGoogleTool# type: ignore[import]fromgoogle.generativeai.types.content_typesimport(# type: ignore[import]FunctionDeclarationType,ToolDict,)fromlangchain_core.callbacks.managerimport(AsyncCallbackManagerForLLMRun,CallbackManagerForLLMRun,)fromlangchain_core.language_modelsimportLanguageModelInputfromlangchain_core.language_models.chat_modelsimportBaseChatModel,LangSmithParamsfromlangchain_core.messagesimport(AIMessage,AIMessageChunk,BaseMessage,FunctionMessage,HumanMessage,SystemMessage,ToolMessage,)fromlangchain_core.messages.aiimportUsageMetadatafromlangchain_core.messages.toolimportinvalid_tool_call,tool_call,tool_call_chunkfromlangchain_core.output_parsers.baseimportOutputParserLikefromlangchain_core.output_parsers.openai_toolsimport(JsonOutputToolsParser,PydanticToolsParser,parse_tool_calls,)fromlangchain_core.outputsimportChatGeneration,ChatGenerationChunk,ChatResultfromlangchain_core.pydantic_v1importBaseModel,Field,SecretStr,root_validatorfromlangchain_core.runnablesimportRunnable,RunnablePassthroughfromlangchain_core.utilsimportsecret_from_envfromlangchain_core.utils.pydanticimportis_basemodel_subclassfromtenacityimport(before_sleep_log,retry,retry_if_exception_type,stop_after_attempt,wait_exponential,)fromlangchain_google_genai._commonimport(GoogleGenerativeAIError,SafetySettingDict,get_client_info,)fromlangchain_google_genai._function_utilsimport(_tool_choice_to_tool_config,_ToolChoiceType,_ToolConfigDict,convert_to_genai_function_declarations,tool_to_dict,)fromlangchain_google_genai._image_utilsimportImageBytesLoaderfromlangchain_google_genai.llmsimport_BaseGoogleGenerativeAIfrom.import_genai_extensionasgenaixIMAGE_TYPES:Tuple=()try:importPILfromPIL.ImageimportImageIMAGE_TYPES=IMAGE_TYPES+(Image,)exceptImportError:PIL=None# type: ignoreImage=None# type: ignorelogger=logging.getLogger(__name__)
[docs]classChatGoogleGenerativeAIError(GoogleGenerativeAIError):""" Custom exception class for errors associated with the `Google GenAI` API. This exception is raised when there are specific issues related to the Google genai API usage in the ChatGoogleGenerativeAI class, such as unsupported message types or roles. """
def_create_retry_decorator()->Callable[[Any],Any]:""" Creates and returns a preconfigured tenacity retry decorator. The retry decorator is configured to handle specific Google API exceptions such as ResourceExhausted and ServiceUnavailable. It uses an exponential backoff strategy for retries. Returns: Callable[[Any], Any]: A retry decorator configured for handling specific Google API exceptions. """multiplier=2min_seconds=1max_seconds=60max_retries=2returnretry(reraise=True,stop=stop_after_attempt(max_retries),wait=wait_exponential(multiplier=multiplier,min=min_seconds,max=max_seconds),retry=(retry_if_exception_type(google.api_core.exceptions.ResourceExhausted)|retry_if_exception_type(google.api_core.exceptions.ServiceUnavailable)|retry_if_exception_type(google.api_core.exceptions.GoogleAPIError)),before_sleep=before_sleep_log(logger,logging.WARNING),)def_chat_with_retry(generation_method:Callable,**kwargs:Any)->Any:""" Executes a chat generation method with retry logic using tenacity. This function is a wrapper that applies a retry mechanism to a provided chat generation function. It is useful for handling intermittent issues like network errors or temporary service unavailability. Args: generation_method (Callable): The chat generation method to be executed. **kwargs (Any): Additional keyword arguments to pass to the generation method. Returns: Any: The result from the chat generation method. """retry_decorator=_create_retry_decorator()@retry_decoratordef_chat_with_retry(**kwargs:Any)->Any:try:returngeneration_method(**kwargs)# Do not retry for these errors.exceptgoogle.api_core.exceptions.FailedPreconditionasexc:if"location is not supported"inexc.message:error_msg=("Your location is not supported by google-generativeai ""at the moment. Try to use ChatVertexAI LLM from ""langchain_google_vertexai.")raiseValueError(error_msg)exceptgoogle.api_core.exceptions.InvalidArgumentase:raiseChatGoogleGenerativeAIError(f"Invalid argument provided to Gemini: {e}")fromeexceptExceptionase:raiseereturn_chat_with_retry(**kwargs)asyncdef_achat_with_retry(generation_method:Callable,**kwargs:Any)->Any:""" Executes a chat generation method with retry logic using tenacity. This function is a wrapper that applies a retry mechanism to a provided chat generation function. It is useful for handling intermittent issues like network errors or temporary service unavailability. Args: generation_method (Callable): The chat generation method to be executed. **kwargs (Any): Additional keyword arguments to pass to the generation method. Returns: Any: The result from the chat generation method. """retry_decorator=_create_retry_decorator()fromgoogle.api_core.exceptionsimportInvalidArgument# type: ignore@retry_decoratorasyncdef_achat_with_retry(**kwargs:Any)->Any:try:returnawaitgeneration_method(**kwargs)exceptInvalidArgumentase:# Do not retry for these errors.raiseChatGoogleGenerativeAIError(f"Invalid argument provided to Gemini: {e}")fromeexceptExceptionase:raiseereturnawait_achat_with_retry(**kwargs)def_is_openai_parts_format(part:dict)->bool:return"type"inpartdef_is_vision_model(model:str)->bool:return"vision"inmodeldef_is_url(s:str)->bool:try:result=urlparse(s)returnall([result.scheme,result.netloc])exceptExceptionase:logger.debug(f"Unable to parse URL: {e}")returnFalsedef_is_b64(s:str)->bool:returns.startswith("data:image")def_load_image_from_gcs(path:str,project:Optional[str]=None)->Image:try:fromgoogle.cloudimportstorage# type: ignore[attr-defined]exceptImportError:raiseImportError("google-cloud-storage is required to load images from GCS."" Install it with `pip install google-cloud-storage`")ifPILisNone:raiseImportError("PIL is required to load images. Please install it ""with `pip install pillow`")gcs_client=storage.Client(project=project)pieces=path.split("/")blobs=list(gcs_client.list_blobs(pieces[2],prefix="/".join(pieces[3:])))iflen(blobs)>1:raiseValueError(f"Found more than one candidate for {path}!")img_bytes=blobs[0].download_as_bytes()returnPIL.Image.open(BytesIO(img_bytes))def_url_to_pil(image_source:str)->Image:ifPILisNone:raiseImportError("PIL is required to load images. Please install it ""with `pip install pillow`")try:ifisinstance(image_source,IMAGE_TYPES):returnimage_source# type: ignore[return-value]elif_is_url(image_source):ifimage_source.startswith("gs://"):return_load_image_from_gcs(image_source)response=requests.get(image_source)response.raise_for_status()returnPIL.Image.open(BytesIO(response.content))elif_is_b64(image_source):_,encoded=image_source.split(",",1)data=base64.b64decode(encoded)returnPIL.Image.open(BytesIO(data))elifos.path.exists(image_source):returnPIL.Image.open(image_source)else:raiseValueError("The provided string is not a valid URL, base64, or file path.")exceptExceptionase:raiseValueError(f"Unable to process the provided image source: {e}")def_convert_to_parts(raw_content:Union[str,Sequence[Union[str,dict]]],)->List[Part]:"""Converts a list of LangChain messages into a google parts."""parts=[]content=[raw_content]ifisinstance(raw_content,str)elseraw_contentimage_loader=ImageBytesLoader()forpartincontent:ifisinstance(part,str):parts.append(Part(text=part))elifisinstance(part,Mapping):# OpenAI Formatif_is_openai_parts_format(part):ifpart["type"]=="text":parts.append(Part(text=part["text"]))elifpart["type"]=="image_url":img_url=part["image_url"]ifisinstance(img_url,dict):if"url"notinimg_url:raiseValueError(f"Unrecognized message image format: {img_url}")img_url=img_url["url"]parts.append(image_loader.load_part(img_url))# Handle media type like LangChain.js# https://github.com/langchain-ai/langchainjs/blob/e536593e2585f1dd7b0afc187de4d07cb40689ba/libs/langchain-google-common/src/utils/gemini.ts#L93-L106elifpart["type"]=="media":if"mime_type"notinpart:raiseValueError(f"Missing mime_type in media part: {part}")mime_type=part["mime_type"]media_part=Part()if"data"inpart:media_part.inline_data=Blob(data=part["data"],mime_type=mime_type)elif"file_uri"inpart:media_part.file_data=FileData(file_uri=part["file_uri"],mime_type=mime_type)else:raiseValueError(f"Media part must have either data or file_uri: {part}")if"video_metadata"inpart:metadata=VideoMetadata(part["video_metadata"])media_part.video_metadata=metadataparts.append(media_part)else:raiseValueError(f"Unrecognized message part type: {part['type']}. Only text, "f"image_url, and media types are supported.")else:# Yolologger.warning("Unrecognized message part format. Assuming it's a text part.")parts.append(Part(text=str(part)))else:# TODO: Maybe some of Google's native stuff# would hit this branch.raiseChatGoogleGenerativeAIError("Gemini only supports text and inline_data parts.")returnpartsdef_parse_chat_history(input_messages:Sequence[BaseMessage],convert_system_message_to_human:bool=False)->Tuple[Optional[Content],List[Content]]:messages:List[Content]=[]ifconvert_system_message_to_human:warnings.warn("Convert_system_message_to_human will be deprecated!")system_instruction:Optional[Content]=Nonefori,messageinenumerate(input_messages):ifi==0andisinstance(message,SystemMessage):system_instruction=Content(parts=_convert_to_parts(message.content))continueelifisinstance(message,AIMessage):role="model"raw_function_call=message.additional_kwargs.get("function_call")ifraw_function_call:function_call=FunctionCall({"name":raw_function_call["name"],"args":json.loads(raw_function_call["arguments"]),})parts=[Part(function_call=function_call)]else:parts=_convert_to_parts(message.content)elifisinstance(message,HumanMessage):role="user"parts=_convert_to_parts(message.content)ifi==1andconvert_system_message_to_humanandsystem_instruction:parts=[pforpinsystem_instruction.parts]+partssystem_instruction=Noneelifisinstance(message,FunctionMessage):role="user"response:Anyifnotisinstance(message.content,str):response=message.contentelse:try:response=json.loads(message.content)exceptjson.JSONDecodeError:response=message.content# leave as str representationparts=[Part(function_response=FunctionResponse(name=message.name,response=({"output":response}ifnotisinstance(response,dict)elseresponse),))]elifisinstance(message,ToolMessage):role="user"prev_message:Optional[BaseMessage]=(input_messages[i-1]ifi>0elseNone)if(prev_messageandisinstance(prev_message,AIMessage)andprev_message.tool_calls):# message.name can be null for ToolMessagename:str=prev_message.tool_calls[0]["name"]else:name=message.name# type: ignoretool_response:Anyifnotisinstance(message.content,str):tool_response=message.contentelse:try:tool_response=json.loads(message.content)exceptjson.JSONDecodeError:tool_response=message.content# leave as str representationparts=[Part(function_response=FunctionResponse(name=name,response=({"output":tool_response}ifnotisinstance(tool_response,dict)elsetool_response),))]else:raiseValueError(f"Unexpected message with type {type(message)} at the position {i}.")messages.append(Content(role=role,parts=parts))returnsystem_instruction,messagesdef_parse_response_candidate(response_candidate:Candidate,streaming:bool=False)->AIMessage:content:Union[None,str,List[str]]=Noneadditional_kwargs={}tool_calls=[]invalid_tool_calls=[]tool_call_chunks=[]forpartinresponse_candidate.content.parts:try:text:Optional[str]=part.textexceptAttributeError:text=NoneiftextisnotNone:ifnotcontent:content=textelifisinstance(content,str)andtext:content=[content,text]elifisinstance(content,list)andtext:content.append(text)eliftext:raiseException("Unexpected content type")ifpart.function_call:function_call={"name":part.function_call.name}# dump to match other function calling llm for nowfunction_call_args_dict=proto.Message.to_dict(part.function_call)["args"]function_call["arguments"]=json.dumps({k:function_call_args_dict[k]forkinfunction_call_args_dict})additional_kwargs["function_call"]=function_callifstreaming:tool_call_chunks.append(tool_call_chunk(name=function_call.get("name"),args=function_call.get("arguments"),id=function_call.get("id",str(uuid.uuid4())),index=function_call.get("index"),# type: ignore))else:try:tool_call_dict=parse_tool_calls([{"function":function_call}],return_id=False,)[0]exceptExceptionase:invalid_tool_calls.append(invalid_tool_call(name=function_call.get("name"),args=function_call.get("arguments"),id=function_call.get("id",str(uuid.uuid4())),error=str(e),))else:tool_calls.append(tool_call(name=tool_call_dict["name"],args=tool_call_dict["args"],id=tool_call_dict.get("id",str(uuid.uuid4())),))ifcontentisNone:content=""ifstreaming:returnAIMessageChunk(content=cast(Union[str,List[Union[str,Dict[Any,Any]]]],content),additional_kwargs=additional_kwargs,tool_call_chunks=tool_call_chunks,)returnAIMessage(content=cast(Union[str,List[Union[str,Dict[Any,Any]]]],content),additional_kwargs=additional_kwargs,tool_calls=tool_calls,invalid_tool_calls=invalid_tool_calls,)def_response_to_result(response:GenerateContentResponse,stream:bool=False,)->ChatResult:"""Converts a PaLM API response into a LangChain ChatResult."""llm_output={"prompt_feedback":proto.Message.to_dict(response.prompt_feedback)}# Get usage metadatatry:input_tokens=response.usage_metadata.prompt_token_countoutput_tokens=response.usage_metadata.candidates_token_counttotal_tokens=response.usage_metadata.total_token_countifinput_tokens+output_tokens+total_tokens>0:lc_usage=UsageMetadata(input_tokens=input_tokens,output_tokens=output_tokens,total_tokens=total_tokens,)else:lc_usage=NoneexceptAttributeError:lc_usage=Nonegenerations:List[ChatGeneration]=[]forcandidateinresponse.candidates:generation_info={}ifcandidate.finish_reason:generation_info["finish_reason"]=candidate.finish_reason.namegeneration_info["safety_ratings"]=[proto.Message.to_dict(safety_rating,use_integers_for_enums=False)forsafety_ratingincandidate.safety_ratings]message=_parse_response_candidate(candidate,streaming=stream)message.usage_metadata=lc_usagegenerations.append((ChatGenerationChunkifstreamelseChatGeneration)(message=message,generation_info=generation_info,))ifnotresponse.candidates:# Likely a "prompt feedback" violation (e.g., toxic input)# Raising an error would be different than how OpenAI handles it,# so we'll just log a warning and continue with an empty message.logger.warning("Gemini produced an empty response. Continuing with empty message\n"f"Feedback: {response.prompt_feedback}")generations=[(ChatGenerationChunkifstreamelseChatGeneration)(message=(AIMessageChunkifstreamelseAIMessage)(content=""),generation_info={},)]returnChatResult(generations=generations,llm_output=llm_output)def_is_event_loop_running()->bool:try:asyncio.get_running_loop()returnTrueexceptRuntimeError:returnFalse
[docs]classChatGoogleGenerativeAI(_BaseGoogleGenerativeAI,BaseChatModel):"""`Google AI` chat models integration. Instantiation: To use, you must have either: 1. The ``GOOGLE_API_KEY``` environment variable set with your API key, or 2. Pass your API key using the google_api_key kwarg to the ChatGoogle constructor. .. code-block:: python from langchain_google_genai import ChatGoogleGenerativeAI llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro") llm.invoke("Write me a ballad about LangChain") Invoke: .. code-block:: python messages = [ ("system", "Translate the user sentence to French."), ("human", "I love programming."), ] llm.invoke(messages) .. code-block:: python AIMessage( content="J'adore programmer. \\n", response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-56cecc34-2e54-4b52-a974-337e47008ad2-0', usage_metadata={'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23} ) Stream: .. code-block:: python for chunk in llm.stream(messages): print(chunk) .. code-block:: python AIMessageChunk(content='J', response_metadata={'finish_reason': 'STOP', 'safety_ratings': []}, id='run-e905f4f4-58cb-4a10-a960-448a2bb649e3', usage_metadata={'input_tokens': 18, 'output_tokens': 1, 'total_tokens': 19}) AIMessageChunk(content="'adore programmer. \n", response_metadata={'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-e905f4f4-58cb-4a10-a960-448a2bb649e3', usage_metadata={'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23}) .. code-block:: python stream = llm.stream(messages) full = next(stream) for chunk in stream: full += chunk full .. code-block:: python AIMessageChunk( content="J'adore programmer. \\n", response_metadata={'finish_reason': 'STOPSTOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}]}, id='run-3ce13a42-cd30-4ad7-a684-f1f0b37cdeec', usage_metadata={'input_tokens': 36, 'output_tokens': 6, 'total_tokens': 42} ) Async: .. code-block:: python await llm.ainvoke(messages) # stream: # async for chunk in (await llm.astream(messages)) # batch: # await llm.abatch([messages]) 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" ) llm_with_tools = llm.bind_tools([GetWeather, GetPopulation]) ai_msg = llm_with_tools.invoke( "Which city is hotter today and which is bigger: LA or NY?" ) ai_msg.tool_calls .. code-block:: python [{'name': 'GetWeather', 'args': {'location': 'Los Angeles, CA'}, 'id': 'c186c99f-f137-4d52-947f-9e3deabba6f6'}, {'name': 'GetWeather', 'args': {'location': 'New York City, NY'}, 'id': 'cebd4a5d-e800-4fa5-babd-4aa286af4f31'}, {'name': 'GetPopulation', 'args': {'location': 'Los Angeles, CA'}, 'id': '4f92d897-f5e4-4d34-a3bc-93062c92591e'}, {'name': 'GetPopulation', 'args': {'location': 'New York City, NY'}, 'id': '634582de-5186-4e4b-968b-f192f0a93678'}] Structured output: .. code-block:: python from typing import Optional from langchain_core.pydantic_v1 import BaseModel, Field class Joke(BaseModel): '''Joke to tell user.''' setup: str = Field(description="The setup of the joke") punchline: str = Field(description="The punchline to the joke") rating: Optional[int] = Field(description="How funny the joke is, from 1 to 10") structured_llm = llm.with_structured_output(Joke) structured_llm.invoke("Tell me a joke about cats") .. code-block:: python Joke( setup='Why are cats so good at video games?', punchline='They have nine lives on the internet', rating=None ) Image input: .. code-block:: python import base64 import httpx from langchain_core.messages import HumanMessage image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8") message = HumanMessage( content=[ {"type": "text", "text": "describe the weather in this image"}, { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}, }, ] ) ai_msg = llm.invoke([message]) ai_msg.content .. code-block:: python 'The weather in this image appears to be sunny and pleasant. The sky is a bright blue with scattered white clouds, suggesting fair weather. The lush green grass and trees indicate a warm and possibly slightly breezy day. There are no signs of rain or storms. \n' Token usage: .. code-block:: python ai_msg = llm.invoke(messages) ai_msg.usage_metadata .. code-block:: python {'input_tokens': 18, 'output_tokens': 5, 'total_tokens': 23} Response metadata .. code-block:: python ai_msg = llm.invoke(messages) ai_msg.response_metadata .. code-block:: python { 'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': [{'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HATE_SPEECH', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_HARASSMENT', 'probability': 'NEGLIGIBLE', 'blocked': False}, {'category': 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability': 'NEGLIGIBLE', 'blocked': False}] } """# noqa: E501client:Any=Field(default=None,exclude=True)#: :meta private:async_client:Any=Field(default=None,exclude=True)#: :meta private:google_api_key:Optional[SecretStr]=Field(alias="api_key",default_factory=secret_from_env("GOOGLE_API_KEY",default=None))"""Google AI API key. If not specified will be read from env var ``GOOGLE_API_KEY``."""default_metadata:Sequence[Tuple[str,str]]=Field(default_factory=list)#: :meta private:convert_system_message_to_human:bool=False"""Whether to merge any leading SystemMessage into the following HumanMessage. Gemini does not support system messages; any unsupported messages will raise an error."""classConfig:allow_population_by_field_name=True@propertydeflc_secrets(self)->Dict[str,str]:return{"google_api_key":"GOOGLE_API_KEY"}@propertydef_llm_type(self)->str:return"chat-google-generative-ai"@classmethoddefis_lc_serializable(self)->bool:returnTrue@root_validator(pre=False,skip_on_failure=True)defvalidate_environment(cls,values:Dict)->Dict:"""Validates params and passes them to google-generativeai package."""if(values.get("temperature")isnotNoneandnot0<=values["temperature"]<=1):raiseValueError("temperature must be in the range [0.0, 1.0]")ifvalues.get("top_p")isnotNoneandnot0<=values["top_p"]<=1:raiseValueError("top_p must be in the range [0.0, 1.0]")ifvalues.get("top_k")isnotNoneandvalues["top_k"]<=0:raiseValueError("top_k must be positive")ifnotvalues["model"].startswith("models/"):values["model"]=f"models/{values['model']}"additional_headers=values.get("additional_headers")or{}values["default_metadata"]=tuple(additional_headers.items())client_info=get_client_info("ChatGoogleGenerativeAI")google_api_key=Noneifnotvalues.get("credentials"):google_api_key=values.get("google_api_key")ifisinstance(google_api_key,SecretStr):google_api_key=google_api_key.get_secret_value()transport:Optional[str]=values.get("transport")values["client"]=genaix.build_generative_service(credentials=values.get("credentials"),api_key=google_api_key,client_info=client_info,client_options=values.get("client_options"),transport=transport,)# NOTE: genaix.build_generative_async_service requires# a running event loop, which causes an error# when initialized inside a ThreadPoolExecutor.# this check ensures that async client is only initialized# within an asyncio event loop to avoid the errorif_is_event_loop_running():values["async_client"]=genaix.build_generative_async_service(credentials=values.get("credentials"),api_key=google_api_key,client_info=client_info,client_options=values.get("client_options"),transport=transport,)else:values["async_client"]=Nonereturnvalues@propertydef_identifying_params(self)->Dict[str,Any]:"""Get the identifying parameters."""return{"model":self.model,"temperature":self.temperature,"top_k":self.top_k,"n":self.n,"safety_settings":self.safety_settings,}def_get_ls_params(self,stop:Optional[List[str]]=None,**kwargs:Any)->LangSmithParams:"""Get standard params for tracing."""params=self._get_invocation_params(stop=stop,**kwargs)ls_params=LangSmithParams(ls_provider="google_genai",ls_model_name=self.model,ls_model_type="chat",ls_temperature=params.get("temperature",self.temperature),)ifls_max_tokens:=params.get("max_output_tokens",self.max_output_tokens):ls_params["ls_max_tokens"]=ls_max_tokensifls_stop:=stoporparams.get("stop",None):ls_params["ls_stop"]=ls_stopreturnls_paramsdef_prepare_params(self,stop:Optional[List[str]],generation_config:Optional[Dict[str,Any]]=None,)->GenerationConfig:gen_config={k:vfork,vin{"candidate_count":self.n,"temperature":self.temperature,"stop_sequences":stop,"max_output_tokens":self.max_output_tokens,"top_k":self.top_k,"top_p":self.top_p,}.items()ifvisnotNone}ifgeneration_config:gen_config={**gen_config,**generation_config}returnGenerationConfig(**gen_config)def_generate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,*,tools:Optional[Sequence[Union[ToolDict,GoogleTool]]]=None,functions:Optional[Sequence[FunctionDeclarationType]]=None,safety_settings:Optional[SafetySettingDict]=None,tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,generation_config:Optional[Dict[str,Any]]=None,**kwargs:Any,)->ChatResult:request=self._prepare_request(messages,stop=stop,tools=tools,functions=functions,safety_settings=safety_settings,tool_config=tool_config,generation_config=generation_config,)response:GenerateContentResponse=_chat_with_retry(request=request,**kwargs,generation_method=self.client.generate_content,metadata=self.default_metadata,)return_response_to_result(response)asyncdef_agenerate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,*,tools:Optional[Sequence[Union[ToolDict,GoogleTool]]]=None,functions:Optional[Sequence[FunctionDeclarationType]]=None,safety_settings:Optional[SafetySettingDict]=None,tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,generation_config:Optional[Dict[str,Any]]=None,**kwargs:Any,)->ChatResult:ifnotself.async_client:updated_kwargs={**kwargs,**{"tools":tools,"functions":functions,"safety_settings":safety_settings,"tool_config":tool_config,"generation_config":generation_config,},}returnawaitsuper()._agenerate(messages,stop,run_manager,**updated_kwargs)request=self._prepare_request(messages,stop=stop,tools=tools,functions=functions,safety_settings=safety_settings,tool_config=tool_config,generation_config=generation_config,)response:GenerateContentResponse=await_achat_with_retry(request=request,**kwargs,generation_method=self.async_client.generate_content,metadata=self.default_metadata,)return_response_to_result(response)def_stream(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,*,tools:Optional[Sequence[Union[ToolDict,GoogleTool]]]=None,functions:Optional[Sequence[FunctionDeclarationType]]=None,safety_settings:Optional[SafetySettingDict]=None,tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,generation_config:Optional[Dict[str,Any]]=None,**kwargs:Any,)->Iterator[ChatGenerationChunk]:request=self._prepare_request(messages,stop=stop,tools=tools,functions=functions,safety_settings=safety_settings,tool_config=tool_config,generation_config=generation_config,)response:GenerateContentResponse=_chat_with_retry(request=request,generation_method=self.client.stream_generate_content,**kwargs,metadata=self.default_metadata,)forchunkinresponse:_chat_result=_response_to_result(chunk,stream=True)gen=cast(ChatGenerationChunk,_chat_result.generations[0])ifrun_manager:run_manager.on_llm_new_token(gen.text)yieldgenasyncdef_astream(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[AsyncCallbackManagerForLLMRun]=None,*,tools:Optional[Sequence[Union[ToolDict,GoogleTool]]]=None,functions:Optional[Sequence[FunctionDeclarationType]]=None,safety_settings:Optional[SafetySettingDict]=None,tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,generation_config:Optional[Dict[str,Any]]=None,**kwargs:Any,)->AsyncIterator[ChatGenerationChunk]:ifnotself.async_client:updated_kwargs={**kwargs,**{"tools":tools,"functions":functions,"safety_settings":safety_settings,"tool_config":tool_config,"generation_config":generation_config,},}asyncforvalueinsuper()._astream(messages,stop,run_manager,**updated_kwargs):yieldvalueelse:request=self._prepare_request(messages,stop=stop,tools=tools,functions=functions,safety_settings=safety_settings,tool_config=tool_config,generation_config=generation_config,)asyncforchunkinawait_achat_with_retry(request=request,generation_method=self.async_client.stream_generate_content,**kwargs,metadata=self.default_metadata,):_chat_result=_response_to_result(chunk,stream=True)gen=cast(ChatGenerationChunk,_chat_result.generations[0])ifrun_manager:awaitrun_manager.on_llm_new_token(gen.text)yieldgendef_prepare_request(self,messages:List[BaseMessage],*,stop:Optional[List[str]]=None,tools:Optional[Sequence[Union[ToolDict,GoogleTool]]]=None,functions:Optional[Sequence[FunctionDeclarationType]]=None,safety_settings:Optional[SafetySettingDict]=None,tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,generation_config:Optional[Dict[str,Any]]=None,)->Tuple[GenerateContentRequest,Dict[str,Any]]:formatted_tools=Noneiftools:formatted_tools=[convert_to_genai_function_declarations(tools)]eliffunctions:formatted_tools=[convert_to_genai_function_declarations(functions)]system_instruction,history=_parse_chat_history(messages,convert_system_message_to_human=self.convert_system_message_to_human,)formatted_tool_config=Noneiftool_config:formatted_tool_config=ToolConfig(function_calling_config=tool_config["function_calling_config"])formatted_safety_settings=[]ifsafety_settings:formatted_safety_settings=[SafetySetting(category=c,threshold=t)forc,tinsafety_settings.items()]request=GenerateContentRequest(model=self.model,contents=history,tools=formatted_tools,tool_config=formatted_tool_config,safety_settings=formatted_safety_settings,generation_config=self._prepare_params(stop,generation_config=generation_config),)ifsystem_instruction:request.system_instruction=system_instructionreturnrequest
[docs]defget_num_tokens(self,text:str)->int:"""Get the number of tokens present in the text. Useful for checking if an input will fit in a model's context window. Args: text: The string input to tokenize. Returns: The integer number of tokens in the text. """result=self.client.count_tokens(model=self.model,contents=[Content(parts=[Part(text=text)])])returnresult.total_tokens
[docs]defbind_tools(self,tools:Sequence[Union[ToolDict,GoogleTool]],tool_config:Optional[Union[Dict,_ToolConfigDict]]=None,*,tool_choice:Optional[Union[_ToolChoiceType,bool]]=None,**kwargs:Any,)->Runnable[LanguageModelInput,BaseMessage]:"""Bind tool-like objects to this chat model. Assumes model is compatible with google-generativeAI tool-calling API. Args: tools: A list of tool definitions to bind to this chat model. Can be a pydantic model, callable, or BaseTool. Pydantic models, callables, and BaseTools will be automatically converted to their schema dictionary representation. **kwargs: Any additional parameters to pass to the :class:`~langchain.runnable.Runnable` constructor. """iftool_choiceandtool_config:raiseValueError("Must specify at most one of tool_choice and tool_config, received "f"both:\n\n{tool_choice=}\n\n{tool_config=}")# Bind dicts for easier serialization/deserialization.genai_tools=[tool_to_dict(convert_to_genai_function_declarations(tools))]iftool_choice:all_names=[f["name"]# type: ignore[index]fortingenai_toolsforfint["function_declarations"]]tool_config=_tool_choice_to_tool_config(tool_choice,all_names)returnself.bind(tools=genai_tools,tool_config=tool_config,**kwargs)