parser_manager.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. """Registry for converting LLM responses via pluggable parsers."""
  2. from __future__ import annotations
  3. from pathlib import Path
  4. from threading import Lock
  5. from typing import Dict, Optional, Type
  6. import yaml
  7. from app.const import CONFIG_DIR
  8. from .parsers import BaseParser, PARSER_REGISTRY
  9. class ParserManager:
  10. """Lazy singleton keeping parser instances referenced by name."""
  11. _instance: Optional["ParserManager"] = None
  12. _lock = Lock()
  13. def __new__(cls, config_path: Optional[Path] = None) -> "ParserManager":
  14. with cls._lock:
  15. if cls._instance is None:
  16. cls._instance = super().__new__(cls)
  17. cls._instance._build(config_path)
  18. return cls._instance
  19. def _build(self, config_path: Optional[Path]) -> None:
  20. path = Path(config_path or (CONFIG_DIR / "parser_settings.yaml"))
  21. self._parsers: Dict[str, BaseParser] = {}
  22. self._register_defaults()
  23. if path.exists():
  24. self._load_custom_parsers(path)
  25. def _register_defaults(self) -> None:
  26. for name, parser_cls in PARSER_REGISTRY.items():
  27. self._parsers[name] = parser_cls()
  28. def _load_custom_parsers(self, path: Path) -> None:
  29. with path.open("r", encoding="utf-8") as fh:
  30. parser_settings = yaml.safe_load(fh) or {}
  31. for name, config in parser_settings.items():
  32. parser_type = config.get("type")
  33. if not parser_type or parser_type not in PARSER_REGISTRY:
  34. continue
  35. parser_cls: Type[BaseParser] = PARSER_REGISTRY[parser_type]
  36. self._parsers[name] = parser_cls(**config.get("options", {}))
  37. def get_parser(self, name: str) -> BaseParser:
  38. if name not in self._parsers:
  39. raise KeyError(f"Parser {name} is not registered")
  40. return self._parsers[name]
  41. def register_parser(self, name: str, parser: BaseParser) -> None:
  42. self._parsers[name] = parser