From b52ecbbdb1e1b91abde99b9807029aa99ae165f7 Mon Sep 17 00:00:00 2001 From: Feng Zhang Date: Sat, 26 Jul 2025 07:14:55 -0700 Subject: [PATCH 1/3] Visualize should show the branch tree --- .../src/advisor/interactive.rs | 95 ++++++++++++++----- examples/financial_advisor/src/advisor/mod.rs | 10 +- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/examples/financial_advisor/src/advisor/interactive.rs b/examples/financial_advisor/src/advisor/interactive.rs index bbda019..7e58f8b 100644 --- a/examples/financial_advisor/src/advisor/interactive.rs +++ b/examples/financial_advisor/src/advisor/interactive.rs @@ -837,28 +837,79 @@ impl<'a> InteractiveSession<'a> { } async fn show_memory_visualization(&self) -> Result<()> { - println!("{}", "🌳 Memory Tree Visualization".green().bold()); - println!("{}", "━".repeat(35).dimmed()); - - // ASCII art representation of memory tree - println!("Memory Tree Structure:"); - println!("ā”œā”€ā”€ šŸ¦ Financial Data"); - println!("│ ā”œā”€ā”€ šŸ“Š Market Data (validated)"); - println!("│ ā”œā”€ā”€ šŸ’¼ Recommendations"); - println!("│ └── šŸ‘¤ Client Profiles"); - println!("ā”œā”€ā”€ šŸ” Validation Layer"); - println!("│ ā”œā”€ā”€ āœ… Cross-references"); - println!("│ ā”œā”€ā”€ šŸ›”ļø Security checks"); - println!("│ └── šŸ“ˆ Confidence scores"); - println!("└── šŸ“ Audit Trail"); - println!(" ā”œā”€ā”€ šŸ• Timestamps"); - println!(" ā”œā”€ā”€ šŸ”— Version links"); - println!(" └── šŸ‘„ User actions"); + println!("{}", "🌳 Branch Tree Visualization".green().bold()); + println!("{}", "━".repeat(50).dimmed()); + + // Get all branches + let branches = self.advisor.list_branches()?; + let current_branch = self.advisor.current_branch(); + + // Display branches in a tree structure + println!("{}", "Repository Branches:".yellow()); + println!(); + + // Sort branches to ensure main/master comes first + let mut sorted_branches = branches.clone(); + sorted_branches.sort_by(|a, b| { + if a == "main" || a == "master" { + std::cmp::Ordering::Less + } else if b == "main" || b == "master" { + std::cmp::Ordering::Greater + } else { + a.cmp(b) + } + }); + + // Display main branch first with special formatting + for (i, branch) in sorted_branches.iter().enumerate() { + let is_last = i == sorted_branches.len() - 1; + let connector = if is_last { "└──" } else { "ā”œā”€ā”€" }; + let vertical_line = if is_last { " " } else { "│ " }; + + // Determine branch color and symbol + let (branch_display, symbol) = if branch == ¤t_branch { + (branch.green().bold(), "ā—") // Current branch + } else if branch == "main" || branch == "master" { + (branch.blue().bold(), "ā—†") // Main branch + } else { + (branch.normal(), "ā—‹") // Other branches + }; + + // Display branch with appropriate formatting + println!("{} {} {} {}", + connector.dimmed(), + symbol, + branch_display, + if branch == ¤t_branch { "(current)".dimmed() } else { "".normal() } + ); + + // Show some recent commits for the current branch + if branch == ¤t_branch { + if let Ok(history) = self.advisor.get_memory_history(Some(3)).await { + for (j, commit) in history.iter().enumerate() { + let is_last_commit = j == history.len() - 1 || j == 2; + let commit_connector = if is_last_commit { "└──" } else { "ā”œā”€ā”€" }; + + println!("{} {} {} {}", + vertical_line.dimmed(), + commit_connector.dimmed(), + commit.hash[..8].yellow(), + commit.message.dimmed() + ); + } + } + } + } println!(); - println!("{} All nodes are cryptographically signed", "šŸ”’".cyan()); - println!("{} Complete history is preserved", "ā±ļø".blue()); - println!("{} Branches allow safe experimentation", "🌿".green()); + println!("{}", "Legend:".cyan()); + println!(" {} Current branch", "ā—".green()); + println!(" {} Main branch", "ā—†".blue()); + println!(" {} Other branches", "ā—‹".normal()); + println!(); + println!("{} All branches share the same versioned memory system", "šŸ’¾".cyan()); + println!("{} Switch branches with: switch ", "šŸ”€".yellow()); + println!("{} Create new branch with: branch ", "🌿".green()); Ok(()) } @@ -925,7 +976,7 @@ impl<'a> InteractiveSession<'a> { fn show_branch_info(&self) { // Show all branches like git branch command - match self.advisor.memory_store.list_branches() { + match self.advisor.list_branches() { Ok(branches) => { let current_branch = self.advisor.get_actual_current_branch(); @@ -965,7 +1016,7 @@ impl<'a> InteractiveSession<'a> { println!("{}", "🌳 Available Branches".green().bold()); println!("{}", "━".repeat(25).dimmed()); - match self.advisor.memory_store.list_branches() { + match self.advisor.list_branches() { Ok(branches) => { let current_branch = self.advisor.get_actual_current_branch(); diff --git a/examples/financial_advisor/src/advisor/mod.rs b/examples/financial_advisor/src/advisor/mod.rs index a8c1e98..23717b7 100644 --- a/examples/financial_advisor/src/advisor/mod.rs +++ b/examples/financial_advisor/src/advisor/mod.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::memory::{MemoryStore, MemoryType, Storable, ValidatedMemory}; +use crate::memory::{MemoryCommit, MemoryStore, MemoryType, Storable, ValidatedMemory}; use crate::security::SecurityMonitor; use crate::validation::{MemoryValidator, ValidationResult}; @@ -413,6 +413,14 @@ impl FinancialAdvisor { } } + pub fn list_branches(&self) -> Result> { + self.memory_store.list_branches() + } + + pub async fn get_memory_history(&self, limit: Option) -> Result> { + self.memory_store.get_memory_history(limit).await + } + async fn generate_ai_reasoning( &self, symbol: &str, From be6760686e6ed7a1e432d11b9c89e6655c29b190 Mon Sep 17 00:00:00 2001 From: Feng Zhang Date: Sat, 26 Jul 2025 08:06:04 -0700 Subject: [PATCH 2/3] refine recommend --- examples/financial_advisor/README.md | 387 +++++++----------- .../src/advisor/interactive.rs | 60 +-- examples/financial_advisor/src/advisor/mod.rs | 83 +++- .../src/advisor/recommendations.rs | 16 +- 4 files changed, 267 insertions(+), 279 deletions(-) diff --git a/examples/financial_advisor/README.md b/examples/financial_advisor/README.md index 45123c1..73f7c54 100644 --- a/examples/financial_advisor/README.md +++ b/examples/financial_advisor/README.md @@ -1,286 +1,205 @@ # Financial Advisory AI with Versioned Memory -A demonstration of an AI-powered financial advisory system using ProllyTree for versioned memory management. This example showcases how to build a secure, auditable AI agent that maintains consistent memory across time and can handle complex financial recommendations with full traceability. - -## Features - -- šŸ¤– **AI-Powered Recommendations**: Uses OpenAI's API to generate intelligent investment advice -- šŸ“Š **Multi-Source Data Validation**: Cross-validates market data from multiple sources -- šŸ”’ **Security Monitoring**: Detects and prevents injection attacks and anomalies -- šŸ“š **Versioned Memory**: Uses ProllyTree to maintain git-like versioned storage of all data -- šŸ• **Temporal Queries**: Query recommendations and data as they existed at any point in time -- 🌿 **Smart Branch Management**: Git-style branch operations with validation and external tool sync -- šŸ¦ **Real-time UI**: Live branch display that updates with external git operations -- šŸ“ **Audit Trail**: Complete audit logs for compliance and debugging -- šŸŽÆ **Risk-Aware**: Adapts recommendations based on client risk tolerance -- šŸ‘¤ **Persistent Profiles**: Client profiles automatically saved per branch - -## Prerequisites - -- Rust (latest stable version) -- Git (for memory versioning) -- OpenAI API key (optional, for AI-enhanced reasoning) +A secure, auditable AI financial advisor demonstrating ProllyTree's versioned memory capabilities with git-like branching, temporal queries, and complete audit trails. ## Quick Start -### 1. Initialize Storage Directory - -First, create a directory with git repository for the advisor's memory: - ```bash -# Create a directory for the advisor's memory -mkdir -p /tmp/advisor -cd /tmp/advisor +# 1. Setup storage with git +mkdir -p /tmp/advisor && cd /tmp/advisor && git init -# Initialize git repository (required for versioned memory) -git init - -# Return to the project directory -cd /path/to/prollytree -``` +# 2. Set OpenAI API key (optional, for AI reasoning) +export OPENAI_API_KEY="your-api-key" -### 2. Set Environment Variables (Optional) - -For AI-enhanced recommendations, set your OpenAI API key: - -```bash -export OPENAI_API_KEY="your-api-key-here" -``` - -### 3. Run the Financial Advisor - -```bash -# Basic usage with temporary storage -cargo run --example financial_advisor -- --storage /tmp/advisor/data advise - -# Or use the shorter form +# 3. Run the advisor cargo run -- --storage /tmp/advisor/data advise ``` -## Usage - -### Interactive Commands - -Once the advisor is running, you can use these commands: - -#### Core Operations -- `recommend ` - Get AI-powered recommendation for a stock symbol (e.g., `recommend AAPL`) -- `profile` - Show current client profile -- `risk ` - Set risk tolerance (`conservative`, `moderate`, or `aggressive`) +## Core Features -#### History and Analysis -- `history` - Show recent recommendations -- `history ` - Show recommendations at a specific git commit -- `history --branch ` - Show recommendations from a specific branch -- `memory` - Show memory system status and statistics -- `audit` - Show complete audit trail +- **Versioned Memory**: Git-like storage with branches, commits, and history +- **AI Recommendations**: OpenAI-powered analysis with risk-aware insights +- **Security**: Injection detection, anomaly monitoring, audit trails +- **Multi-Source Validation**: Cross-validates data from multiple sources -#### Branch Management -- `branch ` - Create and switch to a new memory branch -- `switch ` - Switch to an existing branch -- `list-branches` - Show all available branches with visual indicators -- `branch-info` - List branches in git-style format (like `git branch`) +## How Recommendations Work -#### Advanced Features -- `visualize` - Show memory tree visualization -- `test-inject ` - Test security monitoring (try malicious inputs) +The `recommend ` command generates AI-powered investment advice through a sophisticated pipeline: -#### Other Commands -- `help` - Show all available commands -- `exit` or `quit` - Exit the advisor +### 1. Data Collection (Simulated) +The system simulates fetching real-time market data from three sources: +- **Bloomberg**: Premium data with 95% trust weight (50ms latency) +- **Yahoo Finance**: Free tier with 85% trust weight (120ms latency) +- **Alpha Vantage**: Rate-limited with 80% trust weight (200ms latency) -### Example Session - -```bash +``` šŸ¦ [main] recommend AAPL -šŸ“Š Recommendation Generated -Symbol: AAPL -Action: BUY -Confidence: 52.0% -Reasoning: Analysis of AAPL at $177.89 with P/E ratio 28.4... +šŸ” Fetching market data for AAPL... +šŸ“” Validating data from 3 sources... +``` -šŸ¦ [main] risk aggressive -āœ… Risk tolerance set to: Aggressive +### 2. Data Validation & Cross-Reference +Each source returns realistic market data based on actual stock characteristics: +```json +{ + "price": 177.89, + "pe_ratio": 28.4, + "volume": 53_245_678, + "market_cap": 2_800_000_000_000, + "sector": "Technology" +} +``` -šŸ¦ [main] recommend AAPL +The validator: +- Compares prices across sources (must be within 2% variance) +- Generates SHA-256 hash for data integrity +- Assigns confidence score based on source agreement +- Stores validated data in versioned memory + +### 3. Security Checks +Before processing, the security monitor scans for: +- SQL injection patterns +- Malicious payloads +- Data anomalies +- Manipulation attempts + +### 4. AI-Powered Analysis +The recommendation engine considers: +- **Client Profile**: Risk tolerance, investment timeline, goals +- **Market Data**: Price, P/E ratio, volume, sector trends +- **Historical Context**: Past recommendations on current branch + +With OpenAI API: +``` +🧠 Generating AI-powered analysis... šŸ“Š Recommendation Generated Symbol: AAPL Action: BUY -Confidence: 60.0% -(Notice higher confidence for aggressive risk tolerance) - -šŸ¦ [main] branch test-strategy -🌿 Creating memory branch: test-strategy -āœ… Branch 'test-strategy' created successfully -šŸ”€ Switched to branch 'test-strategy' +Confidence: 85.0% +Reasoning: Strong fundamentals with P/E of 28.4... -šŸ¦ [test-strategy] recommend MSFT -šŸ“Š Recommendation Generated -Symbol: MSFT -Action: BUY -Confidence: 58.0% - -šŸ¦ [test-strategy] list-branches -🌳 Available Branches -━━━━━━━━━━━━━━━━━━━━━━━━━ - ā—‹ main - ā— test-strategy (current) - -šŸ¦ [test-strategy] switch main -šŸ”€ Switching to branch: main -āœ… Switched to branch 'main' - -šŸ¦ [main] history -šŸ“œ Recent Recommendations -šŸ“Š Recommendation #1 - Symbol: AAPL - Action: BUY - Confidence: 60.0% - ... -šŸ“Š Recommendation #2 - Symbol: AAPL - Action: BUY - Confidence: 52.0% - ... - -šŸ¦ [main] memory -🧠 Memory Status -āœ… Memory validation: ACTIVE -šŸ›”ļø Security monitoring: ENABLED -šŸ“ Audit trail: ENABLED -🌿 Current branch: main -šŸ“Š Total commits: 15 -šŸ’” Recommendations: 2 +šŸ¤– AI Analysis: Apple shows robust growth potential with +upcoming product launches and services expansion. The current +valuation offers an attractive entry point for long-term investors. ``` -## Command Line Options - -```bash -cargo run -- [OPTIONS] - -Commands: - advise Start interactive advisory session - visualize Visualize memory evolution - attack Run attack simulations - benchmark Run performance benchmarks - memory Git memory operations - examples Show integration examples - audit Audit memory for compliance - -Options: - -s, --storage Path to store agent memory [default: ./advisor_memory/data] - -h, --help Print help +Without OpenAI API (fallback): +``` +šŸ“Š Recommendation Generated +Symbol: AAPL +Action: HOLD +Confidence: 52.0% +Reasoning: AAPL shows strong fundamentals with a P/E ratio of 28.4... ``` -## Architecture +### 5. Memory Storage +Every recommendation is stored with: +- Full audit trail +- Validation results +- Cross-reference hashes +- Git commit for time-travel queries -### Memory System -- **ProllyTree Storage**: Git-like versioned storage for all data -- **Multi-table Schema**: Separate tables for recommendations, market data, client profiles -- **Cross-validation**: Data integrity through hash validation and cross-references -- **Temporal Queries**: Query data as it existed at any commit or branch +## Key Commands -### Security Features -- **Input Sanitization**: Prevents SQL injection and other attacks -- **Anomaly Detection**: Monitors for suspicious patterns in data -- **Attack Simulation**: Built-in testing for security vulnerabilities -- **Audit Logging**: Complete trail of all operations +### Recommendations & Profiles +- `recommend ` - Get AI recommendation with market analysis +- `profile` - View/edit client profile +- `risk ` - Set risk tolerance -### AI Integration -- **Market Analysis**: Real-time analysis of market conditions -- **Risk Assessment**: Adapts to client risk tolerance -- **Reasoning Generation**: Explains the logic behind recommendations -- **Multi-source Validation**: Cross-checks data from multiple financial sources +### Branch Management +- `branch ` - Create strategy branch +- `switch ` - Change branches +- `visualize` - Show branch tree with commits -## Advanced Usage +### Time Travel +- `history` - Recent recommendations +- `history ` - View at specific commit +- `history --branch ` - Compare branches -### Branch Management +### Security & Audit +- `memory` - System status and validation +- `audit` - Complete operation history +- `test-inject ` - Test security (try SQL injection!) -Create and manage branches for different scenarios: +## Example Workflow ```bash -# Create and switch to a new branch -šŸ¦ [main] branch conservative-strategy -🌿 Creating memory branch: conservative-strategy -āœ… Branch 'conservative-strategy' created successfully -šŸ”€ Switched to branch 'conservative-strategy' - -šŸ¦ [conservative-strategy] risk conservative -āœ… Risk tolerance set to: Conservative - -šŸ¦ [conservative-strategy] recommend MSFT -# Generate recommendations for conservative strategy - -# List all available branches -šŸ¦ [conservative-strategy] list-branches -🌳 Available Branches -━━━━━━━━━━━━━━━━━━━━━━━━━ - ā—‹ main - ā— conservative-strategy (current) - -# Switch back to main branch -šŸ¦ [conservative-strategy] switch main -šŸ”€ Switching to branch: main -āœ… Switched to branch 'main' - -šŸ¦ [main] history --branch conservative-strategy -# Compare recommendations from different branch - -# Git-style branch listing -šŸ¦ [main] branch-info -* main - conservative-strategy +# Start with conservative strategy +šŸ¦ [main] risk conservative +šŸ¦ [main] recommend MSFT +šŸ“Š Action: HOLD, Confidence: 45% (conservative approach) + +# Try aggressive strategy on new branch +šŸ¦ [main] branch aggressive-growth +šŸ¦ [aggressive-growth] risk aggressive +šŸ¦ [aggressive-growth] recommend MSFT +šŸ“Š Action: BUY, Confidence: 78% (growth opportunity identified) + +# Compare branches +šŸ¦ [aggressive-growth] visualize +ā”œā”€ā”€ ā—† main (conservative MSFT: HOLD) +└── ā— aggressive-growth (current) + └── Aggressive MSFT: BUY recommendation + +# Time travel to see past recommendations +šŸ¦ [aggressive-growth] switch main +šŸ¦ [main] history abc1234 +šŸ“Š Viewing recommendations as of 2024-01-15... ``` -#### Branch Validation - -The system prevents common branching mistakes: +## Architecture Highlights -```bash -# Try to create existing branch -šŸ¦ [main] branch main -āš ļø Branch 'main' already exists! -šŸ’” Use 'switch main' to switch to the existing branch - -# Try to switch to non-existent branch -šŸ¦ [main] switch nonexistent -āŒ Branch 'nonexistent' does not exist! -šŸ’” Use 'branch nonexistent' to create a new branch -``` +- **ProllyTree Storage**: Content-addressed storage with Merkle proofs +- **Git Integration**: Native git operations for versioning +- **Multi-Table Schema**: Separate tables for recommendations, market data, profiles +- **Async Processing**: Concurrent data fetching and validation +- **Security Layers**: Input sanitization, anomaly detection, audit logging -### Temporal Analysis - -Analyze how recommendations changed over time: +## Advanced Options ```bash -# Get commit history -šŸ¦> memory - -# Query specific time points -šŸ¦> history abc1234 # Recommendations at specific commit -šŸ¦> history def5678 # Compare with different commit -``` +# Command line interface +cargo run -- [OPTIONS] -### Security Testing +Commands: + advise Interactive advisory session + visualize Memory tree visualization + attack Security testing suite + benchmark Performance measurements + memory Git operations interface + audit Compliance reporting -Test the system's security: +Options: + -s, --storage Storage directory [default: ./advisor_memory/data] + -v, --verbose Show detailed operations +``` -```bash -šŸ¦> test-inject "'; DROP TABLE recommendations; --" -šŸ›”ļø Security Alert: Potential SQL injection detected and blocked +## Technical Notes -šŸ¦> test-inject "unusual market manipulation data" -🚨 Anomaly detected in data pattern -``` +### Data Simulation +The system uses realistic market data simulation: +- Popular stocks (AAPL, MSFT, GOOGL, etc.) have accurate characteristics +- Prices vary ±1% between sources to simulate real discrepancies +- Network latency is simulated based on API tier +- All data includes proper timestamps and source attribution -## Troubleshooting## License +### Without OpenAI API +The system gracefully falls back to rule-based analysis: +- Uses P/E ratios and sector analysis +- Adjusts confidence based on risk tolerance +- Provides detailed reasoning without AI enhancement +- All core features remain functional -This example is part of the ProllyTree project and follows the same license terms. +### Security Features +- **Input Validation**: Regex patterns block SQL injection +- **Anomaly Detection**: Statistical analysis of data patterns +- **Rate Limiting**: Prevents abuse and DOS attempts +- **Audit Trail**: Cryptographically signed operation log -## Contributing +## License -Contributions are welcome! Please see the main project's contributing guidelines. +Part of the ProllyTree project. See main repository for license terms. ## Disclaimer -This is a demonstration system for educational purposes. Do not use for actual financial decisions without proper validation and compliance review. \ No newline at end of file +This is a demonstration system for educational purposes. Not for actual investment decisions. \ No newline at end of file diff --git a/examples/financial_advisor/src/advisor/interactive.rs b/examples/financial_advisor/src/advisor/interactive.rs index 7e58f8b..dcfca17 100644 --- a/examples/financial_advisor/src/advisor/interactive.rs +++ b/examples/financial_advisor/src/advisor/interactive.rs @@ -130,7 +130,6 @@ impl<'a> InteractiveSession<'a> { "branch ".cyan() ); println!(" {} - Switch to existing branch", "switch ".cyan()); - println!(" {} - List all branches", "list-branches".cyan()); println!(" {} - Show this help", "help".cyan()); println!(" {} - Exit", "exit".cyan()); println!(); @@ -283,9 +282,6 @@ impl<'a> InteractiveSession<'a> { println!("Result: '{actual}'"); } - "list-branches" | "lb" => { - self.list_branches(); - } "exit" | "quit" | "q" => { return Ok(false); @@ -339,7 +335,6 @@ impl<'a> InteractiveSession<'a> { "branch ".cyan() ); println!(" {} - Switch to existing branch", "switch ".cyan()); - println!(" {} - List all branches", "list-branches".cyan()); println!(" {} - Show this help", "help".cyan()); println!(" {} - Exit", "exit".cyan()); println!(); @@ -398,6 +393,21 @@ impl<'a> InteractiveSession<'a> { "Confidence".cyan(), recommendation.confidence * 100.0 ); + + // Show analysis mode prominently + let mode_display = match recommendation.analysis_mode { + crate::advisor::AnalysisMode::AIPowered => "šŸ¤– AI-Powered Analysis".bright_green(), + crate::advisor::AnalysisMode::RuleBased => "šŸ“Š Rule-Based Analysis".bright_yellow(), + }; + println!("{}: {}", "Mode".cyan(), mode_display); + + // Show data source information + let data_source_display = match recommendation.data_source { + crate::advisor::DataSource::RealStockData => "šŸ“ˆ Real Market Data".bright_blue(), + crate::advisor::DataSource::SimulatedData => "šŸŽ² Simulated Data".bright_magenta(), + }; + println!("{}: {}", "Data Source".cyan(), data_source_display); + println!("{}: {}", "Client".cyan(), recommendation.client_id); println!(); println!("{}", "Reasoning:".yellow()); @@ -575,6 +585,21 @@ impl<'a> InteractiveSession<'a> { rec.recommendation_type.as_str().bold() ); println!(" {}: {:.1}%", "Confidence".cyan(), rec.confidence * 100.0); + + // Show analysis mode with clear indicator + let mode_display = match rec.analysis_mode { + crate::advisor::AnalysisMode::AIPowered => "šŸ¤– AI-Powered".bright_green(), + crate::advisor::AnalysisMode::RuleBased => "šŸ“Š Rule-Based".bright_yellow(), + }; + println!(" {}: {}", "Analysis Mode".cyan(), mode_display); + + // Show data source + let data_source_display = match rec.data_source { + crate::advisor::DataSource::RealStockData => "šŸ“ˆ Real Data".bright_blue(), + crate::advisor::DataSource::SimulatedData => "šŸŽ² Simulated".bright_magenta(), + }; + println!(" {}: {}", "Data Source".cyan(), data_source_display); + println!(" {}: {}", "Client ID".cyan(), rec.client_id); println!( " {}: {}", @@ -1012,29 +1037,4 @@ impl<'a> InteractiveSession<'a> { } } - fn list_branches(&self) { - println!("{}", "🌳 Available Branches".green().bold()); - println!("{}", "━".repeat(25).dimmed()); - - match self.advisor.list_branches() { - Ok(branches) => { - let current_branch = self.advisor.get_actual_current_branch(); - - if branches.is_empty() { - println!("{} No branches found", "ā„¹ļø".blue()); - } else { - for branch in branches { - if branch == current_branch { - println!(" {} {} (current)", "ā—".green(), branch.bold()); - } else { - println!(" {} {}", "ā—‹".dimmed(), branch); - } - } - } - } - Err(e) => { - println!("{} Failed to list branches: {}", "āŒ".red(), e); - } - } - } } diff --git a/examples/financial_advisor/src/advisor/mod.rs b/examples/financial_advisor/src/advisor/mod.rs index 23717b7..9e4baca 100644 --- a/examples/financial_advisor/src/advisor/mod.rs +++ b/examples/financial_advisor/src/advisor/mod.rs @@ -41,13 +41,29 @@ pub enum RiskTolerance { Aggressive, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DataSource { + #[serde(rename = "Real Stock Data")] + RealStockData, + #[serde(rename = "Simulated Data")] + SimulatedData, +} + struct StockData { price: f64, volume: u64, pe_ratio: f64, market_cap: u64, sector: String, + data_source: DataSource, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AnalysisMode { + #[serde(rename = "AI-Powered")] + AIPowered, + #[serde(rename = "Rule-Based")] + RuleBased, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -61,6 +77,8 @@ pub struct Recommendation { pub timestamp: DateTime, pub validation_result: ValidationResult, pub memory_version: String, + pub analysis_mode: AnalysisMode, + pub data_source: DataSource, } pub struct FinancialAdvisor { @@ -122,17 +140,21 @@ impl FinancialAdvisor { self.security_monitor.check_for_anomalies(&market_data)?; // Step 4: Generate recommendation with full context + let stock_data = self.get_realistic_stock_data(symbol); let mut recommendation = self .recommendation_engine .generate(symbol, client_profile, &market_data, &self.memory_store) .await?; + + // Set the data source + recommendation.data_source = stock_data.data_source; // Step 4.5: Enhance reasoning with AI analysis if self.verbose { println!("🧠 Generating AI-powered analysis..."); } - let ai_reasoning = self + let (ai_reasoning, analysis_mode) = self .generate_ai_reasoning( symbol, &recommendation.recommendation_type, @@ -142,11 +164,20 @@ impl FinancialAdvisor { ) .await?; - // Combine traditional and AI reasoning - recommendation.reasoning = format!( - "{}\n\nšŸ¤– AI Analysis: {}", - recommendation.reasoning, ai_reasoning - ); + // Set the analysis mode + recommendation.analysis_mode = analysis_mode; + + // Combine traditional and AI reasoning with mode indicator + recommendation.reasoning = match recommendation.analysis_mode { + AnalysisMode::AIPowered => format!( + "{}\n\nšŸ¤– AI Analysis: {}", + recommendation.reasoning, ai_reasoning + ), + AnalysisMode::RuleBased => format!( + "{}\n\nšŸ“Š Rule-Based Analysis: {}", + recommendation.reasoning, ai_reasoning + ), + }; // Step 5: Store recommendation with audit trail self.store_recommendation(&recommendation, notes).await?; @@ -427,7 +458,7 @@ impl FinancialAdvisor { recommendation_type: &RecommendationType, market_data: &serde_json::Value, client: &ClientProfile, - ) -> Result { + ) -> Result<(String, AnalysisMode)> { // Build context from market data let price = market_data["price"].as_f64().unwrap_or(0.0); let pe_ratio = market_data["pe_ratio"].as_f64().unwrap_or(0.0); @@ -503,15 +534,18 @@ impl FinancialAdvisor { .and_then(|content| content.as_str()) .unwrap_or("AI analysis unavailable at this time."); - Ok(content.to_string()) + Ok((content.to_string(), AnalysisMode::AIPowered)) } _ => { // Fallback to rule-based reasoning if OpenAI fails - Ok(self.generate_fallback_reasoning( - symbol, - recommendation_type, - market_data, - client, + Ok(( + self.generate_fallback_reasoning( + symbol, + recommendation_type, + market_data, + client, + ), + AnalysisMode::RuleBased, )) } } @@ -649,6 +683,7 @@ impl FinancialAdvisor { pe_ratio: 28.5, market_cap: 2_800_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "GOOGL" => StockData { price: 142.56, @@ -656,6 +691,7 @@ impl FinancialAdvisor { pe_ratio: 24.8, market_cap: 1_750_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "MSFT" => StockData { price: 415.26, @@ -663,6 +699,7 @@ impl FinancialAdvisor { pe_ratio: 32.1, market_cap: 3_100_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "AMZN" => StockData { price: 155.89, @@ -670,6 +707,7 @@ impl FinancialAdvisor { pe_ratio: 45.2, market_cap: 1_650_000_000_000, sector: "Consumer Discretionary".to_string(), + data_source: DataSource::RealStockData, }, "TSLA" => StockData { price: 248.42, @@ -677,6 +715,7 @@ impl FinancialAdvisor { pe_ratio: 65.4, market_cap: 780_000_000_000, sector: "Automotive".to_string(), + data_source: DataSource::RealStockData, }, "META" => StockData { price: 501.34, @@ -684,6 +723,7 @@ impl FinancialAdvisor { pe_ratio: 22.7, market_cap: 1_250_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "NVDA" => StockData { price: 875.28, @@ -691,6 +731,7 @@ impl FinancialAdvisor { pe_ratio: 55.8, market_cap: 2_150_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "NFLX" => StockData { price: 425.67, @@ -698,6 +739,7 @@ impl FinancialAdvisor { pe_ratio: 34.5, market_cap: 185_000_000_000, sector: "Communication Services".to_string(), + data_source: DataSource::RealStockData, }, "JPM" => StockData { price: 195.43, @@ -705,6 +747,7 @@ impl FinancialAdvisor { pe_ratio: 12.8, market_cap: 570_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "JNJ" => StockData { price: 156.78, @@ -712,6 +755,7 @@ impl FinancialAdvisor { pe_ratio: 15.2, market_cap: 410_000_000_000, sector: "Healthcare".to_string(), + data_source: DataSource::RealStockData, }, "V" => StockData { price: 278.94, @@ -719,6 +763,7 @@ impl FinancialAdvisor { pe_ratio: 31.4, market_cap: 590_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "PG" => StockData { price: 165.23, @@ -726,6 +771,7 @@ impl FinancialAdvisor { pe_ratio: 26.7, market_cap: 395_000_000_000, sector: "Consumer Staples".to_string(), + data_source: DataSource::RealStockData, }, "UNH" => StockData { price: 512.87, @@ -733,6 +779,7 @@ impl FinancialAdvisor { pe_ratio: 23.9, market_cap: 485_000_000_000, sector: "Healthcare".to_string(), + data_source: DataSource::RealStockData, }, "HD" => StockData { price: 345.67, @@ -740,6 +787,7 @@ impl FinancialAdvisor { pe_ratio: 22.1, market_cap: 350_000_000_000, sector: "Consumer Discretionary".to_string(), + data_source: DataSource::RealStockData, }, "MA" => StockData { price: 456.23, @@ -747,6 +795,7 @@ impl FinancialAdvisor { pe_ratio: 33.6, market_cap: 425_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "DIS" => StockData { price: 112.45, @@ -754,6 +803,7 @@ impl FinancialAdvisor { pe_ratio: 38.7, market_cap: 205_000_000_000, sector: "Communication Services".to_string(), + data_source: DataSource::RealStockData, }, "PYPL" => StockData { price: 78.94, @@ -761,6 +811,7 @@ impl FinancialAdvisor { pe_ratio: 18.9, market_cap: 85_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "ADBE" => StockData { price: 567.23, @@ -768,6 +819,7 @@ impl FinancialAdvisor { pe_ratio: 41.2, market_cap: 255_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "CRM" => StockData { price: 234.56, @@ -775,6 +827,7 @@ impl FinancialAdvisor { pe_ratio: 48.3, market_cap: 225_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "INTC" => StockData { price: 24.67, @@ -782,6 +835,7 @@ impl FinancialAdvisor { pe_ratio: 14.8, market_cap: 105_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, // Default case for unknown symbols _ => StockData { @@ -790,6 +844,7 @@ impl FinancialAdvisor { pe_ratio: 20.0 + (symbol.len() as f64 * 0.8), market_cap: 50_000_000_000 + (symbol.len() as u64 * 2_000_000_000), sector: "Mixed".to_string(), + data_source: DataSource::SimulatedData, }, } } diff --git a/examples/financial_advisor/src/advisor/recommendations.rs b/examples/financial_advisor/src/advisor/recommendations.rs index 140ed68..9460741 100644 --- a/examples/financial_advisor/src/advisor/recommendations.rs +++ b/examples/financial_advisor/src/advisor/recommendations.rs @@ -4,7 +4,7 @@ use anyhow::Result; use chrono::Utc; use uuid::Uuid; -use super::{ClientProfile, Recommendation, RecommendationType, RiskTolerance}; +use super::{AnalysisMode, ClientProfile, DataSource, Recommendation, RecommendationType, RiskTolerance}; use crate::memory::{MemoryStore, ValidatedMemory}; use crate::validation::ValidationResult; @@ -53,6 +53,8 @@ impl RecommendationEngine { issues: vec![], }, memory_version: format!("v-{}", Utc::now().timestamp()), + analysis_mode: AnalysisMode::RuleBased, // Default, will be updated by AI if available + data_source: DataSource::SimulatedData, // Default, will be updated based on symbol }) } @@ -176,6 +178,7 @@ impl RecommendationEngine { pe_ratio: 28.5, market_cap: 2_800_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "GOOGL" => super::StockData { price: 142.56, @@ -183,6 +186,7 @@ impl RecommendationEngine { pe_ratio: 24.8, market_cap: 1_750_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "MSFT" => super::StockData { price: 415.26, @@ -190,6 +194,7 @@ impl RecommendationEngine { pe_ratio: 32.1, market_cap: 3_100_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "AMZN" => super::StockData { price: 155.89, @@ -197,6 +202,7 @@ impl RecommendationEngine { pe_ratio: 45.2, market_cap: 1_650_000_000_000, sector: "Consumer Discretionary".to_string(), + data_source: DataSource::RealStockData, }, "TSLA" => super::StockData { price: 248.42, @@ -204,6 +210,7 @@ impl RecommendationEngine { pe_ratio: 65.4, market_cap: 780_000_000_000, sector: "Automotive".to_string(), + data_source: DataSource::RealStockData, }, "META" => super::StockData { price: 501.34, @@ -211,6 +218,7 @@ impl RecommendationEngine { pe_ratio: 22.7, market_cap: 1_250_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "NVDA" => super::StockData { price: 875.28, @@ -218,6 +226,7 @@ impl RecommendationEngine { pe_ratio: 55.8, market_cap: 2_150_000_000_000, sector: "Technology".to_string(), + data_source: DataSource::RealStockData, }, "JPM" => super::StockData { price: 195.43, @@ -225,6 +234,7 @@ impl RecommendationEngine { pe_ratio: 12.8, market_cap: 570_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "JNJ" => super::StockData { price: 156.78, @@ -232,6 +242,7 @@ impl RecommendationEngine { pe_ratio: 15.2, market_cap: 410_000_000_000, sector: "Healthcare".to_string(), + data_source: DataSource::RealStockData, }, "V" => super::StockData { price: 278.94, @@ -239,6 +250,7 @@ impl RecommendationEngine { pe_ratio: 31.4, market_cap: 590_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, "PYPL" => super::StockData { price: 78.94, @@ -246,6 +258,7 @@ impl RecommendationEngine { pe_ratio: 18.9, market_cap: 85_000_000_000, sector: "Financial Services".to_string(), + data_source: DataSource::RealStockData, }, // Default case for unknown symbols _ => super::StockData { @@ -254,6 +267,7 @@ impl RecommendationEngine { pe_ratio: 20.0 + (symbol.len() as f64 * 0.8), market_cap: 50_000_000_000 + (symbol.len() as u64 * 2_000_000_000), sector: "Mixed".to_string(), + data_source: DataSource::SimulatedData, }, } } From 69228d76a61aeaaa90bc67097a49d4fa8b68ba7f Mon Sep 17 00:00:00 2001 From: Feng Zhang Date: Sat, 26 Jul 2025 08:14:03 -0700 Subject: [PATCH 3/3] add debug option to recommend --- .../src/advisor/interactive.rs | 128 +++++++++++++----- examples/financial_advisor/src/advisor/mod.rs | 45 +++++- .../src/advisor/recommendations.rs | 4 +- 3 files changed, 138 insertions(+), 39 deletions(-) diff --git a/examples/financial_advisor/src/advisor/interactive.rs b/examples/financial_advisor/src/advisor/interactive.rs index dcfca17..6393549 100644 --- a/examples/financial_advisor/src/advisor/interactive.rs +++ b/examples/financial_advisor/src/advisor/interactive.rs @@ -102,7 +102,7 @@ impl<'a> InteractiveSession<'a> { println!("{}", "Available commands:".yellow()); println!( " {} - Get recommendation for a stock symbol", - "recommend [notes]".cyan() + "recommend [--debug] [notes]".cyan() ); println!(" {} - Show client profile", "profile".cyan()); println!( @@ -149,17 +149,32 @@ impl<'a> InteractiveSession<'a> { "recommend" | "r" => { if parts.len() < 2 { - println!("{} Usage: recommend [notes]", "ā“".yellow()); + println!( + "{} Usage: recommend [--debug] [notes]", + "ā“".yellow() + ); return Ok(true); } let symbol = parts[1].to_uppercase(); - let notes = if parts.len() > 2 { - Some(parts[2..].join(" ")) + + // Check for debug flag and extract notes + let mut debug_mode = false; + let mut remaining_parts = &parts[2..]; + + if !remaining_parts.is_empty() && remaining_parts[0] == "--debug" { + debug_mode = true; + remaining_parts = &remaining_parts[1..]; + } + + let notes = if !remaining_parts.is_empty() { + Some(remaining_parts.join(" ")) } else { None }; - self.handle_recommendation(&symbol, client, notes).await?; + + self.handle_recommendation_with_debug(&symbol, client, notes, debug_mode) + .await?; } "profile" | "p" => { @@ -282,7 +297,6 @@ impl<'a> InteractiveSession<'a> { println!("Result: '{actual}'"); } - "exit" | "quit" | "q" => { return Ok(false); } @@ -307,7 +321,7 @@ impl<'a> InteractiveSession<'a> { println!("{}", "Available commands:".yellow()); println!( " {} - Get recommendation for a stock symbol", - "recommend [notes]".cyan() + "recommend [--debug] [notes]".cyan() ); println!(" {} - Show client profile", "profile".cyan()); println!( @@ -370,14 +384,30 @@ impl<'a> InteractiveSession<'a> { symbol: &str, client: &ClientProfile, notes: Option, + ) -> Result<()> { + self.handle_recommendation_with_debug(symbol, client, notes, false) + .await + } + + async fn handle_recommendation_with_debug( + &mut self, + symbol: &str, + client: &ClientProfile, + notes: Option, + debug_mode: bool, ) -> Result<()> { println!( - "{} Generating recommendation for {}...", + "{} Generating recommendation for {}{}...", "šŸ”".yellow(), - symbol + symbol, + if debug_mode { " (debug mode)" } else { "" } ); - match self.advisor.get_recommendation(symbol, client, notes).await { + match self + .advisor + .get_recommendation_with_debug(symbol, client, notes, debug_mode) + .await + { Ok(recommendation) => { println!(); println!("{}", "šŸ“Š Recommendation Generated".green().bold()); @@ -393,21 +423,29 @@ impl<'a> InteractiveSession<'a> { "Confidence".cyan(), recommendation.confidence * 100.0 ); - + // Show analysis mode prominently let mode_display = match recommendation.analysis_mode { - crate::advisor::AnalysisMode::AIPowered => "šŸ¤– AI-Powered Analysis".bright_green(), - crate::advisor::AnalysisMode::RuleBased => "šŸ“Š Rule-Based Analysis".bright_yellow(), + crate::advisor::AnalysisMode::AIPowered => { + "šŸ¤– AI-Powered Analysis".bright_green() + } + crate::advisor::AnalysisMode::RuleBased => { + "šŸ“Š Rule-Based Analysis".bright_yellow() + } }; println!("{}: {}", "Mode".cyan(), mode_display); - + // Show data source information let data_source_display = match recommendation.data_source { - crate::advisor::DataSource::RealStockData => "šŸ“ˆ Real Market Data".bright_blue(), - crate::advisor::DataSource::SimulatedData => "šŸŽ² Simulated Data".bright_magenta(), + crate::advisor::DataSource::RealStockData => { + "šŸ“ˆ Real Market Data".bright_blue() + } + crate::advisor::DataSource::SimulatedData => { + "šŸŽ² Simulated Data".bright_magenta() + } }; println!("{}: {}", "Data Source".cyan(), data_source_display); - + println!("{}: {}", "Client".cyan(), recommendation.client_id); println!(); println!("{}", "Reasoning:".yellow()); @@ -585,21 +623,21 @@ impl<'a> InteractiveSession<'a> { rec.recommendation_type.as_str().bold() ); println!(" {}: {:.1}%", "Confidence".cyan(), rec.confidence * 100.0); - + // Show analysis mode with clear indicator let mode_display = match rec.analysis_mode { crate::advisor::AnalysisMode::AIPowered => "šŸ¤– AI-Powered".bright_green(), crate::advisor::AnalysisMode::RuleBased => "šŸ“Š Rule-Based".bright_yellow(), }; println!(" {}: {}", "Analysis Mode".cyan(), mode_display); - + // Show data source let data_source_display = match rec.data_source { crate::advisor::DataSource::RealStockData => "šŸ“ˆ Real Data".bright_blue(), crate::advisor::DataSource::SimulatedData => "šŸŽ² Simulated".bright_magenta(), }; println!(" {}: {}", "Data Source".cyan(), data_source_display); - + println!(" {}: {}", "Client ID".cyan(), rec.client_id); println!( " {}: {}", @@ -872,7 +910,7 @@ impl<'a> InteractiveSession<'a> { // Display branches in a tree structure println!("{}", "Repository Branches:".yellow()); println!(); - + // Sort branches to ensure main/master comes first let mut sorted_branches = branches.clone(); sorted_branches.sort_by(|a, b| { @@ -890,32 +928,42 @@ impl<'a> InteractiveSession<'a> { let is_last = i == sorted_branches.len() - 1; let connector = if is_last { "└──" } else { "ā”œā”€ā”€" }; let vertical_line = if is_last { " " } else { "│ " }; - + // Determine branch color and symbol - let (branch_display, symbol) = if branch == ¤t_branch { + let (branch_display, symbol) = if branch == current_branch { (branch.green().bold(), "ā—") // Current branch } else if branch == "main" || branch == "master" { (branch.blue().bold(), "ā—†") // Main branch } else { (branch.normal(), "ā—‹") // Other branches }; - + // Display branch with appropriate formatting - println!("{} {} {} {}", - connector.dimmed(), + println!( + "{} {} {} {}", + connector.dimmed(), symbol, branch_display, - if branch == ¤t_branch { "(current)".dimmed() } else { "".normal() } + if branch == current_branch { + "(current)".dimmed() + } else { + "".normal() + } ); - + // Show some recent commits for the current branch - if branch == ¤t_branch { + if branch == current_branch { if let Ok(history) = self.advisor.get_memory_history(Some(3)).await { for (j, commit) in history.iter().enumerate() { let is_last_commit = j == history.len() - 1 || j == 2; - let commit_connector = if is_last_commit { "└──" } else { "ā”œā”€ā”€" }; - - println!("{} {} {} {}", + let commit_connector = if is_last_commit { + "└──" + } else { + "ā”œā”€ā”€" + }; + + println!( + "{} {} {} {}", vertical_line.dimmed(), commit_connector.dimmed(), commit.hash[..8].yellow(), @@ -932,9 +980,18 @@ impl<'a> InteractiveSession<'a> { println!(" {} Main branch", "ā—†".blue()); println!(" {} Other branches", "ā—‹".normal()); println!(); - println!("{} All branches share the same versioned memory system", "šŸ’¾".cyan()); - println!("{} Switch branches with: switch ", "šŸ”€".yellow()); - println!("{} Create new branch with: branch ", "🌿".green()); + println!( + "{} All branches share the same versioned memory system", + "šŸ’¾".cyan() + ); + println!( + "{} Switch branches with: switch ", + "šŸ”€".yellow() + ); + println!( + "{} Create new branch with: branch ", + "🌿".green() + ); Ok(()) } @@ -1036,5 +1093,4 @@ impl<'a> InteractiveSession<'a> { ); } } - } diff --git a/examples/financial_advisor/src/advisor/mod.rs b/examples/financial_advisor/src/advisor/mod.rs index 9e4baca..8bfa99e 100644 --- a/examples/financial_advisor/src/advisor/mod.rs +++ b/examples/financial_advisor/src/advisor/mod.rs @@ -2,6 +2,7 @@ use anyhow::Result; use chrono::{DateTime, Utc}; +use colored::Colorize; // OpenAI integration for AI-powered recommendations use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -125,6 +126,17 @@ impl FinancialAdvisor { symbol: &str, client_profile: &ClientProfile, notes: Option, + ) -> Result { + self.get_recommendation_with_debug(symbol, client_profile, notes, false) + .await + } + + pub async fn get_recommendation_with_debug( + &mut self, + symbol: &str, + client_profile: &ClientProfile, + notes: Option, + debug_mode: bool, ) -> Result { if self.verbose { println!("šŸ” Fetching market data for {symbol}..."); @@ -145,7 +157,7 @@ impl FinancialAdvisor { .recommendation_engine .generate(symbol, client_profile, &market_data, &self.memory_store) .await?; - + // Set the data source recommendation.data_source = stock_data.data_source; @@ -155,12 +167,13 @@ impl FinancialAdvisor { } let (ai_reasoning, analysis_mode) = self - .generate_ai_reasoning( + .generate_ai_reasoning_with_debug( symbol, &recommendation.recommendation_type, &serde_json::from_str::(&market_data.content) .unwrap_or(serde_json::json!({})), client_profile, + debug_mode, ) .await?; @@ -458,6 +471,24 @@ impl FinancialAdvisor { recommendation_type: &RecommendationType, market_data: &serde_json::Value, client: &ClientProfile, + ) -> Result<(String, AnalysisMode)> { + self.generate_ai_reasoning_with_debug( + symbol, + recommendation_type, + market_data, + client, + false, + ) + .await + } + + async fn generate_ai_reasoning_with_debug( + &self, + symbol: &str, + recommendation_type: &RecommendationType, + market_data: &serde_json::Value, + client: &ClientProfile, + debug_mode: bool, ) -> Result<(String, AnalysisMode)> { // Build context from market data let price = market_data["price"].as_f64().unwrap_or(0.0); @@ -501,6 +532,16 @@ impl FinancialAdvisor { recommendation_type = recommendation_type ); + // Print prompt if debug mode is enabled + if debug_mode { + println!(); + println!("{}", "šŸ” OpenAI Prompt Debug".bright_cyan().bold()); + println!("{}", "━".repeat(60).dimmed()); + println!("{prompt}"); + println!("{}", "━".repeat(60).dimmed()); + println!(); + } + // Make OpenAI API call let openai_request = serde_json::json!({ "model": "gpt-3.5-turbo", diff --git a/examples/financial_advisor/src/advisor/recommendations.rs b/examples/financial_advisor/src/advisor/recommendations.rs index 9460741..039eecd 100644 --- a/examples/financial_advisor/src/advisor/recommendations.rs +++ b/examples/financial_advisor/src/advisor/recommendations.rs @@ -4,7 +4,9 @@ use anyhow::Result; use chrono::Utc; use uuid::Uuid; -use super::{AnalysisMode, ClientProfile, DataSource, Recommendation, RecommendationType, RiskTolerance}; +use super::{ + AnalysisMode, ClientProfile, DataSource, Recommendation, RecommendationType, RiskTolerance, +}; use crate::memory::{MemoryStore, ValidatedMemory}; use crate::validation::ValidationResult;