langchain/libs/community/langchain_community/tools/json/tool.py
Erick Friis c2a3021bb0
multiple: pydantic 2 compatibility, v0.3 (#26443)
Signed-off-by: ChengZi <chen.zhang@zilliz.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com>
Co-authored-by: Dan O'Donovan <dan.odonovan@gmail.com>
Co-authored-by: Tom Daniel Grande <tomdgrande@gmail.com>
Co-authored-by: Grande <Tom.Daniel.Grande@statsbygg.no>
Co-authored-by: Bagatur <baskaryan@gmail.com>
Co-authored-by: ccurme <chester.curme@gmail.com>
Co-authored-by: Harrison Chase <hw.chase.17@gmail.com>
Co-authored-by: Tomaz Bratanic <bratanic.tomaz@gmail.com>
Co-authored-by: ZhangShenao <15201440436@163.com>
Co-authored-by: Friso H. Kingma <fhkingma@gmail.com>
Co-authored-by: ChengZi <chen.zhang@zilliz.com>
Co-authored-by: Nuno Campos <nuno@langchain.dev>
Co-authored-by: Morgante Pell <morgantep@google.com>
2024-09-13 14:38:45 -07:00

135 lines
4.0 KiB
Python

# flake8: noqa
"""Tools for working with JSON specs."""
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Dict, List, Optional, Union
from pydantic import BaseModel
from langchain_core.callbacks import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain_core.tools import BaseTool
def _parse_input(text: str) -> List[Union[str, int]]:
"""Parse input of the form data["key1"][0]["key2"] into a list of keys."""
_res = re.findall(r"\[.*?]", text)
# strip the brackets and quotes, convert to int if possible
res = [i[1:-1].replace('"', "").replace("'", "") for i in _res]
res = [int(i) if i.isdigit() else i for i in res]
return res
class JsonSpec(BaseModel):
"""Base class for JSON spec."""
dict_: Dict
max_value_length: int = 200
@classmethod
def from_file(cls, path: Path) -> JsonSpec:
"""Create a JsonSpec from a file."""
if not path.exists():
raise FileNotFoundError(f"File not found: {path}")
dict_ = json.loads(path.read_text())
return cls(dict_=dict_)
def keys(self, text: str) -> str:
"""Return the keys of the dict at the given path.
Args:
text: Python representation of the path to the dict (e.g. data["key1"][0]["key2"]).
"""
try:
items = _parse_input(text)
val = self.dict_
for i in items:
if i:
val = val[i]
if not isinstance(val, dict):
raise ValueError(
f"Value at path `{text}` is not a dict, get the value directly."
)
return str(list(val.keys()))
except Exception as e:
return repr(e)
def value(self, text: str) -> str:
"""Return the value of the dict at the given path.
Args:
text: Python representation of the path to the dict (e.g. data["key1"][0]["key2"]).
"""
try:
items = _parse_input(text)
val = self.dict_
for i in items:
val = val[i]
if isinstance(val, dict) and len(str(val)) > self.max_value_length:
return "Value is a large dictionary, should explore its keys directly"
str_val = str(val)
if len(str_val) > self.max_value_length:
str_val = str_val[: self.max_value_length] + "..."
return str_val
except Exception as e:
return repr(e)
class JsonListKeysTool(BaseTool):
"""Tool for listing keys in a JSON spec."""
name: str = "json_spec_list_keys"
description: str = """
Can be used to list all keys at a given path.
Before calling this you should be SURE that the path to this exists.
The input is a text representation of the path to the dict in Python syntax (e.g. data["key1"][0]["key2"]).
"""
spec: JsonSpec
def _run(
self,
tool_input: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
return self.spec.keys(tool_input)
async def _arun(
self,
tool_input: str,
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> str:
return self._run(tool_input)
class JsonGetValueTool(BaseTool):
"""Tool for getting a value in a JSON spec."""
name: str = "json_spec_get_value"
description: str = """
Can be used to see value in string format at a given path.
Before calling this you should be SURE that the path to this exists.
The input is a text representation of the path to the dict in Python syntax (e.g. data["key1"][0]["key2"]).
"""
spec: JsonSpec
def _run(
self,
tool_input: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
return self.spec.value(tool_input)
async def _arun(
self,
tool_input: str,
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> str:
return self._run(tool_input)