Compare commits

..

3 Commits

Author SHA1 Message Date
Eugene Yurtsev
b525226531 core[patch]: version 0.3.40 (#29997)
Version 0.3.40 release
2025-02-25 23:09:40 +00:00
Vadym Barda
0fc50b82a0 core[patch]: allow passing description to @tool decorator (#29976) 2025-02-25 17:45:36 -05:00
Naveen SK
21bfc95e14 docs: Correct grammatical typos in various documentation files (#29983)
**Description:**
Fixed grammatical typos in various documentation files

**Issue:**
N/A

**Dependencies:**
N/A

**Twitter handle:**
@MrNaveenSK

Co-authored-by: ccurme <chester.curme@gmail.com>
2025-02-25 19:13:31 +00:00
17 changed files with 149 additions and 42 deletions

View File

@@ -115,7 +115,7 @@
"\n",
"PROMPT_TEMPLATE = \"\"\"Given an input question, create a syntactically correct Elasticsearch query to run. Unless the user specifies in their question a specific number of examples they wish to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.\n",
"\n",
"Unless told to do not query for all the columns from a specific index, only ask for a the few relevant columns given the question.\n",
"Unless told to do not query for all the columns from a specific index, only ask for a few relevant columns given the question.\n",
"\n",
"Pay attention to use only the column names that you can see in the mapping description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which index. Return the query as valid json.\n",
"\n",

View File

@@ -233,7 +233,7 @@ Question: {input}"""
_DEFAULT_TEMPLATE = """Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. Unless the user specifies in his question a specific number of examples he wishes to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for a the few relevant columns given the question.
Never query for all the columns from a specific table, only ask for a few relevant columns given the question.
Pay attention to use only the column names that you can see in the schema description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.

View File

@@ -551,7 +551,7 @@
"\n",
"While a parser encapsulates the logic needed to parse binary data into documents, *blob loaders* encapsulate the logic that's necessary to load blobs from a given storage location.\n",
"\n",
"A the moment, `LangChain` only supports `FileSystemBlobLoader`.\n",
"At the moment, `LangChain` only supports `FileSystemBlobLoader`.\n",
"\n",
"You can use the `FileSystemBlobLoader` to load blobs and then use the parser to parse them."
]

View File

@@ -329,7 +329,7 @@
"id": "fc6059fd-0df7-4b6f-a84c-b5874e983638",
"metadata": {},
"source": [
"We can also pass in an arbitrary function or a runnable. This function/runnable should take in a the graph state and output a list of messages.\n",
"We can also pass in an arbitrary function or a runnable. This function/runnable should take in a graph state and output a list of messages.\n",
"We can do all types of arbitrary formatting of messages here. In this case, let's add a SystemMessage to the start of the list of messages and append another user message at the end."
]
},

View File

@@ -221,7 +221,7 @@
"source": [
"## JSONFormer LLM Wrapper\n",
"\n",
"Let's try that again, now providing a the Action input's JSON Schema to the model."
"Let's try that again, now providing the Action input's JSON Schema to the model."
]
},
{

View File

@@ -19,7 +19,7 @@
"\n",
"In many Q&A applications we want to allow the user to have a back-and-forth conversation, meaning the application needs some sort of \"memory\" of past questions and answers, and some logic for incorporating those into its current thinking.\n",
"\n",
"This is a the second part of a multi-part tutorial:\n",
"This is the second part of a multi-part tutorial:\n",
"\n",
"- [Part 1](/docs/tutorials/rag) introduces RAG and walks through a minimal implementation.\n",
"- [Part 2](/docs/tutorials/qa_chat_history) (this guide) extends the implementation to accommodate conversation-style interactions and multi-step retrieval processes.\n",

View File

@@ -459,6 +459,12 @@ class ChildTool(BaseTool):
@property
def tool_call_schema(self) -> ArgsSchema:
if isinstance(self.args_schema, dict):
if self.description:
return {
**self.args_schema,
"description": self.description,
}
return self.args_schema
full_schema = self.get_input_schema()

View File

@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field, create_model
from langchain_core.callbacks import Callbacks
from langchain_core.runnables import Runnable
from langchain_core.tools.base import BaseTool
from langchain_core.tools.base import ArgsSchema, BaseTool
from langchain_core.tools.simple import Tool
from langchain_core.tools.structured import StructuredTool
@@ -13,8 +13,9 @@ from langchain_core.tools.structured import StructuredTool
@overload
def tool(
*,
description: Optional[str] = None,
return_direct: bool = False,
args_schema: Optional[type] = None,
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,
@@ -27,8 +28,9 @@ def tool(
name_or_callable: str,
runnable: Runnable,
*,
description: Optional[str] = None,
return_direct: bool = False,
args_schema: Optional[type] = None,
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,
@@ -40,8 +42,9 @@ def tool(
def tool(
name_or_callable: Callable,
*,
description: Optional[str] = None,
return_direct: bool = False,
args_schema: Optional[type] = None,
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,
@@ -53,8 +56,9 @@ def tool(
def tool(
name_or_callable: str,
*,
description: Optional[str] = None,
return_direct: bool = False,
args_schema: Optional[type] = None,
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,
@@ -66,8 +70,9 @@ def tool(
name_or_callable: Optional[Union[str, Callable]] = None,
runnable: Optional[Runnable] = None,
*args: Any,
description: Optional[str] = None,
return_direct: bool = False,
args_schema: Optional[type] = None,
args_schema: Optional[ArgsSchema] = None,
infer_schema: bool = True,
response_format: Literal["content", "content_and_artifact"] = "content",
parse_docstring: bool = False,
@@ -83,6 +88,14 @@ def tool(
converted to a tool. Must be provided as a positional argument.
runnable: Optional runnable to convert to a tool. Must be provided as a
positional argument.
description: Optional description for the tool.
Precedence for the tool description value is as follows:
- `description` argument
(used even if docstring and/or `args_schema` are provided)
- tool function docstring
(used even if `args_schema` is provided)
- `args_schema` description
(used only if `description` / docstring are not provided)
return_direct: Whether to return directly from the tool rather
than continuing the agent loop. Defaults to False.
args_schema: optional argument schema for user to specify.
@@ -213,6 +226,7 @@ def tool(
"""
def _tool_factory(dec_func: Union[Callable, Runnable]) -> BaseTool:
tool_description = description
if isinstance(dec_func, Runnable):
runnable = dec_func
@@ -232,25 +246,23 @@ def tool(
coroutine = ainvoke_wrapper
func = invoke_wrapper
schema: Optional[type[BaseModel]] = runnable.input_schema
description = repr(runnable)
schema: Optional[ArgsSchema] = runnable.input_schema
tool_description = description or repr(runnable)
elif inspect.iscoroutinefunction(dec_func):
coroutine = dec_func
func = None
schema = args_schema
description = None
else:
coroutine = None
func = dec_func
schema = args_schema
description = None
if infer_schema or args_schema is not None:
return StructuredTool.from_function(
func,
coroutine,
name=tool_name,
description=description,
description=tool_description,
return_direct=return_direct,
args_schema=schema,
infer_schema=infer_schema,

View File

@@ -27,6 +27,7 @@ from langchain_core.tools.base import (
_get_runnable_config_param,
create_schema_from_function,
)
from langchain_core.utils.pydantic import is_basemodel_subclass
class StructuredTool(BaseTool):
@@ -188,7 +189,16 @@ class StructuredTool(BaseTool):
if description is None and not parse_docstring:
description_ = source_function.__doc__ or None
if description_ is None and args_schema:
description_ = args_schema.__doc__ or None
if isinstance(args_schema, type) and is_basemodel_subclass(args_schema):
description_ = args_schema.__doc__ or None
elif isinstance(args_schema, dict):
description_ = args_schema.get("description")
else:
msg = (
"Invalid args_schema: expected BaseModel or dict, "
f"got {args_schema}"
)
raise TypeError(msg)
if description_ is None:
msg = "Function must have a docstring if description not provided."
raise ValueError(msg)

View File

@@ -17,7 +17,7 @@ dependencies = [
"pydantic<3.0.0,>=2.7.4; python_full_version >= \"3.12.4\"",
]
name = "langchain-core"
version = "0.3.39"
version = "0.3.40"
description = "Building applications with LLMs through composability"
readme = "README.md"

View File

@@ -52,6 +52,7 @@ from langchain_core.tools import (
tool,
)
from langchain_core.tools.base import (
ArgsSchema,
InjectedToolArg,
InjectedToolCallId,
SchemaAnnotationError,
@@ -199,7 +200,7 @@ def test_decorator_with_specified_schema() -> None:
assert isinstance(tool_func, BaseTool)
assert tool_func.args_schema == _MockSchema
@tool(args_schema=_MockSchemaV1)
@tool(args_schema=cast(ArgsSchema, _MockSchemaV1))
def tool_func_v1(arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str:
return f"{arg1} {arg2} {arg3}"
@@ -2398,10 +2399,10 @@ def test_structured_tool_args_schema_dict() -> None:
"required": ["a", "b"],
"title": "add",
"type": "object",
"description": "add two numbers",
}
tool = StructuredTool(
name="add",
description="add two numbers",
args_schema=args_schema,
func=lambda a, b: a + b,
)
@@ -2433,6 +2434,7 @@ def test_simple_tool_args_schema_dict() -> None:
"required": ["a"],
"title": "square",
"type": "object",
"description": "square a number",
}
tool = Tool(
name="square",
@@ -2468,3 +2470,93 @@ def test_empty_string_tool_call_id() -> None:
assert foo.invoke({"type": "tool_call", "args": {"x": 0}, "id": ""}) == ToolMessage(
content="hi", name="foo", tool_call_id=""
)
def test_tool_decorator_description() -> None:
# test basic tool
@tool
def foo(x: int) -> str:
"""Foo."""
return "hi"
assert foo.description == "Foo."
assert (
cast(BaseModel, foo.tool_call_schema).model_json_schema()["description"]
== "Foo."
)
# test basic tool with description
@tool(description="description")
def foo_description(x: int) -> str:
"""Foo."""
return "hi"
assert foo_description.description == "description"
assert (
cast(BaseModel, foo_description.tool_call_schema).model_json_schema()[
"description"
]
== "description"
)
# test tool with args schema
class ArgsSchema(BaseModel):
"""Bar."""
x: int
@tool(args_schema=ArgsSchema)
def foo_args_schema(x: int) -> str:
return "hi"
assert foo_args_schema.description == "Bar."
assert (
cast(BaseModel, foo_args_schema.tool_call_schema).model_json_schema()[
"description"
]
== "Bar."
)
@tool(description="description", args_schema=ArgsSchema)
def foo_args_schema_description(x: int) -> str:
return "hi"
assert foo_args_schema_description.description == "description"
assert (
cast(
BaseModel, foo_args_schema_description.tool_call_schema
).model_json_schema()["description"]
== "description"
)
args_json_schema = {
"description": "JSON Schema.",
"properties": {
"x": {"description": "my field", "title": "X", "type": "string"}
},
"required": ["x"],
"title": "my_tool",
"type": "object",
}
@tool(args_schema=args_json_schema)
def foo_args_jsons_schema(x: int) -> str:
return "hi"
@tool(description="description", args_schema=args_json_schema)
def foo_args_jsons_schema_with_description(x: int) -> str:
return "hi"
assert foo_args_jsons_schema.description == "JSON Schema."
assert (
cast(dict, foo_args_jsons_schema.tool_call_schema)["description"]
== "JSON Schema."
)
assert foo_args_jsons_schema_with_description.description == "description"
assert (
cast(dict, foo_args_jsons_schema_with_description.tool_call_schema)[
"description"
]
== "description"
)

View File

@@ -9,7 +9,7 @@ ESQuery:"""
DEFAULT_DSL_TEMPLATE = """Given an input question, create a syntactically correct Elasticsearch query to run. Unless the user specifies in their question a specific number of examples they wish to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.
Unless told to do not query for all the columns from a specific index, only ask for a the few relevant columns given the question.
Unless told to do not query for all the columns from a specific index, only ask for a few relevant columns given the question.
Pay attention to use only the column names that you can see in the mapping description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which index. Return the query as valid json.

View File

@@ -5,12 +5,14 @@ import re
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
import requests
from langchain_core._api import deprecated
from langchain_core.callbacks import CallbackManagerForChainRun
from langchain_core.language_models import BaseLanguageModel
from langchain_core.output_parsers.openai_functions import JsonOutputFunctionsParser
from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate
from langchain_core.utils.input import get_colored_text
from requests import Response
from langchain.chains.base import Chain
from langchain.chains.llm import LLMChain
@@ -19,7 +21,6 @@ from langchain.chains.sequential import SequentialChain
if TYPE_CHECKING:
from langchain_community.utilities.openapi import OpenAPISpec
from openapi_pydantic import Parameter
from requests import Response
def _get_description(o: Any, prefer_short: bool) -> Optional[str]:
@@ -174,12 +175,6 @@ def openapi_spec_to_openai_fn(
params: Optional[dict] = None,
**kwargs: Any,
) -> Any:
try:
import requests
except ImportError as e:
raise ImportError(
"Could not import requests, please install with `pip install requests`."
) from e
method = _name_to_call_map[name]["method"]
url = _name_to_call_map[name]["url"]
path_params = fn_args.pop("path_params", {})

View File

@@ -10,7 +10,7 @@ Question: {input}"""
_DEFAULT_TEMPLATE = """Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. Unless the user specifies in his question a specific number of examples he wishes to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for a the few relevant columns given the question.
Never query for all the columns from a specific table, only ask for a few relevant columns given the question.
Pay attention to use only the column names that you can see in the schema description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.

View File

@@ -46,6 +46,7 @@ from langsmith.evaluation import (
from langsmith.run_helpers import as_runnable, is_traceable_function
from langsmith.schemas import Dataset, DataType, Example, Run, TracerSession
from langsmith.utils import LangSmithError
from requests import HTTPError
from typing_extensions import TypedDict
from langchain.chains.base import Chain
@@ -971,12 +972,6 @@ def _prepare_eval_run(
tags: Optional[List[str]] = None,
dataset_version: Optional[Union[str, datetime]] = None,
) -> Tuple[MCF, TracerSession, Dataset, List[Example]]:
try:
from requests import HTTPError
except ImportError as e:
raise ImportError(
"Could not import requests, please install with `pip install requests`."
) from e
wrapped_model = _wrap_in_chain_factory(llm_or_chain_factory, dataset_name)
dataset = client.read_dataset(dataset_name=dataset_name)

View File

@@ -12,6 +12,7 @@ dependencies = [
"langsmith<0.4,>=0.1.17",
"pydantic<3.0.0,>=2.7.4",
"SQLAlchemy<3,>=1.4",
"requests<3,>=2",
"PyYAML>=5.3",
"numpy<2,>=1.26.4; python_version < \"3.12\"",
"numpy<3,>=1.26.2; python_version >= \"3.12\"",
@@ -73,7 +74,6 @@ test = [
"langchain-openai",
"toml>=0.10.2",
"packaging>=24.2",
"requests<3,>=2",
]
codespell = ["codespell<3.0.0,>=2.2.0"]
test_integration = [
@@ -102,7 +102,6 @@ typing = [
"mypy-protobuf<4.0.0,>=3.0.0",
"langchain-core",
"langchain-text-splitters",
"requests<3,>=2",
]
dev = [
"jupyter<2.0.0,>=1.0.0",

View File

@@ -2251,6 +2251,7 @@ dependencies = [
{ name = "numpy", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
{ name = "pydantic" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "sqlalchemy" },
]
@@ -2338,7 +2339,6 @@ test = [
{ name = "pytest-socket" },
{ name = "pytest-watcher" },
{ name = "pytest-xdist" },
{ name = "requests" },
{ name = "requests-mock" },
{ name = "responses" },
{ name = "syrupy" },
@@ -2359,7 +2359,6 @@ typing = [
{ name = "langchain-text-splitters" },
{ name = "mypy" },
{ name = "mypy-protobuf" },
{ name = "requests" },
{ name = "types-chardet" },
{ name = "types-pytz" },
{ name = "types-pyyaml" },
@@ -2394,6 +2393,7 @@ requires-dist = [
{ name = "numpy", marker = "python_full_version >= '3.12'", specifier = ">=1.26.2,<3" },
{ name = "pydantic", specifier = ">=2.7.4,<3.0.0" },
{ name = "pyyaml", specifier = ">=5.3" },
{ name = "requests", specifier = ">=2,<3" },
{ name = "sqlalchemy", specifier = ">=1.4,<3" },
]
@@ -2432,7 +2432,6 @@ test = [
{ name = "pytest-socket", specifier = ">=0.6.0,<1.0.0" },
{ name = "pytest-watcher", specifier = ">=0.2.6,<1.0.0" },
{ name = "pytest-xdist", specifier = ">=3.6.1,<4.0.0" },
{ name = "requests", specifier = ">=2,<3" },
{ name = "requests-mock", specifier = ">=1.11.0,<2.0.0" },
{ name = "responses", specifier = ">=0.22.0,<1.0.0" },
{ name = "syrupy", specifier = ">=4.0.2,<5.0.0" },
@@ -2453,7 +2452,6 @@ typing = [
{ name = "langchain-text-splitters", editable = "../text-splitters" },
{ name = "mypy", specifier = ">=1.10,<2.0" },
{ name = "mypy-protobuf", specifier = ">=3.0.0,<4.0.0" },
{ name = "requests", specifier = ">=2,<3" },
{ name = "types-chardet", specifier = ">=5.0.4.6,<6.0.0.0" },
{ name = "types-pytz", specifier = ">=2023.3.0.0,<2024.0.0.0" },
{ name = "types-pyyaml", specifier = ">=6.0.12.2,<7.0.0.0" },