CoolFace
Apppublic

swaleha19/agent_tuning_framework

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
trajectory_data.py434 linesDownload Raw Back to root
1"""2Trajectory Data Management Module for Agent Tuning Optimization Framework3 4This module provides functionality for loading, processing, and managing agent interaction5trajectories for training and evaluation purposes.6"""7 8import os9import json10import pandas as pd11import numpy as np12from typing import List, Dict, Any, Union, Optional, Tuple13from tqdm import tqdm14 15class Trajectory:16    """Class representing a single agent interaction trajectory."""17    18    def __init__(19        self, 20        task_description: str,21        interactions: List[Dict[str, str]],22        metadata: Optional[Dict[str, Any]] = None23    ):24        """25        Initialize a trajectory.26        27        Args:28            task_description: Description of the task29            interactions: List of interaction turns (each with 'user' and 'agent' keys)30            metadata: Additional metadata about the trajectory31        """32        self.task_description = task_description33        self.interactions = interactions34        self.metadata = metadata or {}35        self.quality_score = self.metadata.get('quality_score', None)36        self.is_positive = self.metadata.get('is_positive', True)37    38    def to_dict(self) -> Dict[str, Any]:39        """40        Convert trajectory to dictionary.41        42        Returns:43            Dictionary representation of the trajectory44        """45        return {46            'task_description': self.task_description,47            'interactions': self.interactions,48            'metadata': self.metadata49        }50    51    @classmethod52    def from_dict(cls, data: Dict[str, Any]) -> 'Trajectory':53        """54        Create trajectory from dictionary.55        56        Args:57            data: Dictionary representation of the trajectory58            59        Returns:60            Trajectory instance61        """62        return cls(63            task_description=data['task_description'],64            interactions=data['interactions'],65            metadata=data.get('metadata', {})66        )67    68    def to_training_format(self, format_type: str = 'interleaved') -> str:69        """70        Convert trajectory to training format.71        72        Args:73            format_type: Format type ('interleaved', 'completion', etc.)74            75        Returns:76            Formatted trajectory as string77        """78        if format_type == 'interleaved':79            # Format as interleaved conversation80            result = f"Task: {self.task_description}\n\n"81            82            for i, interaction in enumerate(self.interactions):83                result += f"User: {interaction['user']}\n"84                result += f"Agent: {interaction['agent']}\n\n"85            86            return result.strip()87        88        elif format_type == 'completion':89            # Format as completion task (last agent response is the target)90            if not self.interactions:91                return ""92            93            result = f"Task: {self.task_description}\n\n"94            95            for i, interaction in enumerate(self.interactions[:-1]):96                result += f"User: {interaction['user']}\n"97                result += f"Agent: {interaction['agent']}\n\n"98            99            # Add last user query without agent response100            result += f"User: {self.interactions[-1]['user']}\n"101            result += f"Agent:"102            103            return result.strip(), self.interactions[-1]['agent'].strip()104        105        else:106            raise ValueError(f"Unsupported format type: {format_type}")107    108    def get_quality_score(self) -> float:109        """110        Get quality score for the trajectory.111        112        Returns:113            Quality score (0.0 to 1.0)114        """115        if self.quality_score is not None:116            return self.quality_score117        118        # Calculate simple quality score based on response length and complexity119        score = 0.0120        121        if not self.interactions:122            return score123        124        # Average response length (normalized)125        avg_length = np.mean([len(turn['agent']) for turn in self.interactions])126        length_score = min(avg_length / 500, 1.0)  # Normalize to max of 500 chars127        128        # Response complexity (simple heuristic based on unique words)129        all_responses = " ".join([turn['agent'] for turn in self.interactions])130        unique_words = len(set(all_responses.lower().split()))131        complexity_score = min(unique_words / 200, 1.0)  # Normalize to max of 200 unique words132        133        # Combine scores134        score = 0.6 * length_score + 0.4 * complexity_score135        136        # Cache the score137        self.quality_score = score138        self.metadata['quality_score'] = score139        140        return score141 142 143class TrajectoryDataset:144    """Dataset for managing collections of agent interaction trajectories."""145    146    def __init__(self, name: str):147        """148        Initialize the trajectory dataset.149        150        Args:151            name: Name of the dataset152        """153        self.name = name154        self.trajectories: List[Trajectory] = []155        self.positive_trajectories: List[Trajectory] = []156        self.negative_trajectories: List[Trajectory] = []157    158    def add_trajectory(self, trajectory: Trajectory) -> None:159        """160        Add a trajectory to the dataset.161        162        Args:163            trajectory: Trajectory to add164        """165        self.trajectories.append(trajectory)166        167        # Add to positive or negative list based on metadata168        if trajectory.is_positive:169            self.positive_trajectories.append(trajectory)170        else:171            self.negative_trajectories.append(trajectory)172    173    def load_from_json(self, file_path: str) -> None:174        """175        Load trajectories from JSON file.176        177        Args:178            file_path: Path to JSON file179        """180        with open(file_path, 'r') as f:181            data = json.load(f)182        183        if isinstance(data, list):184            # List of trajectories185            for item in data:186                self.add_trajectory(Trajectory.from_dict(item))187        elif isinstance(data, dict) and 'trajectories' in data:188            # Dictionary with trajectories key189            for item in data['trajectories']:190                self.add_trajectory(Trajectory.from_dict(item))191        else:192            raise ValueError(f"Unsupported JSON format in {file_path}")193    194    def save_to_json(self, file_path: str) -> None:195        """196        Save trajectories to JSON file.197        198        Args:199            file_path: Path to JSON file200        """201        data = {202            'name': self.name,203            'trajectories': [t.to_dict() for t in self.trajectories]204        }205        206        with open(file_path, 'w') as f:207            json.dump(data, f, indent=2)208    209    def get_trajectories(210        self, 211        positive_only: bool = False,212        negative_only: bool = False,213        min_quality: Optional[float] = None,214        max_samples: Optional[int] = None215    ) -> List[Trajectory]:216        """217        Get trajectories based on filtering criteria.218        219        Args:220            positive_only: Whether to return only positive trajectories221            negative_only: Whether to return only negative trajectories222            min_quality: Minimum quality score threshold223            max_samples: Maximum number of samples to return224            225        Returns:226            Filtered list of trajectories227        """228        if positive_only and negative_only:229            raise ValueError("Cannot set both positive_only and negative_only to True")230        231        # Select base list232        if positive_only:233            trajectories = self.positive_trajectories.copy()234        elif negative_only:235            trajectories = self.negative_trajectories.copy()236        else:237            trajectories = self.trajectories.copy()238        239        # Apply quality filter240        if min_quality is not None:241            trajectories = [t for t in trajectories if t.get_quality_score() >= min_quality]242        243        # Apply max samples limit244        if max_samples is not None and max_samples < len(trajectories):245            trajectories = trajectories[:max_samples]246        247        return trajectories248    249    def get_training_examples(250        self, 251        format_type: str = 'interleaved',252        positive_ratio: float = 0.8,253        min_quality: Optional[float] = 0.5,254        max_samples: Optional[int] = None255    ) -> Union[List[str], Tuple[List[str], List[str]]]:256        """257        Get formatted training examples from trajectories.258        259        Args:260            format_type: Format type ('interleaved', 'completion', etc.)261            positive_ratio: Ratio of positive to total examples262            min_quality: Minimum quality score threshold263            max_samples: Maximum number of samples to return264            265        Returns:266            Formatted training examples (format depends on format_type)267        """268        # Get positive and negative trajectories269        positive = self.get_trajectories(positive_only=True, min_quality=min_quality)270        negative = self.get_trajectories(negative_only=True)271        272        # Calculate sample counts273        if max_samples is not None:274            pos_count = int(max_samples * positive_ratio)275            neg_count = max_samples - pos_count276        else:277            pos_count = len(positive)278            neg_count = len(negative)279        280        # Sample trajectories281        if pos_count < len(positive):282            positive = np.random.choice(positive, pos_count, replace=False).tolist()283        284        if neg_count < len(negative):285            negative = np.random.choice(negative, neg_count, replace=False).tolist()286        287        # Format trajectories288        if format_type == 'interleaved':289            pos_examples = [t.to_training_format(format_type) for t in positive]290            neg_examples = [t.to_training_format(format_type) for t in negative]291            return pos_examples + neg_examples292        293        elif format_type == 'completion':294            pos_inputs = []295            pos_targets = []296            297            for t in positive:298                inp, target = t.to_training_format(format_type)299                pos_inputs.append(inp)300                pos_targets.append(target)301            302            neg_inputs = []303            neg_targets = []304            305            for t in negative:306                inp, target = t.to_training_format(format_type)307                neg_inputs.append(inp)308                neg_targets.append(target)309            310            return pos_inputs + neg_inputs, pos_targets + neg_targets311        312        else:313            raise ValueError(f"Unsupported format type: {format_type}")314    315    def analyze_dataset(self) -> Dict[str, Any]:316        """317        Analyze the dataset and return statistics.318        319        Returns:320            Dictionary of dataset statistics321        """322        if not self.trajectories:323            return {324                'total_trajectories': 0,325                'positive_count': 0,326                'negative_count': 0327            }328        329        # Basic counts330        total = len(self.trajectories)331        positive_count = len(self.positive_trajectories)332        negative_count = len(self.negative_trajectories)333        334        # Quality statistics335        quality_scores = [t.get_quality_score() for t in self.trajectories]336        avg_quality = np.mean(quality_scores)337        min_quality = np.min(quality_scores)338        max_quality = np.max(quality_scores)339        340        # Interaction statistics341        interaction_counts = [len(t.interactions) for t in self.trajectories]342        avg_interactions = np.mean(interaction_counts)343        max_interactions = np.max(interaction_counts)344        345        # Task diversity (simple heuristic based on unique task descriptions)346        unique_tasks = len(set([t.task_description for t in self.trajectories]))347        348        return {349            'total_trajectories': total,350            'positive_count': positive_count,351            'negative_count': negative_count,352            'positive_ratio': positive_count / total if total > 0 else 0,353            'avg_quality': avg_quality,354            'min_quality': min_quality,355            'max_quality': max_quality,356            'avg_interactions': avg_interactions,357            'max_interactions': max_interactions,358            'unique_tasks': unique_tasks359        }360 361 362def create_synthetic_dataset(num_trajectories: int = 10) -> TrajectoryDataset:363    """364    Create a synthetic dataset for testing purposes.365    366    Args:367        num_trajectories: Number of trajectories to create368        369    Returns:370        Synthetic trajectory dataset371    """372    dataset = TrajectoryDataset("synthetic_dataset")373    374    # Sample task descriptions375    task_descriptions = [376        "Book a flight from New York to London for next week",377        "Find a vegetarian restaurant near downtown",378        "Schedule a meeting with the marketing team for tomorrow",379        "Order a new laptop with at least 16GB RAM",380        "Write a congratulatory email to a colleague who got promoted",381        "Research the best electric cars available in the market",382        "Create a weekly meal plan with shopping list",383        "Find information about tourist attractions in Barcelona",384        "Help me debug a Python script that's giving an IndexError",385        "Summarize the main points from the attached research paper"386    ]387    388    # Create trajectories389    for i in range(num_trajectories):390        # Select task391        task_idx = i % len(task_descriptions)392        task = task_descriptions[task_idx]393        394        # Create interactions (2-4 turns)395        num_turns = np.random.randint(2, 5)396        interactions = []397        398        for j in range(num_turns):399            if j == 0:400                user_msg = f"I need help with this task: {task}"401                agent_msg = f"I'd be happy to help you {task.lower()}. Could you provide more details about your preferences?"402            elif j == num_turns - 1:403                user_msg = "That sounds good. Please proceed with the final steps."404                agent_msg = f"I've completed the task to {task.lower()}. Here's a summary of what I did..."405            else:406                user_msg = f"I prefer options that are {['affordable', 'convenient', 'high-quality'][j % 3]}."407                agent_msg = f"Based on your preference for {['affordable', 'convenient', 'high-quality'][j % 3]} options, I recommend..."408            409            interactions.append({410                'user': user_msg,411                'agent': agent_msg412            })413        414        # Determine if positive or negative example415        is_positive = (i % 4 != 0)  # 75% positive, 25% negative416        417        # Create metadata418        metadata = {419            'is_positive': is_positive,420            'quality_score': np.random.uniform(0.7, 0.9) if is_positive else np.random.uniform(0.3, 0.5),421            'created_at': '2025-05-21'422        }423        424        # Create and add trajectory425        trajectory = Trajectory(426            task_description=task,427            interactions=interactions,428            metadata=metadata429        )430        431        dataset.add_trajectory(trajectory)432    433    return dataset434