"""Trajectory segmentation operation for task memory generation. This module provides operations to segment trajectories into meaningful step sequences that can be used for more granular memory extraction. """ import json import re from typing import List from flowllm.core.context import C from flowllm.core.enumeration import Role from flowllm.core.op import BaseAsyncOp from flowllm.core.schema import Message as FlowMessage from loguru import logger from reme_ai.schema import Message, Trajectory @C.register_op() class TrajectorySegmentationOp(BaseAsyncOp): """Segment trajectories into meaningful step sequences. This operation uses LLM to identify natural breakpoints in trajectories, allowing for more granular analysis and memory extraction from specific segments rather than entire trajectories. """ file_path: str = __file__ async def async_execute(self): """Segment trajectories into meaningful steps""" # Get trajectories from context all_trajectories: List[Trajectory] = self.context.get("all_trajectories", []) success_trajectories: List[Trajectory] = self.context.get("success_trajectories", []) failure_trajectories: List[Trajectory] = self.context.get("failure_trajectories", []) if not all_trajectories: logger.warning("No trajectories found in context") return # Determine which trajectories to segment target_trajectories = self._get_target_trajectories( all_trajectories, success_trajectories, failure_trajectories, ) # Add segmentation info to trajectories segmented_count = 0 for trajectory in target_trajectories: segments = await self._llm_segment_trajectory(trajectory) trajectory.metadata["segments"] = segments segmented_count += 1 logger.info(f"Segmented {segmented_count} trajectories") # Update context with segmented trajectories def _get_target_trajectories( self, all_trajectories: List[Trajectory], success_trajectories: List[Trajectory], failure_trajectories: List[Trajectory], ) -> List[Trajectory]: """Determine which trajectories to segment based on configuration""" segment_target = self.op_params.get("segment_target", "all") if segment_target == "success": return success_trajectories elif segment_target == "failure": return failure_trajectories else: return all_trajectories async def _llm_segment_trajectory(self, trajectory: Trajectory) -> List[List[Message]]: """Use LLM for trajectory segmentation""" trajectory_content = self._format_trajectory_content(trajectory) prompt = self.prompt_format( prompt_name="step_segmentation_prompt", query=trajectory.metadata.get("query", ""), trajectory_content=trajectory_content, total_steps=len(trajectory.messages), ) def parse_segmentation(message: Message) -> List[List[Message]]: content = message.content segment_points = self._parse_segmentation_response(content) # Segment trajectory based on segmentation points segments = [] start_idx = 0 for end_idx in segment_points: if start_idx < end_idx <= len(trajectory.messages): segments.append(trajectory.messages[start_idx:end_idx]) start_idx = end_idx # Add remaining steps if start_idx < len(trajectory.messages): segments.append(trajectory.messages[start_idx:]) return segments if segments else [trajectory.messages] return await self.llm.achat( messages=[FlowMessage(role=Role.USER, content=prompt)], callback_fn=parse_segmentation, default_value=[trajectory.messages], ) @staticmethod def _format_trajectory_content(trajectory: Trajectory) -> str: """Format trajectory content for LLM processing""" content = "" for i, step in enumerate(trajectory.messages): content += f"Step {i + 1} ({step.role.value}):\n{step.content}\n\n" return content @staticmethod def _parse_segmentation_response(response: str) -> List[int]: """Parse segmentation response from LLM""" segment_points = [] # Try to extract JSON format json_pattern = r"```json\s*([\s\S]*?)\s*```" json_blocks = re.findall(json_pattern, response) if json_blocks: try: parsed = json.loads(json_blocks[0]) if isinstance(parsed, dict) and "segment_points" in parsed: segment_points = parsed["segment_points"] elif isinstance(parsed, list): segment_points = parsed except json.JSONDecodeError: pass # Fallback: extract numbers if not segment_points: numbers = re.findall(r"\b\d+\b", response) segment_points = [int(num) for num in numbers if int(num) > 0] return sorted(list(set(segment_points)))