Files
SIA/sia/response_parser.py

103 lines
4.3 KiB
Python

from datetime import datetime
import uuid
import xml.etree.ElementTree as ET
from typing import Union
from .command import Command
from .delete_command import DeleteCommand
from .stop_command import StopCommand
from .entry import Entry
from .background_entry import BackgroundEntry
from .parse_error_entry import ParseErrorEntry
from .read_entry import ReadEntry
from .reasoning_entry import ReasoningEntry
from .repeat_entry import RepeatEntry
from .single_entry import SingleEntry
from .write_entry import WriteEntry
class ResponseParser:
"""
Parses XML responses from the LLM into commands or entries.
The parser validates the XML structure and converts it into the appropriate
Command or Entry object based on the root element tag. Invalid input results
in ParseErrorEntry objects rather than raising exceptions.
Attributes:
io_buffer: Buffer to use for IO operations
"""
def __init__(self, io_buffer):
"""
Initialize parser with IO buffer.
Args:
io_buffer: Buffer to use for IO operations
"""
self.io_buffer = io_buffer
def parse(self, xml: str) -> Union[Command, Entry]:
"""
Parse XML response into a Command or Entry.
Args:
xml: XML string to parse
Returns:
Command or Entry based on the XML content
ParseErrorEntry for any parsing or validation errors
"""
entry_id = str(uuid.uuid4())
timestamp = datetime.now()
try:
root = ET.fromstring(xml.strip())
except ET.ParseError as e:
return ParseErrorEntry(xml, f"Invalid XML: {str(e)}", entry_id, timestamp)
try:
if root.tag == 'delete':
target_id = root.get('id')
if not target_id:
return ParseErrorEntry(xml, "Delete command missing required 'id' attribute", entry_id, timestamp)
if len(root.attrib) > 1:
return ParseErrorEntry(xml, "Delete command should only have 'id' attribute", entry_id, timestamp)
return DeleteCommand(target_id)
elif root.tag == 'stop':
return StopCommand()
elif root.tag == 'background':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Background entry requires (only) script content", entry_id, timestamp)
return BackgroundEntry(root.text, entry_id, timestamp)
elif root.tag == 'repeat':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Repeat entry requires (only) script content", entry_id, timestamp)
return RepeatEntry(root.text, entry_id, timestamp)
elif root.tag == 'single':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Single entry requires (only) script content", entry_id, timestamp)
return SingleEntry(root.text, entry_id, timestamp)
elif root.tag == 'reasoning':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Reasoning entry requires (only) text content", entry_id, timestamp)
return ReasoningEntry(root.text, entry_id, timestamp)
elif root.tag == 'read_stdin':
return ReadEntry(self.io_buffer, entry_id, timestamp)
elif root.tag == 'write_stdout':
if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '':
return ParseErrorEntry(xml, "Write stdout entry requires (only) text content", entry_id, timestamp)
return WriteEntry(root.text, self.io_buffer, entry_id, timestamp)
else:
return ParseErrorEntry(xml, f"Unknown root element: {root.tag}", entry_id, timestamp)
except Exception as e:
return ParseErrorEntry(xml, f"Error parsing response: {str(e)}", entry_id, timestamp)