jfeng1115's picture
init commit
58d33f0
# 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, Union
from pydantic import BaseModel
from langchain.tools.base 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('"', "") 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 = "json_spec_list_keys"
description = """
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) -> str:
return self.spec.keys(tool_input)
async def _arun(self, tool_input: str) -> str:
return self._run(tool_input)
class JsonGetValueTool(BaseTool):
"""Tool for getting a value in a JSON spec."""
name = "json_spec_get_value"
description = """
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) -> str:
return self.spec.value(tool_input)
async def _arun(self, tool_input: str) -> str:
return self._run(tool_input)