Spaces:
Running
Running
| """ | |
| Spatial Atlas — FieldWorkArena Domain Handler | |
| Main entry point for FieldWorkArena tasks. Orchestrates: | |
| 1. Goal parsing (structured task extraction) | |
| 2. Multimodal file processing (images, PDFs, videos, text) | |
| 3. Spatial scene graph construction | |
| 4. Entropy-guided reasoning | |
| 5. Output format matching | |
| Receives: text goal + file attachments from green agent | |
| Returns: formatted answer text | |
| """ | |
| import logging | |
| from a2a.server.tasks import TaskUpdater | |
| from a2a.types import TaskState | |
| from a2a.utils import new_agent_text_message | |
| from config import Config | |
| from llm import LLMClient | |
| from fieldwork.parser import GoalParser | |
| from fieldwork.vision import VisionPipeline | |
| from fieldwork.spatial import SpatialAnalyzer | |
| from fieldwork.reasoner import FieldWorkReasoner | |
| from fieldwork.formatter import AnswerFormatter | |
| logger = logging.getLogger("spatial-atlas.fieldwork") | |
| class FieldWorkHandler: | |
| """Handle FieldWorkArena benchmark tasks.""" | |
| def __init__(self, config: Config, llm: LLMClient): | |
| self.config = config | |
| self.llm = llm | |
| self.parser = GoalParser() | |
| self.vision = VisionPipeline(llm, max_video_frames=config.max_video_frames) | |
| self.spatial = SpatialAnalyzer(llm) | |
| self.reasoner = FieldWorkReasoner(config, llm) | |
| self.formatter = AnswerFormatter() | |
| async def handle( | |
| self, | |
| text: str, | |
| file_parts: list[tuple[str, str, str | bytes]], | |
| updater: TaskUpdater, | |
| ) -> str: | |
| """ | |
| Process a FieldWorkArena task end-to-end. | |
| Args: | |
| text: Goal text from green agent (# Question / # Input Data / # Output Format) | |
| file_parts: List of (name, mime_type, data) for attached files | |
| updater: A2A task updater for progress reporting | |
| Returns: | |
| Formatted answer string | |
| """ | |
| # 1. Parse the goal string | |
| task = self.parser.parse(text) | |
| logger.info(f"Task parsed: query='{task.query[:80]}...', files={len(file_parts)}") | |
| # 2. Process all file attachments into text context | |
| await updater.update_status( | |
| TaskState.working, | |
| new_agent_text_message( | |
| f"Processing {len(file_parts)} input file(s)..." | |
| ), | |
| ) | |
| file_contexts = [] | |
| for name, mime, data in file_parts: | |
| context = await self.vision.process_file(name, mime, data) | |
| file_contexts.append(context) | |
| logger.info(f"Processed {len(file_contexts)} files into context") | |
| # 3. Build spatial scene graph | |
| await updater.update_status( | |
| TaskState.working, | |
| new_agent_text_message("Building spatial scene graph..."), | |
| ) | |
| scene = await self.spatial.build_scene(task.query, file_contexts) | |
| logger.info( | |
| f"Scene: {scene.entity_count} entities, " | |
| f"{len(scene.relations)} relations, " | |
| f"{scene.violation_count} violations" | |
| ) | |
| # 4. Reason over evidence to produce answer | |
| await updater.update_status( | |
| TaskState.working, | |
| new_agent_text_message("Reasoning over evidence..."), | |
| ) | |
| answer = await self.reasoner.reason( | |
| query=task.query, | |
| file_contexts=file_contexts, | |
| scene=scene, | |
| output_format=task.output_format, | |
| ) | |
| # 5. Format answer to match expected output | |
| formatted = self.formatter.format_answer(answer, task.output_format) | |
| logger.info(f"Answer formatted ({len(formatted)} chars): {formatted[:200]}...") | |
| return formatted | |