Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-llm/src/lexigram/ai/llm/structured/formatter.py: 27%
66 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""Response formatting utilities for LLM outputs."""
3from __future__ import annotations
5import re
6from typing import TYPE_CHECKING, Any, cast
8if TYPE_CHECKING:
9 from lexigram.ai.llm.types import Completion
10 from lexigram.contracts.core import JSON
12from lexigram.ai.llm.structured.exceptions import ParseError
13from lexigram.ai.llm.structured.extractor import JSONExtractor
16class ResponseFormatter:
17 """Format and convert LLM responses to various types.
19 Example:
20 >>> formatter = ResponseFormatter()
21 >>> completion = Completion(content="42", ...)
22 >>> num = formatter.to_int(completion)
23 >>> print(num)
24 42
25 """
27 @staticmethod
28 def to_json(completion: Completion) -> JSON:
29 """Convert response to JSON.
31 Args:
32 completion: LLM completion
34 Returns:
35 Parsed JSON
37 Example:
38 >>> data = formatter.to_json(completion)
39 """
40 data = JSONExtractor.extract(completion.content)
41 # If the extractor returned an array, return the first object for callers that
42 # expect a JSON object (this is a best-effort convenience behavior).
43 if isinstance(data, list):
44 if not data:
45 msg = "Expected JSON object, got empty array"
46 raise ParseError(msg)
47 if not isinstance(data[0], dict):
48 msg = f"Expected JSON object, got array of {type(data[0])}"
49 raise ParseError(msg)
50 return cast("dict[str, Any]", data[0])
51 return cast("dict[str, Any]", data)
53 @staticmethod
54 def to_string(completion: Completion, strip: bool = True) -> str:
55 """Convert response to string.
57 Args:
58 completion: LLM completion
59 strip: Whether to strip whitespace
61 Returns:
62 Response string
64 Example:
65 >>> text = formatter.to_string(completion)
66 """
67 content = completion.content
68 return content.strip() if strip else content
70 @staticmethod
71 def to_int(completion: Completion) -> int:
72 """Convert response to integer.
74 Args:
75 completion: LLM completion
77 Returns:
78 Parsed integer
80 Raises:
81 ParseError: If conversion fails
83 Example:
84 >>> num = formatter.to_int(completion)
85 """
86 content = completion.content.strip()
88 # Try direct conversion
89 try:
90 return int(content)
91 except ValueError:
92 pass
94 # Try extracting number from text
95 numbers = re.findall(r"-?\d+", content)
96 if numbers:
97 return int(numbers[0])
99 msg = f"Cannot convert to int: {content}"
100 raise ParseError(msg)
102 @staticmethod
103 def to_float(completion: Completion) -> float:
104 """Convert response to float.
106 Args:
107 completion: LLM completion
109 Returns:
110 Parsed float
112 Raises:
113 ParseError: If conversion fails
115 Example:
116 >>> num = formatter.to_float(completion)
117 """
118 content = completion.content.strip()
120 # Try direct conversion
121 try:
122 return float(content)
123 except ValueError:
124 pass
126 # Try extracting number from text
127 numbers = re.findall(r"-?\d+\.?\d*", content)
128 if numbers:
129 return float(numbers[0])
131 msg = f"Cannot convert to float: {content}"
132 raise ParseError(msg)
134 @staticmethod
135 def to_bool(completion: Completion) -> bool:
136 """Convert response to boolean.
138 Args:
139 completion: LLM completion
141 Returns:
142 Parsed boolean
144 Example:
145 >>> result = formatter.to_bool(completion)
146 """
147 content = completion.content.strip().lower()
149 # Check common boolean representations
150 if content in ("true", "yes", "1", "y", "correct", "affirmative"):
151 return True
152 if content in ("false", "no", "0", "n", "incorrect", "negative"):
153 return False
155 msg = f"Cannot convert to bool: {content}"
156 raise ParseError(msg)
158 @staticmethod
159 def to_list(completion: Completion, separator: str = "\n") -> list[str]:
160 """Convert response to list of strings.
162 Args:
163 completion: LLM completion
164 separator: String separator (default: newline)
166 Returns:
167 List of strings
169 Example:
170 >>> items = formatter.to_list(completion)
171 """
172 content = completion.content.strip()
174 # Try parsing as JSON array first
175 try:
176 result = JSONExtractor.extract(content)
177 if isinstance(result, list):
178 return list(map(str, result))
179 except ParseError:
180 pass
182 # Split by separator and clean up
183 items = content.split(separator)
184 return list(map(str.strip, filter(str.strip, items)))