mirror of
https://github.com/hwchase17/langchain.git
synced 2025-06-22 14:49:29 +00:00
core[patch]: Fix handling of title
when tool schema is specified manually via JSONSchema (#30479)
Fix issue: https://github.com/langchain-ai/langchain/issues/30456
This commit is contained in:
parent
c5e42a4027
commit
0acca6b9c8
@ -62,17 +62,36 @@ class ToolDescription(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
def _rm_titles(kv: dict, prev_key: str = "") -> dict:
|
def _rm_titles(kv: dict, prev_key: str = "") -> dict:
|
||||||
|
"""Recursively removes "title" fields from a JSON schema dictionary.
|
||||||
|
|
||||||
|
Remove "title" fields from the input JSON schema dictionary,
|
||||||
|
except when a "title" appears within a property definition under "properties".
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kv (dict): The input JSON schema as a dictionary.
|
||||||
|
prev_key (str): The key from the parent dictionary, used to identify context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A new dictionary with appropriate "title" fields removed.
|
||||||
|
"""
|
||||||
new_kv = {}
|
new_kv = {}
|
||||||
|
|
||||||
for k, v in kv.items():
|
for k, v in kv.items():
|
||||||
if k == "title":
|
if k == "title":
|
||||||
if isinstance(v, dict) and prev_key == "properties" and "title" in v:
|
# If the value is a nested dict and part of a property under "properties",
|
||||||
|
# preserve the title but continue recursion
|
||||||
|
if isinstance(v, dict) and prev_key == "properties":
|
||||||
new_kv[k] = _rm_titles(v, k)
|
new_kv[k] = _rm_titles(v, k)
|
||||||
else:
|
else:
|
||||||
|
# Otherwise, remove this "title" key
|
||||||
continue
|
continue
|
||||||
elif isinstance(v, dict):
|
elif isinstance(v, dict):
|
||||||
|
# Recurse into nested dictionaries
|
||||||
new_kv[k] = _rm_titles(v, k)
|
new_kv[k] = _rm_titles(v, k)
|
||||||
else:
|
else:
|
||||||
|
# Leave non-dict values untouched
|
||||||
new_kv[k] = v
|
new_kv[k] = v
|
||||||
|
|
||||||
return new_kv
|
return new_kv
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +60,10 @@ from langchain_core.tools.base import (
|
|||||||
_is_message_content_type,
|
_is_message_content_type,
|
||||||
get_all_basemodel_annotations,
|
get_all_basemodel_annotations,
|
||||||
)
|
)
|
||||||
from langchain_core.utils.function_calling import convert_to_openai_function
|
from langchain_core.utils.function_calling import (
|
||||||
|
convert_to_openai_function,
|
||||||
|
convert_to_openai_tool,
|
||||||
|
)
|
||||||
from langchain_core.utils.pydantic import (
|
from langchain_core.utils.pydantic import (
|
||||||
PYDANTIC_MAJOR_VERSION,
|
PYDANTIC_MAJOR_VERSION,
|
||||||
_create_subset_model,
|
_create_subset_model,
|
||||||
@ -2560,3 +2563,44 @@ def test_tool_decorator_description() -> None:
|
|||||||
]
|
]
|
||||||
== "description"
|
== "description"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_title_property_preserved() -> None:
|
||||||
|
"""Test that the title property is preserved when generating schema.
|
||||||
|
|
||||||
|
https://github.com/langchain-ai/langchain/issues/30456
|
||||||
|
"""
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
|
||||||
|
schema_to_be_extracted = {
|
||||||
|
"type": "object",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"title": {"type": "string", "description": "item title"},
|
||||||
|
"due_date": {"type": "string", "description": "item due date"},
|
||||||
|
},
|
||||||
|
"description": "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
@tool(args_schema=schema_to_be_extracted)
|
||||||
|
def extract_data(extracted_data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Some documentation."""
|
||||||
|
return extracted_data
|
||||||
|
|
||||||
|
assert convert_to_openai_tool(extract_data) == {
|
||||||
|
"function": {
|
||||||
|
"description": "Some documentation.",
|
||||||
|
"name": "extract_data",
|
||||||
|
"parameters": {
|
||||||
|
"properties": {
|
||||||
|
"due_date": {"description": "item due date", "type": "string"},
|
||||||
|
"title": {"description": "item title", "type": "string"},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "function",
|
||||||
|
}
|
||||||
|
@ -190,10 +190,44 @@ schema4 = {
|
|||||||
"required": ["properties"],
|
"required": ["properties"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
schema5 = {
|
||||||
|
"description": "A list of data.",
|
||||||
|
"items": {
|
||||||
|
"description": "foo",
|
||||||
|
"properties": {
|
||||||
|
"title": {"type": "string", "description": "item title"},
|
||||||
|
"due_date": {"type": "string", "description": "item due date"},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
}
|
||||||
|
|
||||||
|
output5 = {
|
||||||
|
"description": "A list of data.",
|
||||||
|
"items": {
|
||||||
|
"description": "foo",
|
||||||
|
"properties": {
|
||||||
|
"title": {"type": "string", "description": "item title"},
|
||||||
|
"due_date": {"type": "string", "description": "item due date"},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"schema, output",
|
"schema, output",
|
||||||
[(schema1, output1), (schema2, output2), (schema3, output3), (schema4, output4)],
|
[
|
||||||
|
(schema1, output1),
|
||||||
|
(schema2, output2),
|
||||||
|
(schema3, output3),
|
||||||
|
(schema4, output4),
|
||||||
|
(schema5, output5),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def test_rm_titles(schema: dict, output: dict) -> None:
|
def test_rm_titles(schema: dict, output: dict) -> None:
|
||||||
assert _rm_titles(schema) == output
|
assert _rm_titles(schema) == output
|
||||||
|
Loading…
Reference in New Issue
Block a user