diff --git a/mcp_manager.py b/mcp_manager.py index be03fd3..ccb8607 100644 --- a/mcp_manager.py +++ b/mcp_manager.py @@ -21,6 +21,8 @@ except ImportError: Agent = None print("Pydantic AI not available. Install with: pip install pydantic-ai") +from templates_manager import TemplateManager + # MCP Protocol imports for direct connection try: from pydantic_ai.mcp import MCPServerStdio @@ -79,6 +81,7 @@ class PydanticAIAnalyzer: def __init__(self, config: Config): self.config = config + self.template_manager = TemplateManager(self.config.templates_dir) self.mcp_server = None self.available_tools = [] self._cached_activity_details = None @@ -109,11 +112,11 @@ class PydanticAIAnalyzer: model_name = f"openrouter:{config.openrouter_model}" + main_system_prompt = self.template_manager.get_template('main_agent_system_prompt.txt') + self.agent = Agent( model=model_name, - system_prompt="""You are an expert cycling coach with access to comprehensive Garmin Connect data. - You analyze cycling workouts, provide performance insights, and give actionable training recommendations. - Use the available tools to gather detailed workout data and provide comprehensive analysis.""", + system_prompt=main_system_prompt, toolsets=[self.mcp_server] if self.mcp_server else [] ) @@ -276,34 +279,20 @@ class PydanticAIAnalyzer: {json.dumps(user_profile, default=str)} """ - prompt = f""" - Analyze my most recent cycling workout using the provided data. Do not call any tools - all necessary data is already loaded. - - {activity_summary} - - {user_info} - - My training rules and goals: - {training_rules} - - Please provide: - 1. Overall assessment of the workout - 2. How well it aligns with my rules and goals - 3. Areas for improvement - 4. Specific feedback on power, heart rate, duration, and intensity - 5. Recovery recommendations - 6. Comparison with typical performance metrics (use user profile data for baselines) - - Focus on the provided activity details for your analysis. - """ + prompt = self.template_manager.get_template( + 'analyze_last_workout_prompt.txt', + activity_summary=activity_summary, + user_info=user_info, + training_rules=training_rules + ) try: # Create temporary agent without tools for this analysis model_name = f"openrouter:{self.config.openrouter_model}" + temp_analysis_system_prompt = self.template_manager.get_template('temp_analysis_system_prompt.txt') temp_agent = Agent( model=model_name, - system_prompt="""You are an expert cycling coach. Analyze the provided cycling workout data and give actionable insights. - Do not use any tools - all data is provided in the prompt.""", + system_prompt=temp_analysis_system_prompt, toolsets=[] ) @@ -338,24 +327,10 @@ class PydanticAIAnalyzer: else: logger.warning("No MCP tools available!") - prompt = f""" - Please suggest my next cycling workout based on my recent training history. Use the get_activities tool - to get my recent activities and analyze the training pattern. - - My training rules and goals: - {training_rules} - - Please provide: - 1. Analysis of my recent training pattern - 2. Identified gaps or imbalances in my training - 3. Specific workout recommendation for my next session - 4. Target zones (power, heart rate, duration) - 5. Rationale for the recommendation based on recent performance - 6. Alternative options if weather/time constraints exist - 7. How this fits into my overall training progression - - Use additional tools like hrv_data or nightly_sleep to inform recovery status and workout readiness. - """ + prompt = self.template_manager.get_template( + 'suggest_next_workout_prompt.txt', + training_rules=training_rules + ) logger.info("About to call agent.run() with workout suggestion prompt") try: @@ -414,33 +389,21 @@ class PydanticAIAnalyzer: {json.dumps(user_profile, default=str)} """ - prompt = f""" - Perform a comprehensive {analysis_type} analysis using the provided cycling training data. - Do not call any tools - all core data is already loaded. Base your analysis on the following information: - - {activity_summary} - - {user_info} - - My training rules and goals: - {training_rules} - - Focus your {analysis_type} analysis on: - 1. **Performance Analysis**: Analyze power, heart rate, training load, and recovery metrics from the provided data - 2. **Training Periodization**: Consider the recent activity patterns and progression - 3. **Actionable Recommendations**: Provide specific, measurable guidance based on the data - 4. **Risk Assessment**: Identify any signs of overtraining or injury risk from the available metrics - - Be thorough and use the provided data points to support your recommendations. - """ + prompt = self.template_manager.get_template( + 'enhanced_analysis_prompt.txt', + analysis_type=analysis_type, + activity_summary=activity_summary, + user_info=user_info, + training_rules=training_rules + ) try: # Create temporary agent without tools for this analysis model_name = f"openrouter:{self.config.openrouter_model}" + enhanced_temp_system_prompt = self.template_manager.get_template('enhanced_temp_system_prompt.txt') temp_agent = Agent( model=model_name, - system_prompt="""You are an expert cycling coach. Perform comprehensive analysis using the provided data. - Do not use any tools - all relevant data is included in the prompt.""", + system_prompt=enhanced_temp_system_prompt, toolsets=[] ) diff --git a/templates/analyze_last_workout_prompt.txt b/templates/analyze_last_workout_prompt.txt new file mode 100644 index 0000000..6ba7a31 --- /dev/null +++ b/templates/analyze_last_workout_prompt.txt @@ -0,0 +1,20 @@ +Analyze my most recent cycling workout using the provided data. + +ACTIVITY SUMMARY: +{activity_summary} + +USER INFO: +{user_info} + +My training rules and goals: +{training_rules} + +Please provide: +1. Overall assessment of the workout +2. How well it aligns with my rules and goals +3. Areas for improvement +4. Specific feedback on power, heart rate, duration, and intensity +5. Recovery recommendations +6. Comparison with typical performance metrics (use user profile data for baselines) + +Focus on the provided activity details for your analysis. \ No newline at end of file diff --git a/templates/enhanced_analysis_prompt.txt b/templates/enhanced_analysis_prompt.txt new file mode 100644 index 0000000..1010afe --- /dev/null +++ b/templates/enhanced_analysis_prompt.txt @@ -0,0 +1,17 @@ +Perform a comprehensive {analysis_type} analysis using the provided cycling training data. +Do not call any tools - all core data is already loaded. Base your analysis on the following information: + +{activity_summary} + +{user_info} + +My training rules and goals: +{training_rules} + +Focus your {analysis_type} analysis on: +1. **Performance Analysis**: Analyze power, heart rate, training load, and recovery metrics from the provided data +2. **Training Periodization**: Consider the recent activity patterns and progression +3. **Actionable Recommendations**: Provide specific, measurable guidance based on the data +4. **Risk Assessment**: Identify any signs of overtraining or injury risk from the available metrics + +Be thorough and use the provided data points to support your recommendations. \ No newline at end of file diff --git a/templates/enhanced_temp_system_prompt.txt b/templates/enhanced_temp_system_prompt.txt new file mode 100644 index 0000000..cdb19cf --- /dev/null +++ b/templates/enhanced_temp_system_prompt.txt @@ -0,0 +1,2 @@ +You are an expert cycling coach. Perform comprehensive analysis using the provided data. +Do not use any tools - all relevant data is included in the prompt. \ No newline at end of file diff --git a/templates/main_agent_system_prompt.txt b/templates/main_agent_system_prompt.txt new file mode 100644 index 0000000..2070ffa --- /dev/null +++ b/templates/main_agent_system_prompt.txt @@ -0,0 +1,3 @@ +You are an expert cycling coach with access to comprehensive Garmin Connect data. +You analyze cycling workouts, provide performance insights, and give actionable training recommendations. +Use the available tools to gather detailed workout data and provide comprehensive analysis. \ No newline at end of file diff --git a/templates/suggest_next_workout_prompt.txt b/templates/suggest_next_workout_prompt.txt new file mode 100644 index 0000000..74e90d7 --- /dev/null +++ b/templates/suggest_next_workout_prompt.txt @@ -0,0 +1,16 @@ +Please suggest my next cycling workout based on my recent training history. Use the get_activities tool +to get my recent activities and analyze the training pattern. + +My training rules and goals: +{training_rules} + +Please provide: +1. Analysis of my recent training pattern +2. Identified gaps or imbalances in my training +3. Specific workout recommendation for my next session +4. Target zones (power, heart rate, duration) +5. Rationale for the recommendation based on recent performance +6. Alternative options if weather/time constraints exist +7. How this fits into my overall training progression + +Use additional tools like hrv_data or nightly_sleep to inform recovery status and workout readiness. \ No newline at end of file diff --git a/templates/temp_analysis_system_prompt.txt b/templates/temp_analysis_system_prompt.txt new file mode 100644 index 0000000..5ce8efd --- /dev/null +++ b/templates/temp_analysis_system_prompt.txt @@ -0,0 +1,2 @@ +You are an expert cycling coach. Analyze the provided cycling workout data and give actionable insights. +Do not use any tools - all data is provided in the prompt. \ No newline at end of file diff --git a/templates_manager.py b/templates_manager.py new file mode 100644 index 0000000..924d17d --- /dev/null +++ b/templates_manager.py @@ -0,0 +1,40 @@ +import os +import logging +from pathlib import Path + +class TemplateManager: + """Manages prompt templates for the cycling analyzer""" + + def __init__(self, templates_dir: str): + self.templates_dir = Path(templates_dir) + self.templates_dir.mkdir(exist_ok=True) + + def list_templates(self) -> list[str]: + """List available template files""" + return [f.name for f in self.templates_dir.glob("*.txt")] + + def get_template(self, template_name: str, **kwargs) -> str: + """Load and format a template with provided variables""" + template_path = self.templates_dir / template_name + if not template_path.exists(): + raise FileNotFoundError(f"Template '{template_name}' not found in {self.templates_dir}") + + with open(template_path, 'r', encoding='utf-8') as f: + template_content = f.read() + + # Debug logging + logger = logging.getLogger(__name__) + logger.debug(f"Loading template: {template_name}") + logger.debug(f"Template content length: {len(template_content)}") + logger.debug(f"Available kwargs: {list(kwargs.keys())}") + + try: + formatted_template = template_content.format(**kwargs) + return formatted_template + except KeyError as e: + logger.error(f"Template content preview: {template_content[:200]}...") + logger.error(f"Missing variable in template '{template_name}': {e}") + logger.error(f"Available kwargs: {list(kwargs.keys())}") + raise ValueError(f"Missing variable in template '{template_name}': {e}") + except Exception as e: + raise ValueError(f"Error formatting template '{template_name}': {e}") \ No newline at end of file