Source code for langchain_community.chat_models.perplexity
"""Wrapper around Perplexity APIs."""from__future__importannotationsimportloggingfromoperatorimportitemgetterfromtypingimport(Any,Dict,Iterator,List,Literal,Mapping,Optional,Tuple,Type,TypeVar,Union,)fromlangchain_core.callbacksimportCallbackManagerForLLMRunfromlangchain_core.language_modelsimportLanguageModelInputfromlangchain_core.language_models.chat_modelsimport(BaseChatModel,generate_from_stream,)fromlangchain_core.messagesimport(AIMessage,AIMessageChunk,BaseMessage,BaseMessageChunk,ChatMessage,ChatMessageChunk,FunctionMessageChunk,HumanMessage,HumanMessageChunk,SystemMessage,SystemMessageChunk,ToolMessageChunk,)fromlangchain_core.messages.aiimportUsageMetadatafromlangchain_core.output_parsersimportJsonOutputParser,PydanticOutputParserfromlangchain_core.outputsimportChatGeneration,ChatGenerationChunk,ChatResultfromlangchain_core.runnablesimportRunnable,RunnableMap,RunnablePassthroughfromlangchain_core.utilsimportfrom_env,get_pydantic_field_namesfromlangchain_core.utils.pydanticimport(is_basemodel_subclass,)frompydanticimportBaseModel,ConfigDict,Field,TypeAdapter,model_validatorfromtyping_extensionsimportSelf_BM=TypeVar("_BM",bound=BaseModel)_DictOrPydanticClass=Union[Dict[str,Any],Type[_BM],Type]_DictOrPydantic=Union[Dict,_BM]logger=logging.getLogger(__name__)def_is_pydantic_class(obj:Any)->bool:returnisinstance(obj,type)andis_basemodel_subclass(obj)def_create_usage_metadata(token_usage:dict)->UsageMetadata:input_tokens=token_usage.get("prompt_tokens",0)output_tokens=token_usage.get("completion_tokens",0)total_tokens=token_usage.get("total_tokens",input_tokens+output_tokens)returnUsageMetadata(input_tokens=input_tokens,output_tokens=output_tokens,total_tokens=total_tokens,)
[docs]classChatPerplexity(BaseChatModel):"""`Perplexity AI` Chat models API. Setup: To use, you should have the ``openai`` python package installed, and the environment variable ``PPLX_API_KEY`` set to 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. .. code-block:: bash pip install openai export PPLX_API_KEY=your_api_key Key init args - completion params: model: str Name of the model to use. e.g. "llama-3.1-sonar-small-128k-online" temperature: float Sampling temperature to use. Default is 0.7 max_tokens: Optional[int] Maximum number of tokens to generate. streaming: bool Whether to stream the results or not. Key init args - client params: pplx_api_key: Optional[str] API key for PerplexityChat API. Default is None. request_timeout: Optional[Union[float, Tuple[float, float]]] Timeout for requests to PerplexityChat completion API. Default is None. max_retries: int Maximum number of retries to make when generating. See full list of supported init args and their descriptions in the params section. Instantiate: .. code-block:: python from langchain_community.chat_models import ChatPerplexity llm = ChatPerplexity( model="llama-3.1-sonar-small-128k-online", temperature=0.7, ) Invoke: .. code-block:: python messages = [ ("system", "You are a chatbot."), ("user", "Hello!") ] llm.invoke(messages) Invoke with structured output: .. code-block:: python from pydantic import BaseModel class StructuredOutput(BaseModel): role: str content: str llm.with_structured_output(StructuredOutput) llm.invoke(messages) Invoke with perplexity-specific params: .. code-block:: python llm.invoke(messages, extra_body={"search_recency_filter": "week"}) Stream: .. code-block:: python for chunk in llm.stream(messages): print(chunk.content) Token usage: .. code-block:: python response = llm.invoke(messages) response.usage_metadata Response metadata: .. code-block:: python response = llm.invoke(messages) response.response_metadata """# noqa: E501client:Any=None#: :meta private:model:str="llama-3.1-sonar-small-128k-online""""Model name."""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."""pplx_api_key:Optional[str]=Field(default_factory=from_env("PPLX_API_KEY",default=None),alias="api_key")"""Base URL path for API requests, leave blank if not using a proxy or service emulator."""request_timeout:Optional[Union[float,Tuple[float,float]]]=Field(None,alias="timeout")"""Timeout for requests to PerplexityChat completion API. Default is None."""max_retries:int=6"""Maximum number of retries to make when generating."""streaming:bool=False"""Whether to stream the results or not."""max_tokens:Optional[int]=None"""Maximum number of tokens to generate."""model_config=ConfigDict(populate_by_name=True,)@propertydeflc_secrets(self)->Dict[str,str]:return{"pplx_api_key":"PPLX_API_KEY"}@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 a 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@model_validator(mode="after")defvalidate_environment(self)->Self:"""Validate that api key and python package exists in environment."""try:importopenaiexceptImportError:raiseImportError("Could not import openai python package. ""Please install it with `pip install openai`.")try:self.client=openai.OpenAI(api_key=self.pplx_api_key,base_url="https://api.perplexity.ai")exceptAttributeError:raiseValueError("`openai` has no `ChatCompletion` attribute, this is likely ""due to an old version of the openai package. Try upgrading it ""with `pip install --upgrade openai`.")returnself@propertydef_default_params(self)->Dict[str,Any]:"""Get the default parameters for calling PerplexityChat API."""return{"max_tokens":self.max_tokens,"stream":self.streaming,"temperature":self.temperature,**self.model_kwargs,}def_convert_message_to_dict(self,message:BaseMessage)->Dict[str,Any]:ifisinstance(message,ChatMessage):message_dict={"role":message.role,"content":message.content}elifisinstance(message,SystemMessage):message_dict={"role":"system","content":message.content}elifisinstance(message,HumanMessage):message_dict={"role":"user","content":message.content}elifisinstance(message,AIMessage):message_dict={"role":"assistant","content":message.content}else:raiseTypeError(f"Got unknown type {message}")returnmessage_dictdef_create_message_dicts(self,messages:List[BaseMessage],stop:Optional[List[str]])->Tuple[List[Dict[str,Any]],Dict[str,Any]]:params=dict(self._invocation_params)ifstopisnotNone:if"stop"inparams:raiseValueError("`stop` found in both the input and default params.")params["stop"]=stopmessage_dicts=[self._convert_message_to_dict(m)forminmessages]returnmessage_dicts,paramsdef_convert_delta_to_message_chunk(self,_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_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}default_chunk_class=AIMessageChunkparams.pop("stream",None)ifstop:params["stop_sequences"]=stopstream_resp=self.client.chat.completions.create(messages=message_dicts,stream=True,**params)first_chunk=Trueprev_total_usage:Optional[UsageMetadata]=Noneforchunkinstream_resp:ifnotisinstance(chunk,dict):chunk=chunk.dict()# Collect standard usage metadata (transform from aggregate to delta)iftotal_usage:=chunk.get("usage"):lc_total_usage=_create_usage_metadata(total_usage)ifprev_total_usage:usage_metadata:Optional[UsageMetadata]={"input_tokens":lc_total_usage["input_tokens"]-prev_total_usage["input_tokens"],"output_tokens":lc_total_usage["output_tokens"]-prev_total_usage["output_tokens"],"total_tokens":lc_total_usage["total_tokens"]-prev_total_usage["total_tokens"],}else:usage_metadata=lc_total_usageprev_total_usage=lc_total_usageelse:usage_metadata=Noneiflen(chunk["choices"])==0:continuechoice=chunk["choices"][0]additional_kwargs={}iffirst_chunk:additional_kwargs["citations"]=chunk.get("citations",[])forattrin["images","related_questions"]:ifattrinchunk:additional_kwargs[attr]=chunk[attr]chunk=self._convert_delta_to_message_chunk(choice["delta"],default_chunk_class)ifisinstance(chunk,AIMessageChunk)andusage_metadata:chunk.usage_metadata=usage_metadataiffirst_chunk:chunk.additional_kwargs|=additional_kwargsfirst_chunk=Falsefinish_reason=choice.get("finish_reason")generation_info=(dict(finish_reason=finish_reason)iffinish_reasonisnotNoneelseNone)default_chunk_class=chunk.__class__chunk=ChatGenerationChunk(message=chunk,generation_info=generation_info)ifrun_manager:run_manager.on_llm_new_token(chunk.text,chunk=chunk)yieldchunkdef_generate(self,messages:List[BaseMessage],stop:Optional[List[str]]=None,run_manager:Optional[CallbackManagerForLLMRun]=None,**kwargs:Any,)->ChatResult:ifself.streaming:stream_iter=self._stream(messages,stop=stop,run_manager=run_manager,**kwargs)ifstream_iter:returngenerate_from_stream(stream_iter)message_dicts,params=self._create_message_dicts(messages,stop)params={**params,**kwargs}response=self.client.chat.completions.create(messages=message_dicts,**params)ifusage:=getattr(response,"usage",None):usage_metadata=_create_usage_metadata(usage.model_dump())else:usage_metadata=Noneadditional_kwargs={"citations":response.citations}forattrin["images","related_questions"]:ifhasattr(response,attr):additional_kwargs[attr]=getattr(response,attr)message=AIMessage(content=response.choices[0].message.content,additional_kwargs=additional_kwargs,usage_metadata=usage_metadata,)returnChatResult(generations=[ChatGeneration(message=message)])@propertydef_invocation_params(self)->Mapping[str,Any]:"""Get the parameters used to invoke the model."""pplx_creds:Dict[str,Any]={"model":self.model,}return{**pplx_creds,**self._default_params}@propertydef_llm_type(self)->str:"""Return type of chat model."""return"perplexitychat"
[docs]defwith_structured_output(self,schema:Optional[_DictOrPydanticClass]=None,*,method:Literal["json_schema"]="json_schema",include_raw:bool=False,strict:Optional[bool]=None,**kwargs:Any,)->Runnable[LanguageModelInput,_DictOrPydantic]:"""Model wrapper that returns outputs formatted to match the given schema for Preplexity. Currently, Preplexity only supports "json_schema" method for structured output as per their official documentation: https://docs.perplexity.ai/guides/structured-outputs Args: schema: The output schema. Can be passed in as: - a JSON Schema, - a TypedDict class, - or a Pydantic class method: The method for steering model generation, currently only support: - "json_schema": Use the JSON Schema to parse the model output include_raw: If False then only the parsed structured output is returned. If an error occurs during model output parsing it will be raised. If True then both the raw model response (a BaseMessage) and the parsed model response will be returned. If an error occurs during output parsing it will be caught and returned as well. The final output is always a dict with keys "raw", "parsed", and "parsing_error". kwargs: Additional keyword args aren't supported. Returns: A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`. | If ``include_raw`` is False and ``schema`` is a Pydantic class, Runnable outputs an instance of ``schema`` (i.e., a Pydantic object). Otherwise, if ``include_raw`` is False then Runnable outputs a dict. | If ``include_raw`` is True, then Runnable outputs a dict with keys: - "raw": BaseMessage - "parsed": None if there was a parsing error, otherwise the type depends on the ``schema`` as described above. - "parsing_error": Optional[BaseException] """# noqa: E501ifmethodin("function_calling","json_mode"):method="json_schema"ifmethod=="json_schema":ifschemaisNone:raiseValueError("schema must be specified when method is not 'json_schema'. ""Received None.")is_pydantic_schema=_is_pydantic_class(schema)ifis_pydantic_schemaandhasattr(schema,"model_json_schema"):# accounting for pydantic v1 and v2response_format=schema.model_json_schema()# type: ignore[union-attr]elifis_pydantic_schema:response_format=schema.schema()# type: ignore[union-attr]elifisinstance(schema,dict):response_format=schemaeliftype(schema).__name__=="_TypedDictMeta":adapter=TypeAdapter(schema)# if use passes typeddictresponse_format=adapter.json_schema()llm=self.bind(response_format={"type":"json_schema","json_schema":{"schema":response_format},})output_parser=(PydanticOutputParser(pydantic_object=schema)# type: ignore[arg-type]ifis_pydantic_schemaelseJsonOutputParser())else:raiseValueError(f"Unrecognized method argument. Expected 'json_schema' Received:\ '{method}'")ifinclude_raw:parser_assign=RunnablePassthrough.assign(parsed=itemgetter("raw")|output_parser,parsing_error=lambda_:None)parser_none=RunnablePassthrough.assign(parsed=lambda_:None)parser_with_fallback=parser_assign.with_fallbacks([parser_none],exception_key="parsing_error")returnRunnableMap(raw=llm)|parser_with_fallbackelse:returnllm|output_parser