mirror of
https://github.com/hwchase17/langchain.git
synced 2025-04-27 19:46:55 +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:
|
||||
"""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 = {}
|
||||
|
||||
for k, v in kv.items():
|
||||
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)
|
||||
else:
|
||||
# Otherwise, remove this "title" key
|
||||
continue
|
||||
elif isinstance(v, dict):
|
||||
# Recurse into nested dictionaries
|
||||
new_kv[k] = _rm_titles(v, k)
|
||||
else:
|
||||
# Leave non-dict values untouched
|
||||
new_kv[k] = v
|
||||
|
||||
return new_kv
|
||||
|
||||
|
||||
|
@ -60,7 +60,10 @@ from langchain_core.tools.base import (
|
||||
_is_message_content_type,
|
||||
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 (
|
||||
PYDANTIC_MAJOR_VERSION,
|
||||
_create_subset_model,
|
||||
@ -2560,3 +2563,44 @@ def test_tool_decorator_description() -> None:
|
||||
]
|
||||
== "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"],
|
||||
}
|
||||
|
||||
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(
|
||||
"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:
|
||||
assert _rm_titles(schema) == output
|
||||
|
Loading…
Reference in New Issue
Block a user