Tony20100/langchain_full huggingface dataset:
ends: Optional[dict[str, str]]
[docs]class CurveStyle(Enum):
    """Enum for different curve styles supported by Mermaid"""
    BASIS = "basis"
    BUMP_X = "bumpX"
    BUMP_Y = "bumpY"
    CARDINAL = "cardinal"
    CATMULL_ROM = "catmullRom"
    LINEAR = "linear"
    MONOTONE_X = "monotoneX"
    MONOTONE_Y = "monotoneY"
    NATURAL = "natural"
    STEP = "step"
    STEP_AFTER = "stepAfter"
    STEP_BEFORE = "stepBefore"
[docs]@dataclass
class NodeColors:
    """Schema for Hexadecimal color codes for different node types"""
    start: str = "#ffdfba"
    end: str = "#baffc9"
    other: str = "#fad7de"
[docs]class MermaidDrawMethod(Enum):
    """Enum for different draw methods supported by Mermaid"""
    PYPPETEER = "pyppeteer"  # Uses Pyppeteer to render the graph
    API = "api"  # Uses Mermaid.INK API to render the graph
[docs]def node_data_str(node: Node) -> str:
    """Convert the data of a node to a string.
    Args:
        node: The node to convert.
    Returns:
        A string representation of the data.
    """
    from langchain_core.runnables.base import Runnable
    if not is_uuid(node.id):
        return node.id
    elif isinstance(node.data, Runnable):
        try:
            data = str(node.data)
            if (
                data.startswith("<")
data = str(node.data)
            if (
                data.startswith("<")
                or data[0] != data[0].upper()
                or len(data.splitlines()) > 1
            ):
                data = node.data.__class__.__name__
            elif len(data) > 42:
                data = data[:42] + "..."
        except Exception:
            data = node.data.__class__.__name__
    else:
        data = node.data.__name__
    return data if not data.startswith("Runnable") else data[8:]
[docs]def node_data_json(
    node: Node, *, with_schemas: bool = False
) -> Dict[str, Union[str, Dict[str, Any]]]:
    """Convert the data of a node to a JSON-serializable format.
    Args:
        node: The node to convert.
        with_schemas: Whether to include the schema of the data if
            it is a Pydantic model.
    Returns:
        A dictionary with the type of the data and the data itself.
    """
    from langchain_core.load.serializable import to_json_not_implemented
    from langchain_core.runnables.base import Runnable, RunnableSerializable
    if isinstance(node.data, RunnableSerializable):
        return {
            "type": "runnable",
            "data": {
                "id": node.data.lc_id(),
                "name": node.data.get_name(),
            },
        }
    elif isinstance(node.data, Runnable):
        return {
            "type": "runnable",
            "data": {
                "id": to_json_not_implemented(node.data)["id"],
                "name": node.data.get_name(),
            },
        }
    elif inspect.isclass(node.data) and issubclass(node.data, BaseModel):
}
    elif inspect.isclass(node.data) and issubclass(node.data, BaseModel):
        return (
            {
                "type": "schema",
                "data": node.data.schema(),
            }
            if with_schemas
            else {
                "type": "schema",
                "data": node_data_str(node),
            }
        )
    else:
        return {
            "type": "unknown",
            "data": node_data_str(node),
        }
[docs]@dataclass
class Graph:
    """Graph of nodes and edges."""
    nodes: Dict[str, Node] = field(default_factory=dict)
    edges: List[Edge] = field(default_factory=list)
[docs]    def to_json(self, *, with_schemas: bool = False) -> Dict[str, List[Dict[str, Any]]]:
        """Convert the graph to a JSON-serializable format."""
        stable_node_ids = {
            node.id: i if is_uuid(node.id) else node.id
            for i, node in enumerate(self.nodes.values())
        }
        edges: List[Dict[str, Any]] = []
        for edge in self.edges:
            edge_dict = {
                "source": stable_node_ids[edge.source],
                "target": stable_node_ids[edge.target],
            }
            if edge.data is not None:
                edge_dict["data"] = edge.data
            if edge.conditional:
                edge_dict["conditional"] = True
            edges.append(edge_dict)
        return {
            "nodes": [
                {
                    "id": stable_node_ids[node.id],
                    **node_data_json(node, with_schemas=with_schemas),
                }
                for node in self.nodes.values()
            ],
            "edges": edges,
        }
],
            "edges": edges,
        }
    def __bool__(self) -> bool:
        return bool(self.nodes)
[docs]    def next_id(self) -> str:
        return uuid4().hex
[docs]    def add_node(
        self, data: Union[Type[BaseModel], RunnableType], id: Optional[str] = None
    ) -> Node:
        """Add a node to the graph and return it."""
        if id is not None and id in self.nodes:
            raise ValueError(f"Node with id {id} already exists")
        node = Node(id=id or self.next_id(), data=data)
        self.nodes[node.id] = node
        return node
[docs]    def remove_node(self, node: Node) -> None:
        """Remove a node from the graphm and all edges connected to it."""
        self.nodes.pop(node.id)
        self.edges = [
            edge
            for edge in self.edges
            if edge.source != node.id and edge.target != node.id
        ]
[docs]    def add_edge(
        self,
        source: Node,
        target: Node,
        data: Optional[str] = None,
        conditional: bool = False,
    ) -> Edge:
        """Add an edge to the graph and return it."""
        if source.id not in self.nodes:
            raise ValueError(f"Source node {source.id} not in graph")
        if target.id not in self.nodes:
            raise ValueError(f"Target node {target.id} not in graph")
        edge = Edge(
            source=source.id, target=target.id, data=data, conditional=conditional
        )
        self.edges.append(edge)
        return edge
[docs]    def extend(
self.edges.append(edge)
        return edge
[docs]    def extend(
        self, graph: Graph, *, prefix: str = ""
    ) -> Tuple[Optional[Node], Optional[Node]]:
        """Add all nodes and edges from another graph.
        Note this doesn't check for duplicates, nor does it connect the graphs."""
        if all(is_uuid(node.id) for node in graph.nodes.values()):
            prefix = ""
        def prefixed(id: str) -> str:
            return f"{prefix}:{id}" if prefix else id
        # prefix each node
        self.nodes.update(
            {prefixed(k): Node(prefixed(k), v.data) for k, v in graph.nodes.items()}
        )
        # prefix each edge's source and target
        self.edges.extend(
            [
                Edge(
                    prefixed(edge.source),
                    prefixed(edge.target),
                    edge.data,
                    edge.conditional,
                )
                for edge in graph.edges
            ]
        )
        # return (prefixed) first and last nodes of the subgraph
        first, last = graph.first_node(), graph.last_node()
        return (
            Node(prefixed(first.id), first.data) if first else None,
            Node(prefixed(last.id), last.data) if last else None,
        )
[docs]    def first_node(self) -> Optional[Node]:
        """Find the single node that is not a target of any edge.
        If there is no such node, or there are multiple, return None.
        When drawing the graph this node would be the origin."""
        targets = {edge.target for edge in self.edges}
        found: List[Node] = []
        for node in self.nodes.values():
            if node.id not in targets:
                found.append(node)
if node.id not in targets:
                found.append(node)
        return found[0] if len(found) == 1 else None
[docs]    def last_node(self) -> Optional[Node]:
        """Find the single node that is not a source of any edge.
        If there is no such node, or there are multiple, return None.
        When drawing the graph this node would be the destination.
        """
        sources = {edge.source for edge in self.edges}
        found: List[Node] = []
        for node in self.nodes.values():
            if node.id not in sources:
                found.append(node)
        return found[0] if len(found) == 1 else None
[docs]    def trim_first_node(self) -> None:
        """Remove the first node if it exists and has a single outgoing edge,
        ie. if removing it would not leave the graph without a "first" node."""
        first_node = self.first_node()
        if first_node:
            if (
                len(self.nodes) == 1
                or len([edge for edge in self.edges if edge.source == first_node.id])
                == 1
            ):
                self.remove_node(first_node)
[docs]    def trim_last_node(self) -> None:
        """Remove the last node if it exists and has a single incoming edge,
        ie. if removing it would not leave the graph without a "last" node."""
        last_node = self.last_node()
        if last_node:
            if (
                len(self.nodes) == 1
                or len([edge for edge in self.edges if edge.target == last_node.id])
                == 1
            ):
                self.remove_node(last_node)
[docs]    def draw_ascii(self) -> str:
[docs]    def draw_ascii(self) -> str:
        from langchain_core.runnables.graph_ascii import draw_ascii
        return draw_ascii(
            {node.id: node_data_str(node) for node in self.nodes.values()},
            self.edges,
        )
[docs]    def print_ascii(self) -> None:
        print(self.draw_ascii())  # noqa: T201
    @overload
    def draw_png(
        self,
        output_file_path: str,
        fontname: Optional[str] = None,
        labels: Optional[LabelsDict] = None,
    ) -> None:
        ...
    @overload
    def draw_png(
        self,
        output_file_path: None,
        fontname: Optional[str] = None,
        labels: Optional[LabelsDict] = None,
    ) -> bytes:
        ...
[docs]    def draw_png(
        self,
        output_file_path: Optional[str] = None,
        fontname: Optional[str] = None,
        labels: Optional[LabelsDict] = None,
    ) -> Union[bytes, None]:
        from langchain_core.runnables.graph_png import PngDrawer
        default_node_labels = {
            node.id: node_data_str(node) for node in self.nodes.values()
        }
        return PngDrawer(
            fontname,
            LabelsDict(
                nodes={
                    **default_node_labels,
                    **(labels["nodes"] if labels is not None else {}),
                },
                edges=labels["edges"] if labels is not None else {},
            ),
        ).draw(self, output_file_path)
[docs]    def draw_mermaid(
        self,
        *,
        with_styles: bool = True,
self,
        *,
        with_styles: bool = True,
        curve_style: CurveStyle = CurveStyle.LINEAR,
        node_colors: NodeColors = NodeColors(
            start="#ffdfba", end="#baffc9", other="#fad7de"
        ),
        wrap_label_n_words: int = 9,
    ) -> str:
        from langchain_core.runnables.graph_mermaid import draw_mermaid
        nodes = {node.id: node_data_str(node) for node in self.nodes.values()}
        first_node = self.first_node()
        first_label = node_data_str(first_node) if first_node is not None else None
        last_node = self.last_node()
        last_label = node_data_str(last_node) if last_node is not None else None
        return draw_mermaid(
            nodes=nodes,
            edges=self.edges,
            first_node_label=first_label,
            last_node_label=last_label,
            with_styles=with_styles,
            curve_style=curve_style,
            node_colors=node_colors,
            wrap_label_n_words=wrap_label_n_words,
        )
[docs]    def draw_mermaid_png(
        self,
        *,
        curve_style: CurveStyle = CurveStyle.LINEAR,
        node_colors: NodeColors = NodeColors(
            start="#ffdfba", end="#baffc9", other="#fad7de"
        ),
        wrap_label_n_words: int = 9,
        output_file_path: Optional[str] = None,
        draw_method: MermaidDrawMethod = MermaidDrawMethod.API,
        background_color: str = "white",
        padding: int = 10,
    ) -> bytes:
padding: int = 10,
    ) -> bytes:
        from langchain_core.runnables.graph_mermaid import draw_mermaid_png
        mermaid_syntax = self.draw_mermaid(
            curve_style=curve_style,
            node_colors=node_colors,
            wrap_label_n_words=wrap_label_n_words,
        )
        return draw_mermaid_png(
            mermaid_syntax=mermaid_syntax,
            output_file_path=output_file_path,
            draw_method=draw_method,
            background_color=background_color,
            padding=padding,
        )
Source code for langchain_core.runnables.configurable
from __future__ import annotations
import enum
import threading
from abc import abstractmethod
from functools import wraps
from typing import (
    Any,
    AsyncIterator,
    Callable,
    Dict,
    Iterator,
    List,
    Optional,
    Sequence,
    Tuple,
    Type,
    Union,
    cast,
)
from weakref import WeakValueDictionary
from langchain_core.pydantic_v1 import BaseModel
from langchain_core.runnables.base import Runnable, RunnableSerializable
from langchain_core.runnables.config import (
    RunnableConfig,
    ensure_config,
    get_config_list,
    get_executor_for_config,
    merge_configs,
)
from langchain_core.runnables.graph import Graph
from langchain_core.runnables.utils import (
    AnyConfigurableField,
    ConfigurableField,
    ConfigurableFieldMultiOption,
    ConfigurableFieldSingleOption,
    ConfigurableFieldSpec,
    Input,
    Output,
    gather_with_concurrency,
    get_unique_config_specs,
)
[docs]class DynamicRunnable(RunnableSerializable[Input, Output]):
    """Serializable Runnable that can be dynamically configured."""
    default: RunnableSerializable[Input, Output]
    config: Optional[RunnableConfig] = None
    class Config:
        arbitrary_types_allowed = True
[docs]    @classmethod
    def is_lc_serializable(cls) -> bool:
        return True
[docs]    @classmethod
    def get_lc_namespace(cls) -> List[str]:
        """Get the namespace of the langchain object."""
        return ["langchain", "schema", "runnable"]
    @property
    def InputType(self) -> Type[Input]:
        return self.default.InputType
def InputType(self) -> Type[Input]:
        return self.default.InputType
    @property
    def OutputType(self) -> Type[Output]:
        return self.default.OutputType
[docs]    def get_input_schema(
        self, config: Optional[RunnableConfig] = None
    ) -> Type[BaseModel]:
        runnable, config = self.prepare(config)
        return runnable.get_input_schema(config)
[docs]    def get_output_schema(
        self, config: Optional[RunnableConfig] = None
    ) -> Type[BaseModel]:
        runnable, config = self.prepare(config)
        return runnable.get_output_schema(config)
[docs]    def get_graph(self, config: Optional[RunnableConfig] = None) -> Graph:
        runnable, config = self.prepare(config)
        return runnable.get_graph(config)
[docs]    def with_config(
        self,
        config: Optional[RunnableConfig] = None,
        # Sadly Unpack is not well supported by mypy so this will have to be untyped
        **kwargs: Any,
    ) -> Runnable[Input, Output]:
        return self.__class__(
            **{**self.__dict__, "config": ensure_config(merge_configs(config, kwargs))}  # type: ignore[arg-type]
        )
[docs]    def prepare(
        self, config: Optional[RunnableConfig] = None
    ) -> Tuple[Runnable[Input, Output], RunnableConfig]:
        runnable: Runnable[Input, Output] = self
        while isinstance(runnable, DynamicRunnable):
            runnable, config = runnable._prepare(merge_configs(runnable.config, config))
        return runnable, cast(RunnableConfig, config)
    @abstractmethod
    def _prepare(
        self, config: Optional[RunnableConfig] = None
def _prepare(
        self, config: Optional[RunnableConfig] = None
    ) -> Tuple[Runnable[Input, Output], RunnableConfig]:
        ...
[docs]    def invoke(
        self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any
    ) -> Output:
        runnable, config = self.prepare(config)
        return runnable.invoke(input, config, **kwargs)
[docs]    async def ainvoke(
        self, input: Input, config: Optional[RunnableConfig] = None, **kwargs: Any
    ) -> Output:
        runnable, config = self.prepare(config)
        return await runnable.ainvoke(input, config, **kwargs)
[docs]    def batch(
        self,
        inputs: List[Input],
        config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Optional[Any],
    ) -> List[Output]:
        configs = get_config_list(config, len(inputs))
        prepared = [self.prepare(c) for c in configs]
        if all(p is self.default for p, _ in prepared):
            return self.default.batch(
                inputs,
                [c for _, c in prepared],
                return_exceptions=return_exceptions,
                **kwargs,
            )
        if not inputs:
            return []
        def invoke(
            prepared: Tuple[Runnable[Input, Output], RunnableConfig],
            input: Input,
        ) -> Union[Output, Exception]:
            bound, config = prepared
            if return_exceptions:
                try:
                    return bound.invoke(input, config, **kwargs)
                except Exception as e:
                    return e
            else:
except Exception as e:
                    return e
            else:
                return bound.invoke(input, config, **kwargs)
        # If there's only one input, don't bother with the executor
        if len(inputs) == 1:
            return cast(List[Output], [invoke(prepared[0], inputs[0])])
        with get_executor_for_config(configs[0]) as executor:
            return cast(List[Output], list(executor.map(invoke, prepared, inputs)))
[docs]    async def abatch(
        self,
        inputs: List[Input],
        config: Optional[Union[RunnableConfig, List[RunnableConfig]]] = None,
        *,
        return_exceptions: bool = False,
        **kwargs: Optional[Any],
    ) -> List[Output]:
        configs = get_config_list(config, len(inputs))
        prepared = [self.prepare(c) for c in configs]
        if all(p is self.default for p, _ in prepared):
            return await self.default.abatch(
                inputs,
                [c for _, c in prepared],
                return_exceptions=return_exceptions,
                **kwargs,
            )
        if not inputs:
            return []
        async def ainvoke(
            prepared: Tuple[Runnable[Input, Output], RunnableConfig],
            input: Input,
        ) -> Union[Output, Exception]:
            bound, config = prepared
            if return_exceptions:
                try:
                    return await bound.ainvoke(input, config, **kwargs)
                except Exception as e:
                    return e
            else:
                return await bound.ainvoke(input, config, **kwargs)
        coros = map(ainvoke, prepared, inputs)
coros = map(ainvoke, prepared, inputs)
        return await gather_with_concurrency(configs[0].get("max_concurrency"), *coros)
[docs]    def stream(
        self,
        input: Input,
        config: Optional[RunnableConfig] = None,
        **kwargs: Optional[Any],
    ) -> Iterator[Output]:
        runnable, config = self.prepare(config)
        return runnable.stream(input, config, **kwargs)
[docs]    async def astream(
        self,
        input: Input,
        config: Optional[RunnableConfig] = None,
        **kwargs: Optional[Any],
    ) -> AsyncIterator[Output]:
        runnable, config = self.prepare(config)
        async for chunk in runnable.astream(input, config, **kwargs):
            yield chunk
[docs]    def transform(
        self,
        input: Iterator[Input],
        config: Optional[RunnableConfig] = None,
        **kwargs: Optional[Any],
    ) -> Iterator[Output]:
        runnable, config = self.prepare(config)
        return runnable.transform(input, config, **kwargs)
[docs]    async def atransform(
        self,
        input: AsyncIterator[Input],
        config: Optional[RunnableConfig] = None,
        **kwargs: Optional[Any],
    ) -> AsyncIterator[Output]:
        runnable, config = self.prepare(config)
        async for chunk in runnable.atransform(input, config, **kwargs):
            yield chunk
    def __getattr__(self, name: str) -> Any:
        attr = getattr(self.default, name)
        if callable(attr):
            @wraps(attr)
            def wrapper(*args: Any, **kwargs: Any) -> Any:
def wrapper(*args: Any, **kwargs: Any) -> Any:
                for key, arg in kwargs.items():
                    if key == "config" and (
                        isinstance(arg, dict)
                        and "configurable" in arg
                        and isinstance(arg["configurable"], dict)
                    ):
                        runnable, config = self.prepare(cast(RunnableConfig, arg))
                        kwargs = {**kwargs, "config": config}
                        return getattr(runnable, name)(*args, **kwargs)
                for idx, arg in enumerate(args):
                    if (
                        isinstance(arg, dict)
                        and "configurable" in arg
                        and isinstance(arg["configurable"], dict)
                    ):
                        runnable, config = self.prepare(cast(RunnableConfig, arg))
                        argsl = list(args)
                        argsl[idx] = config
                        return getattr(runnable, name)(*argsl, **kwargs)
                if self.config:
                    runnable, config = self.prepare()
                    return getattr(runnable, name)(*args, **kwargs)
                return attr(*args, **kwargs)
            return wrapper
        else:
            return attr
[docs]class RunnableConfigurableFields(DynamicRunnable[Input, Output]):
    """Runnable that can be dynamically configured.
    A RunnableConfigurableFields should be initiated using the
    `configurable_fields` method of a Runnable.
    Here is an example of using a RunnableConfigurableFields with LLMs:
        .. code-block:: python
            from langchain_core.prompts import PromptTemplate
            from langchain_core.runnables import ConfigurableField
            from langchain_openai import ChatOpenAI
            model = ChatOpenAI(temperature=0).configurable_fields(
                temperature=ConfigurableField(
                    id="temperature",