Coverage for src / lexigram / contracts / ai / parsers.py: 79%

43 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-19 05:41 +0800

1"""Output Parser contracts for G-04 parity. 

2 

3Defines output parsers analogous to LangChain's output parsers. 

4""" 

5 

6from __future__ import annotations 

7 

8from abc import ABC, abstractmethod 

9import json 

10from typing import Any, cast 

11 

12 

13class BaseOutputParser(ABC): 

14 """Base class for output parsers (like LangChain's BaseOutputParser).""" 

15 

16 @abstractmethod 

17 def parse(self, text: str) -> Any: 

18 """Parse text into structured output. 

19 

20 Args: 

21 text: Text to parse. 

22 

23 Returns: 

24 Parsed output. 

25 """ 

26 ... 

27 

28 def get_format_instructions(self) -> str: 

29 """Get format instructions for the model. 

30 

31 Returns: 

32 Format instructions string. 

33 """ 

34 return "" 

35 

36 

37class JSONOutputParser(BaseOutputParser): 

38 """Parse JSON responses (like LangChain's JsonOutputParser).""" 

39 

40 def parse(self, text: str) -> dict[str, Any]: 

41 """Parse JSON from text. 

42 

43 Args: 

44 text: Text containing JSON. 

45 

46 Returns: 

47 Parsed JSON as dict. 

48 """ 

49 text = text.strip() 

50 if text.startswith("```json"): 

51 text = text[7:] 

52 elif text.startswith("```"): 

53 text = text[3:] 

54 if text.endswith("```"): 

55 text = text[:-3] 

56 text = text.strip() 

57 return cast("dict[str, Any]", json.loads(text)) 

58 

59 def get_format_instructions(self) -> str: 

60 return "Return a valid JSON object." 

61 

62 

63class XMLOutputParser(BaseOutputParser): 

64 """Parse XML responses (like LangChain's XMLOutputParser).""" 

65 

66 def parse(self, text: str) -> Any: 

67 """Parse XML from text.""" 

68 return text.strip() 

69 

70 def get_format_instructions(self) -> str: 

71 return "Return XML." 

72 

73 

74class PydanticOutputParser(BaseOutputParser): 

75 """Parse into Pydantic model (like LangChain's PydanticOutputParser).""" 

76 

77 def __init__(self, model: type) -> None: 

78 self.model = model 

79 

80 def parse(self, text: str) -> Any: 

81 """Parse text into Pydantic model.""" 

82 text = text.strip() 

83 if text.startswith("```json"): 

84 text = text[7:] 

85 elif text.startswith("```"): 

86 text = text[3:] 

87 if text.endswith("```"): 

88 text = text[:-3] 

89 data = json.loads(text.strip()) 

90 return self.model(**data) 

91 

92 def get_format_instructions(self) -> str: 

93 return "Return a valid JSON object." 

94 

95 

96__all__ = [ 

97 "BaseOutputParser", 

98 "JSONOutputParser", 

99 "PydanticOutputParser", 

100 "XMLOutputParser", 

101]