libs/core/langchain_core/language_models/llms.py PYTHON 1,566 lines View on github.com → Search inside
1"""Base interface for traditional large language models (LLMs) to expose.23These are traditionally older models (newer models generally are chat models).4"""56from __future__ import annotations78import asyncio9import builtins10import functools11import json12import logging13from abc import ABC, abstractmethod14from collections.abc import AsyncIterator, Callable, Iterator, Sequence15from pathlib import Path16from typing import (17    TYPE_CHECKING,18    Any,19    cast,20)2122import yaml23from pydantic import ConfigDict24from tenacity import (25    RetryCallState,26    before_sleep_log,27    retry,28    retry_base,29    retry_if_exception_type,30    stop_after_attempt,31    wait_exponential,32)33from typing_extensions import override3435from langchain_core._api import deprecated, suppress_langchain_deprecation_warning36from langchain_core.caches import BaseCache37from langchain_core.callbacks import (38    AsyncCallbackManager,39    AsyncCallbackManagerForLLMRun,40    BaseCallbackManager,41    CallbackManager,42    CallbackManagerForLLMRun,43    Callbacks,44)45from langchain_core.globals import get_llm_cache46from langchain_core.language_models._utils import _filter_invocation_params_for_tracing47from langchain_core.language_models.base import (48    BaseLanguageModel,49    LangSmithParams,50    LanguageModelInput,51)52from langchain_core.load import dumpd53from langchain_core.messages import (54    convert_to_messages,55)56from langchain_core.outputs import Generation, GenerationChunk, LLMResult, RunInfo57from langchain_core.prompt_values import ChatPromptValue, PromptValue, StringPromptValue58from langchain_core.runnables import RunnableConfig, ensure_config, get_config_list59from langchain_core.runnables.config import run_in_executor60from langchain_core.utils._signature import _signature_parameters6162if TYPE_CHECKING:63    import builtins64    import uuid6566logger = logging.getLogger(__name__)6768_background_tasks: set[asyncio.Task[None]] = set()697071@functools.lru_cache72def _log_error_once(msg: str) -> None:73    """Log an error once."""74    logger.error(msg)757677def create_base_retry_decorator(78    error_types: list[type[BaseException]],79    max_retries: int = 1,80    run_manager: AsyncCallbackManagerForLLMRun | CallbackManagerForLLMRun | None = None,81) -> Callable[[Any], Any]:82    """Create a retry decorator for a given LLM and provided a list of error types.8384    Args:85        error_types: List of error types to retry on.86        max_retries: Number of retries.87        run_manager: Callback manager for the run.8889    Returns:90        A retry decorator.9192    Raises:93        ValueError: If the cache is not set and cache is True.94    """95    logging_ = before_sleep_log(logger, logging.WARNING)9697    def _before_sleep(retry_state: RetryCallState) -> None:98        logging_(retry_state)99        if run_manager:100            if isinstance(run_manager, AsyncCallbackManagerForLLMRun):101                coro = run_manager.on_retry(retry_state)102                try:103                    try:104                        loop = asyncio.get_event_loop()105                    except RuntimeError:106                        asyncio.run(coro)107                    else:108                        if loop.is_running():109                            task = loop.create_task(coro)110                            _background_tasks.add(task)111                            task.add_done_callback(_background_tasks.discard)112                        else:113                            asyncio.run(coro)114                except Exception as e:115                    _log_error_once(f"Error in on_retry: {e}")116            else:117                run_manager.on_retry(retry_state)118119    min_seconds = 4120    max_seconds = 10121    # Wait 2^x * 1 second between each retry starting with122    # 4 seconds, then up to 10 seconds, then 10 seconds afterwards123    retry_instance: retry_base = retry_if_exception_type(error_types[0])124    for error in error_types[1:]:125        retry_instance |= retry_if_exception_type(error)126    return retry(127        reraise=True,128        stop=stop_after_attempt(max_retries),129        wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds),130        retry=retry_instance,131        before_sleep=_before_sleep,132    )133134135def _resolve_cache(*, cache: BaseCache | bool | None) -> BaseCache | None:136    """Resolve the cache."""137    llm_cache: BaseCache | None138    if isinstance(cache, BaseCache):139        llm_cache = cache140    elif cache is None:141        llm_cache = get_llm_cache()142    elif cache is True:143        llm_cache = get_llm_cache()144        if llm_cache is None:145            msg = (146                "No global cache was configured. Use `set_llm_cache`."147                "to set a global cache if you want to use a global cache."148                "Otherwise either pass a cache object or set cache to False/None"149            )150            raise ValueError(msg)151    elif cache is False:152        llm_cache = None153    else:154        msg = f"Unsupported cache value {cache}"  # type: ignore[unreachable]155        raise ValueError(msg)156    return llm_cache157158159def get_prompts(160    params: dict[str, Any],161    prompts: list[str],162    cache: BaseCache | bool | None = None,  # noqa: FBT001163) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:164    """Get prompts that are already cached.165166    Args:167        params: Dictionary of parameters.168        prompts: List of prompts.169        cache: Cache object.170171    Returns:172        A tuple of existing prompts, llm_string, missing prompt indexes,173            and missing prompts.174175    Raises:176        ValueError: If the cache is not set and cache is True.177    """178    llm_string = str(sorted(params.items()))179    missing_prompts = []180    missing_prompt_idxs = []181    existing_prompts = {}182183    llm_cache = _resolve_cache(cache=cache)184    for i, prompt in enumerate(prompts):185        if llm_cache is not None:186            cache_val = llm_cache.lookup(prompt, llm_string)187            if isinstance(cache_val, list):188                existing_prompts[i] = cache_val189                continue190        missing_prompts.append(prompt)191        missing_prompt_idxs.append(i)192    return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts193194195async def aget_prompts(196    params: dict[str, Any],197    prompts: list[str],198    cache: BaseCache | bool | None = None,  # noqa: FBT001199) -> tuple[dict[int, list[Generation]], str, list[int], list[str]]:200    """Get prompts that are already cached. Async version.201202    Args:203        params: Dictionary of parameters.204        prompts: List of prompts.205        cache: Cache object.206207    Returns:208        A tuple of existing prompts, llm_string, missing prompt indexes,209            and missing prompts.210211    Raises:212        ValueError: If the cache is not set and cache is True.213    """214    llm_string = str(sorted(params.items()))215    missing_prompts = []216    missing_prompt_idxs = []217    existing_prompts = {}218    llm_cache = _resolve_cache(cache=cache)219    for i, prompt in enumerate(prompts):220        if llm_cache is not None:221            cache_val = await llm_cache.alookup(prompt, llm_string)222            if isinstance(cache_val, list):223                existing_prompts[i] = cache_val224                continue225        missing_prompts.append(prompt)226        missing_prompt_idxs.append(i)227    return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts228229230def update_cache(231    cache: BaseCache | bool | None,  # noqa: FBT001232    existing_prompts: dict[int, list[Generation]],233    llm_string: str,234    missing_prompt_idxs: list[int],235    new_results: LLMResult,236    prompts: list[str],237) -> dict[str, Any] | None:238    """Update the cache and get the LLM output.239240    Args:241        cache: Cache object.242        existing_prompts: Dictionary of existing prompts.243        llm_string: LLM string.244        missing_prompt_idxs: List of missing prompt indexes.245        new_results: LLMResult object.246        prompts: List of prompts.247248    Returns:249        LLM output.250251    Raises:252        ValueError: If the cache is not set and cache is True.253    """254    llm_cache = _resolve_cache(cache=cache)255    for i, result in enumerate(new_results.generations):256        existing_prompts[missing_prompt_idxs[i]] = result257        prompt = prompts[missing_prompt_idxs[i]]258        if llm_cache is not None:259            llm_cache.update(prompt, llm_string, result)260    return new_results.llm_output261262263async def aupdate_cache(264    cache: BaseCache | bool | None,  # noqa: FBT001265    existing_prompts: dict[int, list[Generation]],266    llm_string: str,267    missing_prompt_idxs: list[int],268    new_results: LLMResult,269    prompts: list[str],270) -> dict[str, Any] | None:271    """Update the cache and get the LLM output. Async version.272273    Args:274        cache: Cache object.275        existing_prompts: Dictionary of existing prompts.276        llm_string: LLM string.277        missing_prompt_idxs: List of missing prompt indexes.278        new_results: LLMResult object.279        prompts: List of prompts.280281    Returns:282        LLM output.283284    Raises:285        ValueError: If the cache is not set and cache is True.286    """287    llm_cache = _resolve_cache(cache=cache)288    for i, result in enumerate(new_results.generations):289        existing_prompts[missing_prompt_idxs[i]] = result290        prompt = prompts[missing_prompt_idxs[i]]291        if llm_cache is not None:292            await llm_cache.aupdate(prompt, llm_string, result)293    return new_results.llm_output294295296class BaseLLM(BaseLanguageModel[str], ABC):297    """Base LLM abstract interface.298299    It should take in a prompt and return a string.300    """301302    model_config = ConfigDict(303        arbitrary_types_allowed=True,304    )305306    @functools.cached_property307    def _serialized(self) -> builtins.dict[str, Any]:308        # self is always a Serializable object in this case, thus the result is309        # guaranteed to be a dict since dumpd uses the default callback, which uses310        # obj.to_json which always returns TypedDict subclasses311        return cast("builtins.dict[str, Any]", dumpd(self))312313    # --- Runnable methods ---314315    @property316    @override317    def OutputType(self) -> type[str]:318        """Get the output type for this `Runnable`."""319        return str320321    def _convert_input(self, model_input: LanguageModelInput) -> PromptValue:322        if isinstance(model_input, PromptValue):323            return model_input324        if isinstance(model_input, str):325            return StringPromptValue(text=model_input)326        if isinstance(model_input, Sequence):327            return ChatPromptValue(messages=convert_to_messages(model_input))328        msg = (  # type: ignore[unreachable]329            f"Invalid input type {type(model_input)}. "330            "Must be a PromptValue, str, or list of BaseMessages."331        )332        raise ValueError(msg)333334    def _get_ls_params(335        self,336        stop: list[str] | None = None,337        **kwargs: Any,338    ) -> LangSmithParams:339        """Get standard params for tracing."""340        # get default provider from class name341        default_provider = self.__class__.__name__342        default_provider = default_provider.removesuffix("LLM")343        default_provider = default_provider.lower()344345        ls_params = LangSmithParams(ls_provider=default_provider, ls_model_type="llm")346        if stop:347            ls_params["ls_stop"] = stop348349        # model350        if "model" in kwargs and isinstance(kwargs["model"], str):351            ls_params["ls_model_name"] = kwargs["model"]352        elif hasattr(self, "model") and isinstance(self.model, str):353            ls_params["ls_model_name"] = self.model354        elif hasattr(self, "model_name") and isinstance(self.model_name, str):355            ls_params["ls_model_name"] = self.model_name356357        # temperature358        if "temperature" in kwargs and isinstance(kwargs["temperature"], (int, float)):359            ls_params["ls_temperature"] = kwargs["temperature"]360        elif hasattr(self, "temperature") and isinstance(361            self.temperature, (int, float)362        ):363            ls_params["ls_temperature"] = self.temperature364365        # max_tokens366        if "max_tokens" in kwargs and isinstance(kwargs["max_tokens"], int):367            ls_params["ls_max_tokens"] = kwargs["max_tokens"]368        elif hasattr(self, "max_tokens") and isinstance(self.max_tokens, int):369            ls_params["ls_max_tokens"] = self.max_tokens370371        return ls_params372373    @override374    def invoke(375        self,376        input: LanguageModelInput,377        config: RunnableConfig | None = None,378        *,379        stop: list[str] | None = None,380        **kwargs: Any,381    ) -> str:382        config = ensure_config(config)383        return (384            self.generate_prompt(385                [self._convert_input(input)],386                stop=stop,387                callbacks=config.get("callbacks"),388                tags=config.get("tags"),389                metadata=config.get("metadata"),390                run_name=config.get("run_name"),391                run_id=config.pop("run_id", None),392                **kwargs,393            )394            .generations[0][0]395            .text396        )397398    @override399    async def ainvoke(400        self,401        input: LanguageModelInput,402        config: RunnableConfig | None = None,403        *,404        stop: list[str] | None = None,405        **kwargs: Any,406    ) -> str:407        config = ensure_config(config)408        llm_result = await self.agenerate_prompt(409            [self._convert_input(input)],410            stop=stop,411            callbacks=config.get("callbacks"),412            tags=config.get("tags"),413            metadata=config.get("metadata"),414            run_name=config.get("run_name"),415            run_id=config.pop("run_id", None),416            **kwargs,417        )418        return llm_result.generations[0][0].text419420    @override421    def batch(422        self,423        inputs: list[LanguageModelInput],424        config: RunnableConfig | list[RunnableConfig] | None = None,425        *,426        return_exceptions: bool = False,427        **kwargs: Any,428    ) -> list[str]:429        if not inputs:430            return []431432        config = get_config_list(config, len(inputs))433        max_concurrency = config[0].get("max_concurrency")434435        if max_concurrency is None:436            try:437                llm_result = self.generate_prompt(438                    [self._convert_input(input_) for input_ in inputs],439                    callbacks=[c.get("callbacks") for c in config],440                    tags=[c.get("tags") for c in config],441                    metadata=[c.get("metadata") for c in config],442                    run_name=[c.get("run_name") for c in config],443                    **kwargs,444                )445                return [g[0].text for g in llm_result.generations]446            except Exception as e:447                if return_exceptions:448                    return cast("list[str]", [e for _ in inputs])449                raise450        else:451            batches = [452                inputs[i : i + max_concurrency]453                for i in range(0, len(inputs), max_concurrency)454            ]455            config = [{**c, "max_concurrency": None} for c in config]456            return [457                output458                for i, batch in enumerate(batches)459                for output in self.batch(460                    batch,461                    config=config[i * max_concurrency : (i + 1) * max_concurrency],462                    return_exceptions=return_exceptions,463                    **kwargs,464                )465            ]466467    @override468    async def abatch(469        self,470        inputs: list[LanguageModelInput],471        config: RunnableConfig | list[RunnableConfig] | None = None,472        *,473        return_exceptions: bool = False,474        **kwargs: Any,475    ) -> list[str]:476        if not inputs:477            return []478        config = get_config_list(config, len(inputs))479        max_concurrency = config[0].get("max_concurrency")480481        if max_concurrency is None:482            try:483                llm_result = await self.agenerate_prompt(484                    [self._convert_input(input_) for input_ in inputs],485                    callbacks=[c.get("callbacks") for c in config],486                    tags=[c.get("tags") for c in config],487                    metadata=[c.get("metadata") for c in config],488                    run_name=[c.get("run_name") for c in config],489                    **kwargs,490                )491                return [g[0].text for g in llm_result.generations]492            except Exception as e:493                if return_exceptions:494                    return cast("list[str]", [e for _ in inputs])495                raise496        else:497            batches = [498                inputs[i : i + max_concurrency]499                for i in range(0, len(inputs), max_concurrency)500            ]501            config = [{**c, "max_concurrency": None} for c in config]502            return [503                output504                for i, batch in enumerate(batches)505                for output in await self.abatch(506                    batch,507                    config=config[i * max_concurrency : (i + 1) * max_concurrency],508                    return_exceptions=return_exceptions,509                    **kwargs,510                )511            ]512513    @override514    def stream(515        self,516        input: LanguageModelInput,517        config: RunnableConfig | None = None,518        *,519        stop: list[str] | None = None,520        **kwargs: Any,521    ) -> Iterator[str]:522        if type(self)._stream == BaseLLM._stream:  # noqa: SLF001523            # model doesn't implement streaming, so use default implementation524            yield self.invoke(input, config=config, stop=stop, **kwargs)525        else:526            prompt = self._convert_input(input).to_string()527            config = ensure_config(config)528            params = self._dict_for_compat()529            params["stop"] = stop530            params = {**params, **kwargs}531            options = {"stop": stop}532            inheritable_metadata = {533                **(config.get("metadata") or {}),534                **self._get_ls_params_with_defaults(stop=stop, **kwargs),535            }536            callback_manager = CallbackManager.configure(537                config.get("callbacks"),538                self.callbacks,539                self.verbose,540                config.get("tags"),541                self.tags,542                inheritable_metadata,543                self.metadata,544                langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(545                    params546                ),547            )548            (run_manager,) = callback_manager.on_llm_start(549                self._serialized,550                [prompt],551                invocation_params=params,552                options=options,553                name=config.get("run_name"),554                run_id=config.pop("run_id", None),555                batch_size=1,556            )557            generation: GenerationChunk | None = None558            try:559                for chunk in self._stream(560                    prompt, stop=stop, run_manager=run_manager, **kwargs561                ):562                    yield chunk.text563                    if generation is None:564                        generation = chunk565                    else:566                        generation += chunk567            except BaseException as e:568                run_manager.on_llm_error(569                    e,570                    response=LLMResult(571                        generations=[[generation]] if generation else []572                    ),573                )574                raise575576            if generation is None:577                err = ValueError("No generation chunks were returned")578                run_manager.on_llm_error(err, response=LLMResult(generations=[]))579                raise err580581            run_manager.on_llm_end(LLMResult(generations=[[generation]]))582583    @override584    async def astream(585        self,586        input: LanguageModelInput,587        config: RunnableConfig | None = None,588        *,589        stop: list[str] | None = None,590        **kwargs: Any,591    ) -> AsyncIterator[str]:592        if (593            type(self)._astream is BaseLLM._astream  # noqa: SLF001594            and type(self)._stream is BaseLLM._stream  # noqa: SLF001595        ):596            yield await self.ainvoke(input, config=config, stop=stop, **kwargs)597            return598599        prompt = self._convert_input(input).to_string()600        config = ensure_config(config)601        params = self._dict_for_compat()602        params["stop"] = stop603        params = {**params, **kwargs}604        options = {"stop": stop}605        inheritable_metadata = {606            **(config.get("metadata") or {}),607            **self._get_ls_params_with_defaults(stop=stop, **kwargs),608        }609        callback_manager = AsyncCallbackManager.configure(610            config.get("callbacks"),611            self.callbacks,612            self.verbose,613            config.get("tags"),614            self.tags,615            inheritable_metadata,616            self.metadata,617            langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(618                params619            ),620        )621        (run_manager,) = await callback_manager.on_llm_start(622            self._serialized,623            [prompt],624            invocation_params=params,625            options=options,626            name=config.get("run_name"),627            run_id=config.pop("run_id", None),628            batch_size=1,629        )630        generation: GenerationChunk | None = None631        try:632            async for chunk in self._astream(633                prompt,634                stop=stop,635                run_manager=run_manager,636                **kwargs,637            ):638                yield chunk.text639                if generation is None:640                    generation = chunk641                else:642                    generation += chunk643        except BaseException as e:644            await run_manager.on_llm_error(645                e,646                response=LLMResult(generations=[[generation]] if generation else []),647            )648            raise649650        if generation is None:651            err = ValueError("No generation chunks were returned")652            await run_manager.on_llm_error(err, response=LLMResult(generations=[]))653            raise err654655        await run_manager.on_llm_end(LLMResult(generations=[[generation]]))656657    # --- Custom methods ---658659    @abstractmethod660    def _generate(661        self,662        prompts: list[str],663        stop: list[str] | None = None,664        run_manager: CallbackManagerForLLMRun | None = None,665        **kwargs: Any,666    ) -> LLMResult:667        """Run the LLM on the given prompts.668669        Args:670            prompts: The prompts to generate from.671            stop: Stop words to use when generating.672673                Model output is cut off at the first occurrence of any of these674                substrings.675676                If stop tokens are not supported consider raising `NotImplementedError`.677            run_manager: Callback manager for the run.678679        Returns:680            The LLM result.681        """682683    async def _agenerate(684        self,685        prompts: list[str],686        stop: list[str] | None = None,687        run_manager: AsyncCallbackManagerForLLMRun | None = None,688        **kwargs: Any,689    ) -> LLMResult:690        """Run the LLM on the given prompts.691692        Args:693            prompts: The prompts to generate from.694            stop: Stop words to use when generating.695696                Model output is cut off at the first occurrence of any of these697                substrings.698699                If stop tokens are not supported consider raising `NotImplementedError`.700            run_manager: Callback manager for the run.701702        Returns:703            The LLM result.704        """705        return await run_in_executor(706            None,707            self._generate,708            prompts,709            stop,710            run_manager.get_sync() if run_manager else None,711            **kwargs,712        )713714    def _stream(715        self,716        prompt: str,717        stop: list[str] | None = None,718        run_manager: CallbackManagerForLLMRun | None = None,719        **kwargs: Any,720    ) -> Iterator[GenerationChunk]:721        """Stream the LLM on the given prompt.722723        This method should be overridden by subclasses that support streaming.724725        If not implemented, the default behavior of calls to stream will be to726        fallback to the non-streaming version of the model and return727        the output as a single chunk.728729        Args:730            prompt: The prompt to generate from.731            stop: Stop words to use when generating.732733                Model output is cut off at the first occurrence of any of these734                substrings.735            run_manager: Callback manager for the run.736            **kwargs: Arbitrary additional keyword arguments.737738                These are usually passed to the model provider API call.739740        Yields:741            Generation chunks.742        """743        raise NotImplementedError744745    async def _astream(746        self,747        prompt: str,748        stop: list[str] | None = None,749        run_manager: AsyncCallbackManagerForLLMRun | None = None,750        **kwargs: Any,751    ) -> AsyncIterator[GenerationChunk]:752        """An async version of the _stream method.753754        The default implementation uses the synchronous _stream method and wraps it in755        an async iterator. Subclasses that need to provide a true async implementation756        should override this method.757758        Args:759            prompt: The prompt to generate from.760            stop: Stop words to use when generating.761762                Model output is cut off at the first occurrence of any of these763                substrings.764            run_manager: Callback manager for the run.765            **kwargs: Arbitrary additional keyword arguments.766767                These are usually passed to the model provider API call.768769        Yields:770            Generation chunks.771        """772        iterator = await run_in_executor(773            None,774            self._stream,775            prompt,776            stop,777            run_manager.get_sync() if run_manager else None,778            **kwargs,779        )780        done = object()781        while True:782            item = await run_in_executor(783                None,784                next,785                iterator,786                done,787            )788            if item is done:789                break790            yield item  # type: ignore[misc]791792    @override793    def generate_prompt(794        self,795        prompts: list[PromptValue],796        stop: list[str] | None = None,797        callbacks: Callbacks | list[Callbacks] | None = None,798        **kwargs: Any,799    ) -> LLMResult:800        prompt_strings = [p.to_string() for p in prompts]801        return self.generate(prompt_strings, stop=stop, callbacks=callbacks, **kwargs)802803    @override804    async def agenerate_prompt(805        self,806        prompts: list[PromptValue],807        stop: list[str] | None = None,808        callbacks: Callbacks | list[Callbacks] | None = None,809        **kwargs: Any,810    ) -> LLMResult:811        prompt_strings = [p.to_string() for p in prompts]812        return await self.agenerate(813            prompt_strings, stop=stop, callbacks=callbacks, **kwargs814        )815816    def _generate_helper(817        self,818        prompts: list[str],819        stop: list[str] | None,820        run_managers: list[CallbackManagerForLLMRun],821        *,822        new_arg_supported: bool,823        **kwargs: Any,824    ) -> LLMResult:825        try:826            output = (827                self._generate(828                    prompts,829                    stop=stop,830                    # TODO: support multiple run managers831                    run_manager=run_managers[0] if run_managers else None,832                    **kwargs,833                )834                if new_arg_supported835                else self._generate(prompts, stop=stop)836            )837        except BaseException as e:838            for run_manager in run_managers:839                run_manager.on_llm_error(e, response=LLMResult(generations=[]))840            raise841        flattened_outputs = output.flatten()842        for manager, flattened_output in zip(843            run_managers, flattened_outputs, strict=False844        ):845            manager.on_llm_end(flattened_output)846        if run_managers:847            output.run = [848                RunInfo(run_id=run_manager.run_id) for run_manager in run_managers849            ]850        return output851852    def generate(853        self,854        prompts: list[str],855        stop: list[str] | None = None,856        callbacks: Callbacks | list[Callbacks] | None = None,857        *,858        tags: list[str] | list[list[str]] | None = None,859        metadata: builtins.dict[str, Any] | list[builtins.dict[str, Any]] | None = None,860        run_name: str | list[str] | None = None,861        run_id: uuid.UUID | list[uuid.UUID | None] | None = None,862        **kwargs: Any,863    ) -> LLMResult:864        """Pass a sequence of prompts to a model and return generations.865866        This method should make use of batched calls for models that expose a batched867        API.868869        Use this method when you want to:870871        1. Take advantage of batched calls,872        2. Need more output from the model than just the top generated value,873        3. Are building chains that are agnostic to the underlying language model874            type (e.g., pure text completion models vs chat models).875876        Args:877            prompts: List of string prompts.878            stop: Stop words to use when generating.879880                Model output is cut off at the first occurrence of any of these881                substrings.882            callbacks: `Callbacks` to pass through.883884                Used for executing additional functionality, such as logging or885                streaming, throughout generation.886            tags: List of tags to associate with each prompt. If provided, the length887                of the list must match the length of the prompts list.888            metadata: List of metadata dictionaries to associate with each prompt. If889                provided, the length of the list must match the length of the prompts890                list.891            run_name: List of run names to associate with each prompt. If provided, the892                length of the list must match the length of the prompts list.893            run_id: List of run IDs to associate with each prompt. If provided, the894                length of the list must match the length of the prompts list.895            **kwargs: Arbitrary additional keyword arguments.896897                These are usually passed to the model provider API call.898899        Raises:900            ValueError: If prompts is not a list.901            ValueError: If the length of `callbacks`, `tags`, `metadata`, or902                `run_name` (if provided) does not match the length of prompts.903904        Returns:905            An `LLMResult`, which contains a list of candidate `Generations` for each906                input prompt and additional model provider-specific output.907        """908        if not isinstance(prompts, list):909            msg = (  # type: ignore[unreachable]910                "Argument 'prompts' is expected to be of type list[str], received"911                f" argument of type {type(prompts)}."912            )913            raise ValueError(msg)  # noqa: TRY004914        # Create callback managers915        if isinstance(metadata, list):916            metadata = [917                {918                    **(meta or {}),919                    **self._get_ls_params_with_defaults(stop=stop, **kwargs),920                }921                for meta in metadata922            ]923        elif isinstance(metadata, dict):924            metadata = {925                **(metadata or {}),926                **self._get_ls_params_with_defaults(stop=stop, **kwargs),927            }928        if (929            isinstance(callbacks, list)930            and callbacks931            and (932                isinstance(callbacks[0], (list, BaseCallbackManager))933                or callbacks[0] is None934            )935        ):936            # We've received a list of callbacks args to apply to each input937            if len(callbacks) != len(prompts):938                msg = "callbacks must be the same length as prompts"939                raise ValueError(msg)940            if tags is not None and not (941                isinstance(tags, list) and len(tags) == len(prompts)942            ):943                msg = "tags must be a list of the same length as prompts"944                raise ValueError(msg)945            if metadata is not None and not (946                isinstance(metadata, list) and len(metadata) == len(prompts)947            ):948                msg = "metadata must be a list of the same length as prompts"949                raise ValueError(msg)950            if run_name is not None and not (951                isinstance(run_name, list) and len(run_name) == len(prompts)952            ):953                msg = "run_name must be a list of the same length as prompts"954                raise ValueError(msg)955            tags_list = cast("list[list[str] | None]", tags or ([None] * len(prompts)))956            metadata_list = cast(957                "list[builtins.dict[str, Any] | None]",958                metadata or ([{}] * len(prompts)),959            )960            run_name_list = run_name or cast(961                "list[str | None]", ([None] * len(prompts))962            )963            params = self._dict_for_compat()964            params["stop"] = stop965            callback_managers = [966                CallbackManager.configure(967                    callback,968                    self.callbacks,969                    self.verbose,970                    tag,971                    self.tags,972                    meta,973                    self.metadata,974                    langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(975                        params976                    ),977                )978                for callback, tag, meta in zip(979                    callbacks, tags_list, metadata_list, strict=False980                )981            ]982        else:983            # We've received a single callbacks arg to apply to all inputs984            params = self._dict_for_compat()985            params["stop"] = stop986            callback_managers = [987                CallbackManager.configure(988                    cast("Callbacks", callbacks),989                    self.callbacks,990                    self.verbose,991                    cast("list[str]", tags),992                    self.tags,993                    cast("builtins.dict[str, Any]", metadata),994                    self.metadata,995                    langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(996                        params997                    ),998                )999            ] * len(prompts)1000            run_name_list = [cast("str | None", run_name)] * len(prompts)1001        run_ids_list = self._get_run_ids_list(run_id, prompts)1002        options = {"stop": stop}1003        (1004            existing_prompts,1005            llm_string,1006            missing_prompt_idxs,1007            missing_prompts,1008        ) = get_prompts(params, prompts, self.cache)1009        new_arg_supported = _signature_parameters(self._generate).get("run_manager")1010        if (self.cache is None and get_llm_cache() is None) or self.cache is False:1011            run_managers = [1012                callback_manager.on_llm_start(1013                    self._serialized,1014                    [prompt],1015                    invocation_params=params,1016                    options=options,1017                    name=run_name,1018                    batch_size=len(prompts),1019                    run_id=run_id_,1020                )[0]1021                for callback_manager, prompt, run_name, run_id_ in zip(1022                    callback_managers,1023                    prompts,1024                    run_name_list,1025                    run_ids_list,1026                    strict=False,1027                )1028            ]1029            return self._generate_helper(1030                prompts,1031                stop,1032                run_managers,1033                new_arg_supported=bool(new_arg_supported),1034                **kwargs,1035            )1036        if len(missing_prompts) > 0:1037            run_managers = [1038                callback_managers[idx].on_llm_start(1039                    self._serialized,1040                    [prompts[idx]],1041                    invocation_params=params,1042                    options=options,1043                    name=run_name_list[idx],1044                    batch_size=len(missing_prompts),1045                )[0]1046                for idx in missing_prompt_idxs1047            ]1048            new_results = self._generate_helper(1049                missing_prompts,1050                stop,1051                run_managers,1052                new_arg_supported=bool(new_arg_supported),1053                **kwargs,1054            )1055            llm_output = update_cache(1056                self.cache,1057                existing_prompts,1058                llm_string,1059                missing_prompt_idxs,1060                new_results,1061                prompts,1062            )1063            run_info = (1064                [RunInfo(run_id=run_manager.run_id) for run_manager in run_managers]1065                if run_managers1066                else None1067            )1068        else:1069            llm_output = {}1070            run_info = None1071        generations = [existing_prompts[i] for i in range(len(prompts))]1072        return LLMResult(generations=generations, llm_output=llm_output, run=run_info)10731074    @staticmethod1075    def _get_run_ids_list(1076        run_id: uuid.UUID | list[uuid.UUID | None] | None, prompts: list[str]1077    ) -> list[uuid.UUID | None]:1078        if run_id is None:1079            return [None] * len(prompts)1080        if isinstance(run_id, list):1081            if len(run_id) != len(prompts):1082                msg = (1083                    "Number of manually provided run_id's does not match batch length."1084                    f" {len(run_id)} != {len(prompts)}"1085                )1086                raise ValueError(msg)1087            return run_id1088        return [run_id] + [None] * (len(prompts) - 1)10891090    async def _agenerate_helper(1091        self,1092        prompts: list[str],1093        stop: list[str] | None,1094        run_managers: list[AsyncCallbackManagerForLLMRun],1095        *,1096        new_arg_supported: bool,1097        **kwargs: Any,1098    ) -> LLMResult:1099        try:1100            output = (1101                await self._agenerate(1102                    prompts,1103                    stop=stop,1104                    run_manager=run_managers[0] if run_managers else None,1105                    **kwargs,1106                )1107                if new_arg_supported1108                else await self._agenerate(prompts, stop=stop)1109            )1110        except BaseException as e:1111            await asyncio.gather(1112                *[1113                    run_manager.on_llm_error(e, response=LLMResult(generations=[]))1114                    for run_manager in run_managers1115                ]1116            )1117            raise1118        flattened_outputs = output.flatten()1119        await asyncio.gather(1120            *[1121                run_manager.on_llm_end(flattened_output)1122                for run_manager, flattened_output in zip(1123                    run_managers, flattened_outputs, strict=False1124                )1125            ]1126        )1127        if run_managers:1128            output.run = [1129                RunInfo(run_id=run_manager.run_id) for run_manager in run_managers1130            ]1131        return output11321133    async def agenerate(1134        self,1135        prompts: list[str],1136        stop: list[str] | None = None,1137        callbacks: Callbacks | list[Callbacks] | None = None,1138        *,1139        tags: list[str] | list[list[str]] | None = None,1140        metadata: builtins.dict[str, Any] | list[builtins.dict[str, Any]] | None = None,1141        run_name: str | list[str] | None = None,1142        run_id: uuid.UUID | list[uuid.UUID | None] | None = None,1143        **kwargs: Any,1144    ) -> LLMResult:1145        """Asynchronously pass a sequence of prompts to a model and return generations.11461147        This method should make use of batched calls for models that expose a batched1148        API.11491150        Use this method when you want to:11511152        1. Take advantage of batched calls,1153        2. Need more output from the model than just the top generated value,1154        3. Are building chains that are agnostic to the underlying language model1155            type (e.g., pure text completion models vs chat models).11561157        Args:1158            prompts: List of string prompts.1159            stop: Stop words to use when generating.11601161                Model output is cut off at the first occurrence of any of these1162                substrings.1163            callbacks: `Callbacks` to pass through.11641165                Used for executing additional functionality, such as logging or1166                streaming, throughout generation.1167            tags: List of tags to associate with each prompt. If provided, the length1168                of the list must match the length of the prompts list.1169            metadata: List of metadata dictionaries to associate with each prompt. If1170                provided, the length of the list must match the length of the prompts1171                list.1172            run_name: List of run names to associate with each prompt. If provided, the1173                length of the list must match the length of the prompts list.1174            run_id: List of run IDs to associate with each prompt. If provided, the1175                length of the list must match the length of the prompts list.1176            **kwargs: Arbitrary additional keyword arguments.11771178                These are usually passed to the model provider API call.11791180        Raises:1181            ValueError: If the length of `callbacks`, `tags`, `metadata`, or1182                `run_name` (if provided) does not match the length of prompts.11831184        Returns:1185            An `LLMResult`, which contains a list of candidate `Generations` for each1186                input prompt and additional model provider-specific output.1187        """1188        if isinstance(metadata, list):1189            metadata = [1190                {1191                    **(meta or {}),1192                    **self._get_ls_params_with_defaults(stop=stop, **kwargs),1193                }1194                for meta in metadata1195            ]1196        elif isinstance(metadata, dict):1197            metadata = {1198                **(metadata or {}),1199                **self._get_ls_params_with_defaults(stop=stop, **kwargs),1200            }1201        # Create callback managers1202        if isinstance(callbacks, list) and (1203            isinstance(callbacks[0], (list, BaseCallbackManager))1204            or callbacks[0] is None1205        ):1206            # We've received a list of callbacks args to apply to each input1207            if len(callbacks) != len(prompts):1208                msg = "callbacks must be the same length as prompts"1209                raise ValueError(msg)1210            if tags is not None and not (1211                isinstance(tags, list) and len(tags) == len(prompts)1212            ):1213                msg = "tags must be a list of the same length as prompts"1214                raise ValueError(msg)1215            if metadata is not None and not (1216                isinstance(metadata, list) and len(metadata) == len(prompts)1217            ):1218                msg = "metadata must be a list of the same length as prompts"1219                raise ValueError(msg)1220            if run_name is not None and not (1221                isinstance(run_name, list) and len(run_name) == len(prompts)1222            ):1223                msg = "run_name must be a list of the same length as prompts"1224                raise ValueError(msg)1225            tags_list = cast("list[list[str] | None]", tags or ([None] * len(prompts)))1226            metadata_list = cast(1227                "list[builtins.dict[str, Any] | None]",1228                metadata or ([{}] * len(prompts)),1229            )1230            run_name_list = run_name or cast(1231                "list[str | None]", ([None] * len(prompts))1232            )1233            params = self._dict_for_compat()1234            params["stop"] = stop1235            callback_managers = [1236                AsyncCallbackManager.configure(1237                    callback,1238                    self.callbacks,1239                    self.verbose,1240                    tag,1241                    self.tags,1242                    meta,1243                    self.metadata,1244                    langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(1245                        params1246                    ),1247                )1248                for callback, tag, meta in zip(1249                    callbacks, tags_list, metadata_list, strict=False1250                )1251            ]1252        else:1253            # We've received a single callbacks arg to apply to all inputs1254            params = self._dict_for_compat()1255            params["stop"] = stop1256            callback_managers = [1257                AsyncCallbackManager.configure(1258                    callbacks,1259                    self.callbacks,1260                    self.verbose,1261                    cast("list[str]", tags),1262                    self.tags,1263                    cast("builtins.dict[str, Any]", metadata),1264                    self.metadata,1265                    langsmith_inheritable_metadata=_filter_invocation_params_for_tracing(1266                        params1267                    ),1268                )1269            ] * len(prompts)1270            run_name_list = [cast("str | None", run_name)] * len(prompts)1271        run_ids_list = self._get_run_ids_list(run_id, prompts)1272        options = {"stop": stop}1273        (1274            existing_prompts,1275            llm_string,1276            missing_prompt_idxs,1277            missing_prompts,1278        ) = await aget_prompts(params, prompts, self.cache)12791280        # Verify whether the cache is set, and if the cache is set,1281        # verify whether the cache is available.1282        new_arg_supported = _signature_parameters(self._agenerate).get("run_manager")1283        if (self.cache is None and get_llm_cache() is None) or self.cache is False:1284            run_managers = await asyncio.gather(1285                *[1286                    callback_manager.on_llm_start(1287                        self._serialized,1288                        [prompt],1289                        invocation_params=params,1290                        options=options,1291                        name=run_name,1292                        batch_size=len(prompts),1293                        run_id=run_id_,1294                    )1295                    for callback_manager, prompt, run_name, run_id_ in zip(1296                        callback_managers,1297                        prompts,1298                        run_name_list,1299                        run_ids_list,1300                        strict=False,1301                    )1302                ]1303            )1304            run_managers = [r[0] for r in run_managers]  # type: ignore[misc]1305            return await self._agenerate_helper(1306                prompts,1307                stop,1308                run_managers,  # type: ignore[arg-type]1309                new_arg_supported=bool(new_arg_supported),1310                **kwargs,1311            )1312        if len(missing_prompts) > 0:1313            run_managers = await asyncio.gather(1314                *[1315                    callback_managers[idx].on_llm_start(1316                        self._serialized,1317                        [prompts[idx]],1318                        invocation_params=params,1319                        options=options,1320                        name=run_name_list[idx],1321                        batch_size=len(missing_prompts),1322                    )1323                    for idx in missing_prompt_idxs1324                ]1325            )1326            run_managers = [r[0] for r in run_managers]  # type: ignore[misc]1327            new_results = await self._agenerate_helper(1328                missing_prompts,1329                stop,1330                run_managers,  # type: ignore[arg-type]1331                new_arg_supported=bool(new_arg_supported),1332                **kwargs,1333            )1334            llm_output = await aupdate_cache(1335                self.cache,1336                existing_prompts,1337                llm_string,1338                missing_prompt_idxs,1339                new_results,1340                prompts,1341            )1342            run_info = (1343                [RunInfo(run_id=run_manager.run_id) for run_manager in run_managers]  # type: ignore[attr-defined]1344                if run_managers1345                else None1346            )1347        else:1348            llm_output = {}1349            run_info = None1350        generations = [existing_prompts[i] for i in range(len(prompts))]1351        return LLMResult(generations=generations, llm_output=llm_output, run=run_info)13521353    async def _call_async(1354        self,1355        prompt: str,1356        stop: list[str] | None = None,1357        callbacks: Callbacks = None,1358        *,1359        tags: list[str] | None = None,1360        metadata: builtins.dict[str, Any] | None = None,1361        **kwargs: Any,1362    ) -> str:1363        """Check Cache and run the LLM on the given prompt and input."""1364        result = await self.agenerate(1365            [prompt],1366            stop=stop,1367            callbacks=callbacks,1368            tags=tags,1369            metadata=metadata,1370            **kwargs,1371        )1372        return result.generations[0][0].text13731374    def __str__(self) -> str:1375        """Return a string representation of the object for printing."""1376        cls_name = f"\033[1m{self.__class__.__name__}\033[0m"1377        return f"{cls_name}\nParams: {self._identifying_params}"13781379    @property1380    @abstractmethod1381    def _llm_type(self) -> str:1382        """Return type of llm."""13831384    @deprecated("1.4.2", alternative="asdict", removal="2.0.0")1385    @override1386    def dict(self, **_kwargs: Any) -> builtins.dict[str, Any]:1387        """DEPRECATED - use `asdict()` instead.13881389        Return a dictionary representation of the LLM.1390        """1391        return self.asdict()13921393    def asdict(self) -> builtins.dict[str, Any]:1394        """Return a dictionary representation of the LLM."""1395        starter_dict = dict(self._identifying_params)1396        starter_dict["_type"] = self._llm_type1397        return starter_dict13981399    def _dict_for_compat(self) -> builtins.dict[str, Any]:1400        """Return the LLM dictionary while preserving deprecated overrides."""1401        with suppress_langchain_deprecation_warning():1402            return self.dict()14031404    def save(self, file_path: Path | str) -> None:1405        """Save the LLM.14061407        Args:1408            file_path: Path to file to save the LLM to.14091410        Raises:1411            ValueError: If the file path is not a string or Path object.14121413        Example:1414            ```python1415            llm.save(file_path="path/llm.yaml")1416            ```1417        """1418        # Convert file to Path object.1419        save_path = Path(file_path)14201421        directory_path = save_path.parent1422        directory_path.mkdir(parents=True, exist_ok=True)14231424        # Fetch dictionary to save1425        prompt_dict = self._dict_for_compat()14261427        if save_path.suffix == ".json":1428            with save_path.open("w", encoding="utf-8") as f:1429                json.dump(prompt_dict, f, indent=4)1430        elif save_path.suffix.endswith((".yaml", ".yml")):1431            with save_path.open("w", encoding="utf-8") as f:1432                yaml.dump(prompt_dict, f, default_flow_style=False)1433        else:1434            msg = f"{save_path} must be json or yaml"1435            raise ValueError(msg)143614371438class LLM(BaseLLM):1439    """Simple interface for implementing a custom LLM.14401441    You should subclass this class and implement the following:14421443    - `_call` method: Run the LLM on the given prompt and input (used by `invoke`).1444    - `_identifying_params` property: Return a dictionary of the identifying parameters1445        This is critical for caching and tracing purposes. Identifying parameters1446        is a dict that identifies the LLM.1447        It should mostly include a `model_name`.14481449    Optional: Override the following methods to provide more optimizations:14501451    - `_acall`: Provide a native async version of the `_call` method.1452        If not provided, will delegate to the synchronous version using1453        `run_in_executor`. (Used by `ainvoke`).1454    - `_stream`: Stream the LLM on the given prompt and input.1455        `stream` will use `_stream` if provided, otherwise it1456        use `_call` and output will arrive in one chunk.1457    - `_astream`: Override to provide a native async version of the `_stream` method.1458        `astream` will use `_astream` if provided, otherwise it will implement1459        a fallback behavior that will use `_stream` if `_stream` is implemented,1460        and use `_acall` if `_stream` is not implemented.1461    """14621463    @abstractmethod1464    def _call(1465        self,1466        prompt: str,1467        stop: list[str] | None = None,1468        run_manager: CallbackManagerForLLMRun | None = None,1469        **kwargs: Any,1470    ) -> str:1471        """Run the LLM on the given input.14721473        Override this method to implement the LLM logic.14741475        Args:1476            prompt: The prompt to generate from.1477            stop: Stop words to use when generating.14781479                Model output is cut off at the first occurrence of any of these1480                substrings.14811482                If stop tokens are not supported consider raising `NotImplementedError`.1483            run_manager: Callback manager for the run.1484            **kwargs: Arbitrary additional keyword arguments.14851486                These are usually passed to the model provider API call.14871488        Returns:1489            The model output as a string. SHOULD NOT include the prompt.1490        """14911492    async def _acall(1493        self,1494        prompt: str,1495        stop: list[str] | None = None,1496        run_manager: AsyncCallbackManagerForLLMRun | None = None,1497        **kwargs: Any,1498    ) -> str:1499        """Async version of the _call method.15001501        The default implementation delegates to the synchronous _call method using1502        `run_in_executor`. Subclasses that need to provide a true async implementation1503        should override this method to reduce the overhead of using `run_in_executor`.15041505        Args:1506            prompt: The prompt to generate from.1507            stop: Stop words to use when generating.15081509                Model output is cut off at the first occurrence of any of these1510                substrings.15111512                If stop tokens are not supported consider raising `NotImplementedError`.1513            run_manager: Callback manager for the run.1514            **kwargs: Arbitrary additional keyword arguments.15151516                These are usually passed to the model provider API call.15171518        Returns:1519            The model output as a string. SHOULD NOT include the prompt.1520        """1521        return await run_in_executor(1522            None,1523            self._call,1524            prompt,1525            stop,1526            run_manager.get_sync() if run_manager else None,1527            **kwargs,1528        )15291530    def _generate(1531        self,1532        prompts: list[str],1533        stop: list[str] | None = None,1534        run_manager: CallbackManagerForLLMRun | None = None,1535        **kwargs: Any,1536    ) -> LLMResult:1537        # TODO: add caching here.1538        generations = []1539        new_arg_supported = _signature_parameters(self._call).get("run_manager")1540        for prompt in prompts:1541            text = (1542                self._call(prompt, stop=stop, run_manager=run_manager, **kwargs)1543                if new_arg_supported1544                else self._call(prompt, stop=stop, **kwargs)1545            )1546            generations.append([Generation(text=text)])1547        return LLMResult(generations=generations)15481549    async def _agenerate(1550        self,1551        prompts: list[str],1552        stop: list[str] | None = None,1553        run_manager: AsyncCallbackManagerForLLMRun | None = None,1554        **kwargs: Any,1555    ) -> LLMResult:1556        generations = []1557        new_arg_supported = _signature_parameters(self._acall).get("run_manager")1558        for prompt in prompts:1559            text = (1560                await self._acall(prompt, stop=stop, run_manager=run_manager, **kwargs)1561                if new_arg_supported1562                else await self._acall(prompt, stop=stop, **kwargs)1563            )1564            generations.append([Generation(text=text)])1565        return LLMResult(generations=generations)

Code quality findings 8

Ensure functions have docstrings for documentation
info maintainability missing-docstring
def invoke(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def ainvoke(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def batch(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def abatch(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def stream(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def astream(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def generate_prompt(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def agenerate_prompt(

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings