|
|
@ -7,9 +7,10 @@ from typing import Any, Callable, List, Optional
|
|
|
|
|
|
|
|
|
|
|
|
import jsonpatch
|
|
|
|
import jsonpatch
|
|
|
|
|
|
|
|
|
|
|
|
from langchain.schema import BaseOutputParser, OutputParserException
|
|
|
|
from langchain.schema.output_parser import (
|
|
|
|
from langchain.schema.output import ChatGeneration, Generation
|
|
|
|
BaseCumulativeTransformOutputParser,
|
|
|
|
from langchain.schema.output_parser import BaseCumulativeTransformOutputParser
|
|
|
|
OutputParserException,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _replace_new_line(match: re.Match[str]) -> str:
|
|
|
|
def _replace_new_line(match: re.Match[str]) -> str:
|
|
|
@ -44,10 +45,10 @@ def _custom_parser(multiline_string: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
# Adapted from https://github.com/KillianLucas/open-interpreter/blob/main/interpreter/utils/parse_partial_json.py
|
|
|
|
# Adapted from https://github.com/KillianLucas/open-interpreter/blob/main/interpreter/utils/parse_partial_json.py
|
|
|
|
# MIT License
|
|
|
|
# MIT License
|
|
|
|
def parse_partial_json(s: str) -> Any:
|
|
|
|
def parse_partial_json(s: str, *, strict: bool = False) -> Any:
|
|
|
|
# Attempt to parse the string as-is.
|
|
|
|
# Attempt to parse the string as-is.
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return json.loads(s)
|
|
|
|
return json.loads(s, strict=strict)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
@ -97,7 +98,7 @@ def parse_partial_json(s: str) -> Any:
|
|
|
|
|
|
|
|
|
|
|
|
# Attempt to parse the modified string as JSON.
|
|
|
|
# Attempt to parse the modified string as JSON.
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
return json.loads(new_s)
|
|
|
|
return json.loads(new_s, strict=strict)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
# If we still can't parse the string as JSON, return None to indicate failure.
|
|
|
|
# If we still can't parse the string as JSON, return None to indicate failure.
|
|
|
|
return None
|
|
|
|
return None
|
|
|
@ -162,62 +163,26 @@ def parse_and_check_json_markdown(text: str, expected_keys: List[str]) -> dict:
|
|
|
|
return json_obj
|
|
|
|
return json_obj
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SimpleJsonOutputParser(BaseOutputParser[Any]):
|
|
|
|
class SimpleJsonOutputParser(BaseCumulativeTransformOutputParser[Any]):
|
|
|
|
"""Parse the output of an LLM call to a JSON object."""
|
|
|
|
"""Parse the output of an LLM call to a JSON object.
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, text: str) -> Any:
|
|
|
|
When used in streaming mode, it will yield partial JSON objects containing
|
|
|
|
text = text.strip()
|
|
|
|
all the keys that have been returned so far.
|
|
|
|
try:
|
|
|
|
|
|
|
|
return parse_partial_json(text)
|
|
|
|
|
|
|
|
except JSONDecodeError as e:
|
|
|
|
|
|
|
|
raise OutputParserException(f"Invalid json output: {text}") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def _type(self) -> str:
|
|
|
|
|
|
|
|
return "simple_json_output_parser"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PartialFunctionsJsonOutputParser(BaseCumulativeTransformOutputParser[Any]):
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def _type(self) -> str:
|
|
|
|
|
|
|
|
return "partial_functions_json"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_result(self, result: List[Generation]) -> Any:
|
|
|
|
In streaming, if `diff` is set to `True`, yields JSONPatch operations
|
|
|
|
if len(result) != 1:
|
|
|
|
describing the difference between the previous and the current object.
|
|
|
|
raise OutputParserException(
|
|
|
|
"""
|
|
|
|
f"Expected exactly one result, but got {len(result)}"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
generation = result[0]
|
|
|
|
|
|
|
|
if not isinstance(generation, ChatGeneration):
|
|
|
|
|
|
|
|
raise OutputParserException(
|
|
|
|
|
|
|
|
"This output parser can only be used with a chat generation."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
message = generation.message
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
function_call = message.additional_kwargs["function_call"]
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return parse_partial_json(function_call["arguments"])
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _diff(self, prev: Optional[Any], next: Any) -> Any:
|
|
|
|
def _diff(self, prev: Optional[Any], next: Any) -> Any:
|
|
|
|
return jsonpatch.make_patch(prev, next).patch
|
|
|
|
return jsonpatch.make_patch(prev, next).patch
|
|
|
|
|
|
|
|
|
|
|
|
# This method would be called by the default implementation of `parse_result`
|
|
|
|
|
|
|
|
# but we're overriding that method so it's not needed.
|
|
|
|
|
|
|
|
def parse(self, text: str) -> Any:
|
|
|
|
def parse(self, text: str) -> Any:
|
|
|
|
raise NotImplementedError()
|
|
|
|
text = text.strip()
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return parse_json_markdown(text.strip(), parse_partial_json)
|
|
|
|
|
|
|
|
except JSONDecodeError as e:
|
|
|
|
|
|
|
|
raise OutputParserException(f"Invalid json output: {text}") from e
|
|
|
|
|
|
|
|
|
|
|
|
class PartialJsonOutputParser(BaseCumulativeTransformOutputParser[Any]):
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def _type(self) -> str:
|
|
|
|
def _type(self) -> str:
|
|
|
|
return "partial_functions_json"
|
|
|
|
return "simple_json_output_parser"
|
|
|
|
|
|
|
|
|
|
|
|
def _diff(self, prev: Optional[Any], next: Any) -> Any:
|
|
|
|
|
|
|
|
return jsonpatch.make_patch(prev, next).patch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, text: str) -> Any:
|
|
|
|
|
|
|
|
return parse_json_markdown(text, parse_partial_json)
|
|
|
|
|
|
|
|