diff --git a/examples/financial_advisor/Cargo.toml b/examples/financial_advisor/Cargo.toml index 8f1c768..453c4fa 100644 --- a/examples/financial_advisor/Cargo.toml +++ b/examples/financial_advisor/Cargo.toml @@ -8,29 +8,23 @@ prollytree = { path = "../..", features = ["sql", "git"] } rig-core = "0.2.1" hex = "0.4" tokio = { version = "1.0", features = ["full"] } -async-trait = "0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" uuid = { version = "1.0", features = ["v4", "serde"] } chrono = { version = "0.4", features = ["serde"] } anyhow = "1.0" -thiserror = "1.0" clap = { version = "4.0", features = ["derive"] } colored = "2.0" indicatif = "0.17" -tabled = "0.16" sha2 = "0.10" gluesql-core = "0.15" -futures = "0.3" dotenv = "0.15" -reqwest = { version = "0.12", features = ["json"] } -tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -[dev-dependencies] -criterion = "0.5" -tempfile = "3.0" - [[bin]] name = "financial-advisor" path = "src/main.rs" + +[[example]] +name = "enhanced_demo" +path = "examples/demo.rs" diff --git a/examples/financial_advisor/README.md b/examples/financial_advisor/README.md index 73f7c54..248db04 100644 --- a/examples/financial_advisor/README.md +++ b/examples/financial_advisor/README.md @@ -1,205 +1,188 @@ -# Financial Advisory AI with Versioned Memory +# Financial Advisory AI with Agent Memory -A secure, auditable AI financial advisor demonstrating ProllyTree's versioned memory capabilities with git-like branching, temporal queries, and complete audit trails. +A comprehensive financial advisory system demonstrating ProllyTree's capabilities - from basic versioned memory to sophisticated agent memory with behavioral learning. -## Quick Start +## πŸš€ Two Implementation Levels -```bash -# 1. Setup storage with git -mkdir -p /tmp/advisor && cd /tmp/advisor && git init +### Original Financial Advisor +Secure, auditable AI financial advisor with git-like versioned memory and complete audit trails. -# 2. Set OpenAI API key (optional, for AI reasoning) -export OPENAI_API_KEY="your-api-key" - -# 3. Run the advisor -cargo run -- --storage /tmp/advisor/data advise -``` +**Key Features:** +- Git-like versioned storage with branches and time travel +- AI-powered recommendations with fallback to rules +- Security monitoring with injection detection +- Multi-source data validation -## Core Features +### Enhanced Financial Advisor +Advanced system with full agent memory integration, multi-step workflows, and behavioral learning. -- **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 +**Key Enhancements:** +- Complete 4-type agent memory system (semantic, episodic, procedural, short-term) +- Multi-step workflow orchestration with context +- Behavioral pattern learning and adaptation +- Deep personalization and compliance automation -## How Recommendations Work +--- -The `recommend ` command generates AI-powered investment advice through a sophisticated pipeline: +## πŸ“‹ Quick Start -### 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) +### Basic Financial Advisor -``` -🏦 [main] recommend AAPL -πŸ” Fetching market data for AAPL... -πŸ“‘ Validating data from 3 sources... +```bash +# Setup and run +mkdir -p /tmp/advisor && cd /tmp/advisor && git init +export OPENAI_API_KEY="your-api-key" # optional +cargo run -- --storage /tmp/advisor/data advise ``` -### 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" -} -``` +### Enhanced Financial Advisor -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: 85.0% -Reasoning: Strong fundamentals with P/E of 28.4... - -πŸ€– 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. -``` +```bash +# Interactive session +cargo run -- enhanced --verbose -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... +# Complete demonstration +cargo run --example enhanced_demo + +# With AI integration +OPENAI_API_KEY="your-key" cargo run -- enhanced --verbose ``` -### 5. Memory Storage -Every recommendation is stored with: -- Full audit trail -- Validation results -- Cross-reference hashes -- Git commit for time-travel queries +--- + +## 🎯 What Each Version Demonstrates -## Key Commands +### Original Version +- **Versioned Memory**: Git-like storage with temporal queries +- **Security First**: Input validation, anomaly detection, audit trails +- **AI Integration**: OpenAI-powered analysis with graceful fallbacks +- **Real-world Simulation**: Multi-source market data with realistic delays -### Recommendations & Profiles -- `recommend ` - Get AI recommendation with market analysis -- `profile` - View/edit client profile -- `risk ` - Set risk tolerance +### Enhanced Version +- **Agent Memory Architecture**: All four memory types working together +- **Complex Workflows**: Multi-step analysis with memory context +- **Behavioral Learning**: Client adaptation based on interaction history +- **Compliance Automation**: Procedural memory for regulatory rules +- **Personalization Engine**: Dynamic communication and risk adaptation -### Branch Management -- `branch ` - Create strategy branch -- `switch ` - Change branches -- `visualize` - Show branch tree with commits +--- -### Time Travel -- `history` - Recent recommendations -- `history ` - View at specific commit -- `history --branch ` - Compare branches +## πŸ“š Documentation -### Security & Audit -- `memory` - System status and validation -- `audit` - Complete operation history -- `test-inject ` - Test security (try SQL injection!) +### Detailed Guides +- **[Original Guide](docs/original.md)** - Complete walkthrough of the basic advisor +- **[Enhanced Guide](docs/enhanced.md)** - Advanced features and memory integration +- **[Architecture](docs/architecture.md)** - System design and evolution comparison +- **[Memory Architecture](MEMORY_ARCHITECTURE.md)** - Agent memory system diagrams -## Example Workflow +### Key Commands +**Original Advisor:** ```bash -# 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... +recommend AAPL # Get AI recommendation +profile # View/edit client profile +branch strategy1 # Create investment strategy branch +history # View recommendation history +audit # Complete operation audit ``` -## Architecture Highlights +**Enhanced Advisor:** +```bash +client sarah_retired # Set current client +recommend AAPL # Full workflow recommendation +research GOOGL # Deep market research +stats # Memory system statistics +optimize # Optimize memory performance +``` + +--- + +## πŸ› οΈ Technical Architecture + +### Memory System Evolution + +**Original:** Simple versioned storage +``` +User Input ──► Validation ──► AI Analysis ──► Git Storage +``` +**Enhanced:** Multi-type agent memory +``` +User Input ──► Workflow Engine ──► Analysis Modules ──► Agent Memory + β”‚ β”‚ β”‚ + Market Research β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” + Risk Analysis β”‚ Semantic β”‚ β”‚Episodic β”‚ + Compliance ──► β”‚ Facts β”‚ β”‚Episodes β”‚ + Personalization β”‚ Knowledge β”‚ β”‚Learning β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Procedural β”‚ β”‚Short- β”‚ + β”‚ Workflows β”‚ β”‚term β”‚ + β”‚ Rules β”‚ β”‚Context β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Technologies - **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 +- **Agent Memory System**: Advanced memory abstraction with 4 specialized types +- **Rig Framework**: AI-powered analysis and reasoning +- **Git Integration**: Native version control for auditability +- **Async Rust**: High-performance concurrent processing + +--- -## Advanced Options +## πŸ”§ Command Reference ```bash -# Command line interface -cargo run -- [OPTIONS] - -Commands: - advise Interactive advisory session - visualize Memory tree visualization - attack Security testing suite - benchmark Performance measurements - memory Git operations interface - audit Compliance reporting - -Options: - -s, --storage Storage directory [default: ./advisor_memory/data] - -v, --verbose Show detailed operations +# Build and test +cargo build --all +cargo test + +# Run different modes +cargo run -- advise # Original interactive mode +cargo run -- enhanced # Enhanced interactive mode +cargo run -- visualize # Memory visualization +cargo run -- benchmark # Performance testing + +# Examples +cargo run --example enhanced_demo # Complete enhanced demonstration +cargo run --example memory_demo # Memory system showcase + +# Options +--storage # Custom storage directory +--verbose # Detailed operation logging ``` -## Technical Notes +--- -### 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 +## ⚠️ Important Notes -### 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 +### Data & Security +- Uses realistic simulated market data (not real trading data) +- All security features are for demonstration purposes +- Complete audit trails for all operations +- Git-based storage ensures data integrity -### 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 +### AI Integration +- Works with or without OpenAI API key +- Graceful fallback to rule-based analysis +- Memory-contextual prompts in enhanced version +- Configurable AI model selection -## License +### Educational Value +This project demonstrates: +1. **Versioned Memory Systems** - Git-like storage with temporal queries +2. **Agent Memory Architecture** - Complete 4-type memory implementation +3. **Complex Workflow Orchestration** - Multi-step analysis with context +4. **Behavioral Learning** - Client adaptation and outcome-based improvement +5. **Security Best Practices** - Input validation and comprehensive auditing +6. **Real-World Application** - Practical financial advisory scenarios -Part of the ProllyTree project. See main repository for license terms. +--- + +## πŸ“„ License & Disclaimer -## Disclaimer +Part of the ProllyTree project. See main repository for license terms. -This is a demonstration system for educational purposes. Not for actual investment decisions. \ 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/docs/architecture.md b/examples/financial_advisor/docs/architecture.md new file mode 100644 index 0000000..6c15f25 --- /dev/null +++ b/examples/financial_advisor/docs/architecture.md @@ -0,0 +1,294 @@ +# AgentMemorySystem Architecture & Lifecycle + +## System Architecture Overview + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AgentMemorySystem β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Short-term β”‚ β”‚ Semantic β”‚ β”‚ Episodic β”‚ β”‚ +β”‚ β”‚ Working Memory │◄──►│ Knowledge Base │◄──►│ Experience β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Analysis β”‚ β”‚ β€’ Client Profilesβ”‚ β”‚ β€’ Interactions β”‚ β”‚ +β”‚ β”‚ Context β”‚ β”‚ β€’ Market Data β”‚ β”‚ β€’ Outcomes β”‚ β”‚ +β”‚ β”‚ β€’ Step Results β”‚ β”‚ β€’ Entity Facts β”‚ β”‚ β€’ Decisions β”‚ β”‚ +β”‚ β”‚ β€’ Active Threadsβ”‚ β”‚ β€’ Relationships β”‚ β”‚ β€’ Time-based β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β–² β–² β–² β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Procedural β”‚ β”‚ +β”‚ β”‚ Workflows β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β€’ Analysis Steps β”‚ β”‚ +β”‚ β”‚ β€’ Risk Proceduresβ”‚ β”‚ +β”‚ β”‚ β€’ Compliance β”‚ β”‚ +β”‚ β”‚ β€’ Learning Algos β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Persistent Storage β”‚ + β”‚ β”‚ + β”‚ β€’ Git-based Versioning β”‚ + β”‚ β€’ Content-addressed Storage β”‚ + β”‚ β€’ Merkle Tree Proofs β”‚ + β”‚ β€’ Cross-reference Tracking β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Memory Lifecycle Workflows + +### 1. Client Recommendation Workflow + +``` +START: Client requests recommendation for AAPL +β”‚ +β”œβ”€ STEP 1: Initialize Analysis Context +β”‚ β”‚ +β”‚ β”œβ”€ Short-term ──► Store: analysis_id, client_id, symbol, timestamp +β”‚ β”‚ +β”‚ └─ Semantic ───► Retrieve: client_profile, risk_tolerance, goals +β”‚ +β”œβ”€ STEP 2: Market Research Phase +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Query: market_entity_facts(AAPL) +β”‚ β”‚ └─ Returns: valuation_metrics, sector_info, analyst_ratings +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Search: similar_market_conditions(last_30_days) +β”‚ β”‚ └─ Returns: past_market_episodes, sentiment_patterns +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Execute: market_analysis_workflow +β”‚ β”‚ └─ Returns: analysis_steps, validation_rules +β”‚ β”‚ +β”‚ └─ Short-term ─► Update: market_analysis_results +β”‚ +β”œβ”€ STEP 3: Risk Assessment Phase +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Query: client_risk_history(client_id, 90_days) +β”‚ β”‚ └─ Returns: past_decisions, risk_outcomes, patterns +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Execute: risk_assessment_workflow +β”‚ β”‚ └─ Returns: risk_categories, scoring_algorithms +β”‚ β”‚ +β”‚ β”œβ”€ Short-term ─► Combine: market_data + risk_history β†’ risk_scores +β”‚ β”‚ +β”‚ └─ Semantic ───► Store: updated_client_risk_profile +β”‚ +β”œβ”€ STEP 4: Compliance Validation +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Retrieve: compliance_rules, regulatory_procedures +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Check: client_restrictions, investment_limits +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Review: past_compliance_issues(365_days) +β”‚ β”‚ +β”‚ └─ Short-term ─► Validate: recommendation_against_rules +β”‚ +β”œβ”€ STEP 5: Generate Recommendation +β”‚ β”‚ +β”‚ β”œβ”€ Short-term ─► Synthesize: all_analysis_results β†’ base_recommendation +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Apply: personalization_algorithms +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Enhance: client_communication_preferences +β”‚ β”‚ +β”‚ └─ Short-term ─► Finalize: detailed_recommendation +β”‚ +└─ STEP 6: Learning & Storage + β”‚ + β”œβ”€ Episodic ───► Store: recommendation_episode + β”‚ └─ Content: decision, reasoning, context, metadata + β”‚ + β”œβ”€ Semantic ───► Update: client_profile, market_knowledge + β”‚ + β”œβ”€ Procedural ─► Learn: workflow_optimization, success_patterns + β”‚ + └─ Short-term ─► Clear: temporary_analysis_context + +END: Return DetailedRecommendation to client +``` + +### 2. Learning from Outcomes Workflow + +``` +START: Client reports recommendation outcome +β”‚ +β”œβ”€ INPUT: recommendation_id, actual_return, satisfaction, followed_advice +β”‚ +β”œβ”€ STEP 1: Retrieve Original Context +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Find: original_recommendation_episode(recommendation_id) +β”‚ β”‚ └─ Returns: decision_context, reasoning, market_conditions +β”‚ β”‚ +β”‚ └─ Short-term ─► Load: analysis_context_for_learning +β”‚ +β”œβ”€ STEP 2: Outcome Analysis +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Execute: outcome_analysis_workflow +β”‚ β”‚ └─ Compare: predicted_vs_actual, success_factors +β”‚ β”‚ +β”‚ β”œβ”€ Short-term ─► Calculate: recommendation_accuracy, client_satisfaction_delta +β”‚ β”‚ +β”‚ └─ Semantic ───► Update: market_entity_performance_data +β”‚ +β”œβ”€ STEP 3: Pattern Recognition +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Query: similar_past_recommendations +β”‚ β”‚ └─ Find: patterns_in_successful_outcomes +β”‚ β”‚ +β”‚ β”œβ”€ Procedural ─► Update: success_pattern_algorithms +β”‚ β”‚ +β”‚ └─ Short-term ─► Identify: improvement_opportunities +β”‚ +β”œβ”€ STEP 4: Client Behavior Learning +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Store: client_decision_pattern +β”‚ β”‚ └─ Content: followed_advice, satisfaction, context +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Update: client_behavioral_profile +β”‚ β”‚ └─ Adjust: risk_tolerance_evolution, preferences +β”‚ β”‚ +β”‚ └─ Procedural ─► Adapt: personalization_strategies +β”‚ +└─ STEP 5: System Optimization + β”‚ + β”œβ”€ Procedural ─► Update: workflow_efficiency_metrics + β”‚ + β”œβ”€ Semantic ───► Refine: confidence_scoring_algorithms + β”‚ + └─ Episodic ───► Archive: complete_learning_episode + +END: System improved for future recommendations +``` + +### 3. Memory Optimization Lifecycle + +``` +START: Periodic memory optimization (triggered by size/time) +β”‚ +β”œβ”€ STEP 1: Memory Analysis +β”‚ β”‚ +β”‚ β”œβ”€ Short-term ─► Scan: active_threads, expired_contexts +β”‚ β”‚ └─ Identify: cleanup_candidates +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Analyze: entity_access_patterns, staleness +β”‚ β”‚ └─ Mark: low_value_facts, duplicates +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Review: episode_relevance, temporal_importance +β”‚ β”‚ └─ Categorize: archive_candidates, delete_candidates +β”‚ β”‚ +β”‚ └─ Procedural ─► Assess: workflow_usage_frequency +β”‚ └─ Optimize: rarely_used_procedures +β”‚ +β”œβ”€ STEP 2: Memory Consolidation +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Merge: related_entity_facts +β”‚ β”‚ └─ Consolidate: redundant_information +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Compress: similar_episodes β†’ patterns +β”‚ β”‚ └─ Extract: behavioral_insights +β”‚ β”‚ +β”‚ └─ Procedural ─► Optimize: workflow_execution_paths +β”‚ +β”œβ”€ STEP 3: Archival Process +β”‚ β”‚ +β”‚ β”œβ”€ Episodic ───► Archive: old_episodes_to_cold_storage +β”‚ β”‚ +β”‚ β”œβ”€ Semantic ───► Backup: stable_entity_facts +β”‚ β”‚ +β”‚ └─ Procedural ─► Version: procedure_definitions +β”‚ +└─ STEP 4: Performance Optimization + β”‚ + β”œβ”€ Storage ────► Defragment: memory_structures + β”‚ + β”œβ”€ Indexes ────► Rebuild: search_optimization_structures + β”‚ + └─ Metrics ────► Update: system_performance_baselines + +END: Optimized memory system with improved performance +``` + +## Data Flow Between Memory Types + +### Cross-Memory Interactions + +``` +Client Profile Evolution: +Semantic(client_facts) ──► Episodic(interactions) ──► Procedural(learning) ──► Semantic(updated_profile) + +Market Intelligence Buildup: +Semantic(market_entities) ──► Episodic(market_events) ──► Procedural(analysis) ──► Short-term(insights) + +Recommendation Refinement: +Procedural(workflows) ──► Short-term(execution) ──► Episodic(outcomes) ──► Procedural(optimization) + +Compliance Monitoring: +Procedural(rules) ──► Semantic(client_restrictions) ──► Short-term(validation) ──► Episodic(violations) +``` + +### Memory Access Patterns + +``` +High Frequency Access: +β”œβ”€ Short-term: Active analysis contexts, temporary calculations +β”œβ”€ Semantic: Client profiles, market data, frequently used facts +└─ Procedural: Core workflows, validation rules, scoring algorithms + +Medium Frequency Access: +β”œβ”€ Episodic: Recent client interactions, market events (30-90 days) +└─ Semantic: Secondary market data, less frequent client facts + +Low Frequency Access: +β”œβ”€ Episodic: Historical episodes (>90 days), archived interactions +β”œβ”€ Procedural: Rarely used workflows, legacy procedures +└─ Semantic: Cold storage facts, backup entity data +``` + +## Integration with Financial Advisory System + +``` +Financial Advisor Request Flow: +β”‚ +User Input ──► CLI Interface ──► Enhanced Advisor + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Workflow Processor β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Analysis Module Registry β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β”‚ β”‚ β”‚ Market β”‚ β”‚ Risk β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚Research β”‚ β”‚ Analysis β”‚ β”‚ β”‚ + β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β”‚ β”‚ β”‚Complianceβ”‚ β”‚Recommendationβ”‚ β”‚ + β”‚ β”‚ β”‚ Module β”‚ β”‚ Engine β”‚ β”‚ β”‚ + β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ AgentMemorySystem β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚Short β”‚β”‚Semanticβ”‚β”‚Episodicβ”‚β”‚Proc. β”‚ + β”‚ β”‚Term β”‚β”‚Memory β”‚β”‚Memory β”‚β”‚Memory β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Git-based Storage β”‚ + β”‚ β”‚ + β”‚ Versioned β€’ Auditable β€’ Provable β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +This architecture demonstrates how the AgentMemorySystem provides the intelligence layer that transforms basic financial advisory into a sophisticated, learning, and adaptive system. \ No newline at end of file diff --git a/examples/financial_advisor/docs/enhanced.md b/examples/financial_advisor/docs/enhanced.md new file mode 100644 index 0000000..cc6b97e --- /dev/null +++ b/examples/financial_advisor/docs/enhanced.md @@ -0,0 +1,257 @@ +# Enhanced Financial Advisor - Detailed Guide + +Sophisticated, memory-driven system utilizing the full ProllyTree agent memory abstraction with multi-step AI workflows, behavioral learning, and complex financial analysis. + +## Core Enhancements + +- **Complete Agent Memory Integration**: Utilizes all four memory types (short-term, semantic, episodic, procedural) from `src/agent` +- **Multi-Step Workflow Engine**: Complex analysis pipelines that leverage memory for context and learning +- **Deep Personalization**: Client-specific adaptations based on behavioral patterns and interaction history +- **Advanced AI Integration**: Enhanced Rig framework usage with contextual memory-driven prompts +- **Learning and Adaptation**: System learns from recommendation outcomes to improve future advice + +## Architecture Components + +### 1. Enhanced Memory Schema (`src/memory/enhanced_types.rs`) +Financial-specific data structures for comprehensive memory storage: + +- **ClientEntity**: Rich client profiles with risk evolution tracking +- **MarketEntity**: Comprehensive market data with analyst ratings +- **RecommendationEpisode**: Detailed recommendation history with outcomes +- **AnalysisWorkflow**: Procedural knowledge for multi-step analysis +- **ComplianceRule**: Automated compliance checking and validation + +### 2. Workflow Processor (`src/advisor/workflow.rs`) +Orchestrates complex multi-step financial analysis: + +```rust +pub async fn execute_recommendation_workflow( + &mut self, + symbol: &str, + client_id: &str, +) -> Result +``` + +**Workflow Steps:** +1. Initialize analysis context with client memory +2. Market research using semantic and episodic memory +3. Risk analysis with historical pattern recognition +4. Compliance validation with automated rule checking +5. Personalized recommendation generation +6. Learning outcome storage for future improvement + +### 3. Analysis Modules (`src/advisor/analysis_modules.rs`) +Specialized analysis components: + +- **MarketResearchModule**: Comprehensive market analysis with AI insights +- **RiskAnalysisModule**: Multi-dimensional risk assessment +- **ComplianceModule**: Automated regulatory compliance checking +- **RecommendationModule**: Personalized recommendation generation + +### 4. Personalization Engine (`src/advisor/personalization.rs`) +Advanced client behavior modeling and adaptation: + +- **ClientBehaviorModel**: Tracks decision patterns, risk evolution, communication preferences +- **PersonalizationInsights**: AI-driven adaptation strategies +- **Memory-Driven Learning**: Continuous improvement from client interactions + +### 5. Enhanced Financial Advisor (`src/advisor/enhanced_advisor.rs`) +Main coordinator that brings everything together: + +```rust +pub struct EnhancedFinancialAdvisor { + memory_system: Arc, + workflow_processor: WorkflowProcessor, + analysis_modules: AnalysisModuleRegistry, + rig_client: Option, +} +``` + +## Memory System Architecture +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Short-term β”‚ β”‚ Semantic β”‚ β”‚ Episodic β”‚ +β”‚ Working Memory β”‚ β”‚ Knowledge Base β”‚ β”‚ Experience β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Analysis │◄──►│ β€’ Client Profiles│◄──►│ β€’ Interactions β”‚ +β”‚ Context β”‚ β”‚ β€’ Market Data β”‚ β”‚ β€’ Outcomes β”‚ +β”‚ β€’ Step Results β”‚ β”‚ β€’ Compliance β”‚ β”‚ β€’ Decisions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Procedural β”‚ + β”‚ Workflows β”‚ + β”‚ β”‚ + β”‚ β€’ Analysis Steps β”‚ + β”‚ β€’ Risk Proceduresβ”‚ + β”‚ β€’ Compliance β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Key Features Demonstrated + +### Memory-Driven Intelligence +- **Semantic Memory**: Store and retrieve client profiles, market entities, compliance rules +- **Episodic Memory**: Learn from past interactions, recommendation outcomes, market events +- **Procedural Memory**: Codify analysis workflows, compliance procedures, learning algorithms +- **Short-Term Memory**: Maintain context during multi-step analysis workflows + +### Complex Workflow Capabilities +- **Multi-Source Data Validation**: Cross-reference market data from multiple sources +- **Sequential Analysis Steps**: Each step builds on previous results and memory context +- **Dynamic Adaptation**: Workflow adapts based on client profile and market conditions +- **Memory Checkpoint Creation**: Track workflow execution for debugging and learning + +### Advanced Personalization +- **Behavioral Pattern Recognition**: Learn client decision-making patterns +- **Communication Style Adaptation**: Adjust language and detail level per client preferences +- **Risk Tolerance Evolution**: Track how client risk preferences change over time +- **Outcome-Based Learning**: Improve recommendations based on historical success rates + +### Compliance and Risk Management +- **Automated Compliance Checking**: Real-time validation against regulatory rules +- **Multi-Dimensional Risk Assessment**: Comprehensive risk analysis across categories +- **Client Suitability Validation**: Ensure recommendations align with client profiles +- **Audit Trail Generation**: Complete documentation for regulatory compliance + +## Quick Start + +```bash +# 1. Run enhanced interactive session +cargo run -- enhanced --verbose + +# 2. Run comprehensive demonstration +cargo run --example enhanced_demo + +# 3. With AI integration +OPENAI_API_KEY="your-key" cargo run -- enhanced --verbose +``` + +## Enhanced Commands + +### Interactive Session Commands +- `client ` - Set current client for personalized analysis +- `recommend ` - Get enhanced recommendation with full workflow +- `research ` - Perform deep research analysis +- `stats` - Show memory system statistics +- `optimize` - Optimize memory system performance + +### Usage Examples + +#### Multi-Client Scenarios + +```rust +// Client 1: Conservative retiree +advisor.set_current_client("sarah_retired").await?; +advisor.update_client_risk_profile("sarah_retired", RiskTolerance::Conservative).await?; +let conservative_rec = advisor.get_enhanced_recommendation("JNJ").await?; + +// Client 2: Aggressive young investor +advisor.set_current_client("mike_young").await?; +advisor.update_client_risk_profile("mike_young", RiskTolerance::Aggressive).await?; +let aggressive_rec = advisor.get_enhanced_recommendation("NVDA").await?; +``` + +#### Portfolio Management + +```rust +let holdings = vec![ + ("AAPL".to_string(), 0.25), + ("MSFT".to_string(), 0.20), + ("JNJ".to_string(), 0.15), +]; + +let rebalancing_recs = advisor.analyze_portfolio_rebalancing("client_id", holdings).await?; +``` + +#### Learning from Outcomes + +```rust +let outcome = RecommendationOutcome { + actual_return: 0.085, // 8.5% return + client_satisfaction: Some(0.9), + followed_recommendation: true, + notes: "Client very satisfied with results".to_string(), +}; + +advisor.update_recommendation_outcome(&recommendation_id, outcome).await?; +``` + +## Use Cases Demonstrated + +### 1. Personalized Client Advisory +- Different recommendation styles for conservative vs aggressive clients +- Communication adaptation based on client preferences +- Risk tolerance evolution tracking + +### 2. Deep Market Research +- Multi-source data analysis with cross-validation +- Historical pattern recognition +- AI-enhanced market insights + +### 3. Portfolio Management +- Multi-asset portfolio analysis +- Rebalancing recommendations +- Risk-adjusted performance optimization + +### 4. Compliance Automation +- Real-time regulatory compliance checking +- Automated violation detection +- Comprehensive audit trail generation + +### 5. Continuous Learning +- Recommendation outcome tracking +- Behavioral pattern learning +- Workflow optimization based on success rates + +## Performance and Analytics + +### Memory Statistics +- Total memories stored across all types +- Storage utilization and performance metrics +- Memory access patterns and optimization opportunities +- Cross-reference relationship mapping + +### Learning Analytics +- Recommendation accuracy tracking over time +- Client satisfaction correlation analysis +- Behavioral pattern recognition success rates +- Workflow efficiency improvements + +### Compliance Reporting +- Automated compliance violation detection +- Risk assessment accuracy validation +- Audit trail completeness verification +- Regulatory reporting automation + +## Configuration + +### Environment Variables +```bash +export OPENAI_API_KEY="your-api-key" # Enable AI-powered analysis +export ADVISOR_STORAGE="/path/to/storage" # Custom storage location +export ADVISOR_VERBOSE="true" # Enable verbose logging +``` + +### Customization Points +- **Analysis Modules**: Add new specialized analysis components +- **Memory Schema**: Extend with domain-specific data structures +- **Workflow Steps**: Create custom analysis workflows +- **Personalization Rules**: Add new behavioral pattern recognition +- **Compliance Rules**: Configure regulatory compliance checking + +## Technical Architecture + +### Design Patterns +- **Memory-Driven Architecture**: All decisions informed by stored knowledge +- **Workflow Orchestration**: Complex analysis pipelines +- **Behavioral Adaptation**: Learning from user interactions +- **Compliance-First Design**: Regulatory requirements built into the system + +### Key Technologies +- **ProllyTree Storage**: Content-addressed storage with Merkle proofs +- **ProllyTree Agent Memory**: Advanced memory abstraction layer +- **Rig Framework**: AI-powered analysis and reasoning +- **Git Integration**: Native git operations for versioning +- **Async Rust**: High-performance concurrent processing \ No newline at end of file diff --git a/examples/financial_advisor/docs/original.md b/examples/financial_advisor/docs/original.md new file mode 100644 index 0000000..2e9e452 --- /dev/null +++ b/examples/financial_advisor/docs/original.md @@ -0,0 +1,141 @@ +# Original Financial Advisor - Detailed Guide + +A secure, auditable AI financial advisor with git-like versioned memory, temporal queries, and complete audit trails. + +## Core Features + +- **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 + +## How Recommendations Work + +The `recommend ` command generates AI-powered investment advice through a sophisticated pipeline: + +### 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) + +``` +🏦 [main] recommend AAPL +πŸ” Fetching market data for AAPL... +πŸ“‘ Validating data from 3 sources... +``` + +### 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" +} +``` + +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: 85.0% +Reasoning: Strong fundamentals with P/E of 28.4... + +πŸ€– 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 Reference + +### Recommendations & Profiles +- `recommend ` - Get AI recommendation with market analysis +- `profile` - View/edit client profile +- `risk ` - Set risk tolerance + +### Branch Management +- `branch ` - Create strategy branch +- `switch ` - Change branches +- `visualize` - Show branch tree with commits + +### Time Travel +- `history` - Recent recommendations +- `history ` - View at specific commit +- `history --branch ` - Compare branches + +### Security & Audit +- `memory` - System status and validation +- `audit` - Complete operation history +- `test-inject ` - Test security (try SQL injection!) + +## Quick Start + +```bash +# 1. Setup storage with git +mkdir -p /tmp/advisor && cd /tmp/advisor && git init + +# 2. Set OpenAI API key (optional, for AI reasoning) +export OPENAI_API_KEY="your-api-key" + +# 3. Run the basic advisor +cargo run -- --storage /tmp/advisor/data advise +``` + +## 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 + +## Data Simulation Notes +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 + +## 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 + +## Configuration + +### Environment Variables +```bash +export OPENAI_API_KEY="your-api-key" # Enable AI-powered analysis +export ADVISOR_STORAGE="/path/to/storage" # Custom storage location +export ADVISOR_VERBOSE="true" # Enable verbose logging +``` + +### Customization Points +- **Security Rules**: Modify injection detection patterns +- **Data Sources**: Add new simulated market data providers +- **Validation Logic**: Customize cross-source validation rules +- **Branch Strategies**: Create custom investment strategy branches \ No newline at end of file diff --git a/examples/financial_advisor/examples/demo.rs b/examples/financial_advisor/examples/demo.rs new file mode 100644 index 0000000..b8d2a97 --- /dev/null +++ b/examples/financial_advisor/examples/demo.rs @@ -0,0 +1,431 @@ +use anyhow::Result; +use chrono::Duration; +use colored::Colorize; + +use financial_advisor::advisor::enhanced_advisor::EnhancedFinancialAdvisor; +use financial_advisor::advisor::RiskTolerance; +use financial_advisor::memory::enhanced_types::*; + +/// Comprehensive demonstration of the enhanced financial advisor capabilities +#[tokio::main] +async fn main() -> Result<()> { + println!( + "{}", + "🏦 Enhanced Financial Advisor Demonstration" + .bright_blue() + .bold() + ); + println!( + "{}", + "Showcasing advanced agent memory and multi-step workflows".dimmed() + ); + println!("{}", "━".repeat(70).dimmed()); + println!(); + + // Initialize the enhanced advisor + let storage_path = "/tmp/enhanced_advisor_demo"; + std::fs::create_dir_all(storage_path)?; + + let api_key = std::env::var("OPENAI_API_KEY").ok(); + if api_key.is_none() { + println!("πŸ’‘ Tip: Set OPENAI_API_KEY environment variable for AI-powered analysis"); + } + + let mut advisor = EnhancedFinancialAdvisor::new(storage_path, api_key.as_deref(), true).await?; + + println!("βœ… Enhanced Financial Advisor initialized with agent memory system"); + println!(); + + // Demonstration 1: Multi-Client Scenarios + println!( + "{}", + "πŸ“‹ Demonstration 1: Multi-Client Personalized Analysis" + .bright_green() + .bold() + ); + println!("{}", "━".repeat(50).dimmed()); + + await_demo_clients(&mut advisor).await?; + + println!(); + + // Demonstration 2: Complex Workflow Analysis + println!( + "{}", + "πŸ“‹ Demonstration 2: Deep Research & Multi-Step Analysis" + .bright_green() + .bold() + ); + println!("{}", "━".repeat(50).dimmed()); + + await_demo_deep_analysis(&mut advisor).await?; + + println!(); + + // Demonstration 3: Portfolio Management + println!( + "{}", + "πŸ“‹ Demonstration 3: Portfolio Rebalancing & Risk Management" + .bright_green() + .bold() + ); + println!("{}", "━".repeat(50).dimmed()); + + await_demo_portfolio_management(&mut advisor).await?; + + println!(); + + // Demonstration 4: Learning and Adaptation + println!( + "{}", + "πŸ“‹ Demonstration 4: Learning from Outcomes" + .bright_green() + .bold() + ); + println!("{}", "━".repeat(50).dimmed()); + + await_demo_learning(&mut advisor).await?; + + println!(); + + // Demonstration 5: Memory System Capabilities + println!( + "{}", + "πŸ“‹ Demonstration 5: Advanced Memory Features" + .bright_green() + .bold() + ); + println!("{}", "━".repeat(50).dimmed()); + + await_demo_memory_features(&mut advisor).await?; + + println!(); + + // Final summary + println!("{}", "🎯 Demonstration Complete!".bright_green().bold()); + println!("{}", "━".repeat(70).dimmed()); + + let stats = advisor.get_memory_statistics().await?; + println!( + "πŸ“Š Total memories created: {}", + stats.overall.total_memories + ); + println!( + "πŸ’Ύ Storage utilization: {:.2} MB", + stats.overall.total_size_bytes as f64 / 1024.0 / 1024.0 + ); + println!("πŸ”„ Active threads: {}", stats.short_term.active_threads); + + println!(); + println!("πŸ’‘ This demonstration showcased:"); + println!(" β€’ Memory-driven personalization across multiple clients"); + println!(" β€’ Complex multi-step financial analysis workflows"); + println!(" β€’ Deep market research with AI integration"); + println!(" β€’ Portfolio rebalancing with risk management"); + println!(" β€’ Learning from recommendation outcomes"); + println!(" β€’ Advanced memory system optimization"); + println!(); + println!( + "πŸ“‚ Memory data persisted to: {}", + storage_path.bright_cyan() + ); + + Ok(()) +} + +async fn await_demo_clients(advisor: &mut EnhancedFinancialAdvisor) -> Result<()> { + // Client 1: Conservative retiree + println!( + "πŸ‘€ {}", + "Client 1: Sarah (Conservative Retiree)".bright_cyan() + ); + + advisor.set_current_client("sarah_retired").await?; + advisor + .update_client_risk_profile("sarah_retired", RiskTolerance::Conservative) + .await?; + + let recommendation1 = advisor.get_enhanced_recommendation("JNJ").await?; + print_recommendation_summary(&recommendation1, "JNJ"); + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // Client 2: Young aggressive investor + println!( + "πŸ‘€ {}", + "Client 2: Mike (Young Aggressive Investor)".bright_cyan() + ); + + advisor.set_current_client("mike_young").await?; + advisor + .update_client_risk_profile("mike_young", RiskTolerance::Aggressive) + .await?; + + let recommendation2 = advisor.get_enhanced_recommendation("NVDA").await?; + print_recommendation_summary(&recommendation2, "NVDA"); + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // Client 3: Moderate family investor + println!( + "πŸ‘€ {}", + "Client 3: Jennifer (Family Moderate Investor)".bright_cyan() + ); + + advisor.set_current_client("jennifer_family").await?; + advisor + .update_client_risk_profile("jennifer_family", RiskTolerance::Moderate) + .await?; + + let recommendation3 = advisor.get_enhanced_recommendation("MSFT").await?; + print_recommendation_summary(&recommendation3, "MSFT"); + + Ok(()) +} + +async fn await_demo_deep_analysis(advisor: &mut EnhancedFinancialAdvisor) -> Result<()> { + advisor.set_current_client("mike_young").await?; + + println!("πŸ”¬ Performing deep research analysis on AAPL..."); + + let market_analysis = advisor.perform_deep_research("AAPL").await?; + + println!("πŸ“ˆ Fundamental Analysis:"); + println!( + " Growth Prospects: {}", + market_analysis.fundamental_analysis.growth_prospects + ); + println!( + " Competitive Position: {}", + market_analysis.fundamental_analysis.competitive_position + ); + + println!("πŸ“Š Technical Analysis:"); + println!( + " Trend Direction: {}", + market_analysis.technical_analysis.trend_direction + ); + println!( + " Support Levels: {:?}", + market_analysis.technical_analysis.support_levels + ); + + println!("🏭 Sector Analysis:"); + println!( + " Sector Trend: {}", + market_analysis.sector_analysis.sector_trend + ); + println!( + " Relative Performance: {:.1}%", + market_analysis.sector_analysis.relative_performance * 100.0 + ); + + println!("πŸ’­ Sentiment Analysis:"); + println!( + " Analyst Sentiment: {:.1}%", + market_analysis.sentiment_analysis.analyst_sentiment * 100.0 + ); + println!( + " Market Sentiment: {:.1}%", + market_analysis.sentiment_analysis.market_sentiment * 100.0 + ); + + if !market_analysis.ai_insights.is_empty() { + println!("πŸ€– AI Insights:"); + println!(" {}", market_analysis.ai_insights); + } + + Ok(()) +} + +async fn await_demo_portfolio_management(advisor: &mut EnhancedFinancialAdvisor) -> Result<()> { + advisor.set_current_client("jennifer_family").await?; + + println!("βš–οΈ Analyzing portfolio rebalancing for diversified family portfolio..."); + + // Simulate current portfolio holdings + let holdings = vec![ + ("AAPL".to_string(), 0.25), // 25% Apple + ("MSFT".to_string(), 0.20), // 20% Microsoft + ("JNJ".to_string(), 0.15), // 15% Johnson & Johnson + ("V".to_string(), 0.15), // 15% Visa + ("PG".to_string(), 0.10), // 10% Procter & Gamble + ("GOOGL".to_string(), 0.15), // 15% Google + ]; + + let rebalancing_recommendations = advisor + .analyze_portfolio_rebalancing("jennifer_family", holdings) + .await?; + + println!("πŸ“Š Rebalancing Analysis Results:"); + for (i, rec) in rebalancing_recommendations.iter().enumerate() { + println!( + " {}. {:?} - Confidence: {:.1}%", + i + 1, + rec.base_recommendation, + rec.confidence_adjustment * 100.0 + ); + println!( + " Reasoning: {}", + rec.personalized_reasoning + .split('.') + .next() + .unwrap_or("Analysis complete") + ); + } + + Ok(()) +} + +async fn await_demo_learning(advisor: &mut EnhancedFinancialAdvisor) -> Result<()> { + advisor.set_current_client("sarah_retired").await?; + + println!("πŸ“ˆ Simulating recommendation outcomes and learning..."); + + // Get a recommendation first + let recommendation = advisor.get_enhanced_recommendation("PG").await?; + + // Simulate a positive outcome after some time + let outcome = RecommendationOutcome { + actual_return: 0.085, // 8.5% return + time_to_outcome: Duration::days(90), + client_satisfaction: Some(0.9), // Very satisfied + followed_recommendation: true, + notes: "Client was very pleased with steady growth and dividend income".to_string(), + }; + + println!("βœ… Positive outcome recorded:"); + println!(" Return: {:.1}%", outcome.actual_return * 100.0); + println!( + " Client Satisfaction: {:.1}/10", + outcome.client_satisfaction.unwrap_or(0.0) * 10.0 + ); + println!( + " Followed Advice: {}", + if outcome.followed_recommendation { + "Yes" + } else { + "No" + } + ); + + // Update the recommendation outcome + advisor + .update_recommendation_outcome(&recommendation.recommendation_id, outcome) + .await?; + + println!("🧠 Learning algorithms updated with outcome data"); + + // Simulate another recommendation to show adaptation + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + let adapted_recommendation = advisor.get_enhanced_recommendation("UNH").await?; + println!("🎯 Subsequent recommendation generated with learned patterns:"); + print_recommendation_summary(&adapted_recommendation, "UNH"); + + Ok(()) +} + +async fn await_demo_memory_features(advisor: &mut EnhancedFinancialAdvisor) -> Result<()> { + println!("🧠 Demonstrating advanced memory system features..."); + + // Get current memory statistics + let stats = advisor.get_memory_statistics().await?; + println!("πŸ“Š Current Memory State:"); + println!(" Total Memories: {}", stats.overall.total_memories); + println!(" Short-term Threads: {}", stats.short_term.active_threads); + println!( + " Storage Size: {:.2} MB", + stats.overall.total_size_bytes as f64 / 1024.0 / 1024.0 + ); + println!(" Avg Access Count: {:.2}", stats.overall.avg_access_count); + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // Demonstrate memory optimization + println!("🧹 Performing memory system optimization..."); + let optimization_report = advisor.optimize_memory_system().await?; + + println!("βœ… Optimization Results:"); + println!( + " Expired memories cleaned: {}", + optimization_report.expired_cleaned + ); + println!( + " Memories consolidated: {}", + optimization_report.memories_consolidated + ); + println!( + " Memories archived: {}", + optimization_report.memories_archived + ); + println!( + " Low-value memories pruned: {}", + optimization_report.memories_pruned + ); + println!( + " Total items processed: {}", + optimization_report.total_processed() + ); + + // Show updated statistics + let updated_stats = advisor.get_memory_statistics().await?; + println!("πŸ“Š Post-Optimization State:"); + println!( + " Total Memories: {} (change: {:+})", + updated_stats.overall.total_memories, + updated_stats.overall.total_memories as i64 - stats.overall.total_memories as i64 + ); + + Ok(()) +} + +fn print_recommendation_summary(recommendation: &DetailedRecommendation, symbol: &str) { + println!( + " πŸ“Š {} Recommendation: {:?} ({:.1}% confidence)", + symbol.bright_yellow(), + recommendation.base_recommendation, + recommendation.confidence * 100.0 + ); + + println!( + " πŸ’­ Key Insight: {}", + recommendation + .personalized_reasoning + .split('.') + .next() + .unwrap_or("Analysis complete") + .trim() + ); + + println!( + " βš–οΈ Risk Score: {:.1}/10 (Alignment: {:.1}%)", + recommendation.risk_assessment.overall_risk_score * 10.0, + recommendation.risk_assessment.client_risk_alignment * 100.0 + ); + + println!( + " πŸ›‘οΈ Compliance: {}", + if recommendation.compliance_validation.passed { + "βœ… PASSED".green() + } else { + "❌ ISSUES".red() + } + ); + + println!( + " ⏱️ Processing: {:.2}s", + recommendation + .execution_metadata + .total_execution_time + .num_milliseconds() as f64 + / 1000.0 + ); + + println!(); +} + +// Helper function to create a delay for better demo pacing +async fn _demo_pause() { + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; +} diff --git a/examples/financial_advisor/src/advisor/analysis_modules.rs b/examples/financial_advisor/src/advisor/analysis_modules.rs new file mode 100644 index 0000000..a9e8f7e --- /dev/null +++ b/examples/financial_advisor/src/advisor/analysis_modules.rs @@ -0,0 +1,1051 @@ +use anyhow::Result; +// Removed unused DateTime and Utc imports +use rig::{completion::Prompt, providers::openai::Client}; +// Removed unused Deserialize and Serialize imports +use std::collections::HashMap; +use std::sync::Arc; + +use prollytree::agent::AgentMemorySystem; + +use crate::advisor::{RecommendationType, RiskTolerance}; +use crate::memory::enhanced_types::*; + +/// Registry of analysis modules for different financial analysis tasks +pub struct AnalysisModuleRegistry { + pub market_research: MarketResearchModule, + pub risk_analysis: RiskAnalysisModule, + pub compliance_check: ComplianceModule, + pub recommendation_engine: RecommendationModule, +} + +impl AnalysisModuleRegistry { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + market_research: MarketResearchModule::new(memory_system.clone(), rig_client.clone()), + risk_analysis: RiskAnalysisModule::new(memory_system.clone(), rig_client.clone()), + compliance_check: ComplianceModule::new(memory_system.clone(), rig_client.clone()), + recommendation_engine: RecommendationModule::new(memory_system.clone(), rig_client), + } + } +} + +/// Market research and analysis module +pub struct MarketResearchModule { + memory_system: Arc, + rig_client: Option, +} + +impl MarketResearchModule { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + memory_system, + rig_client, + } + } + + /// Perform comprehensive market research for a symbol + pub async fn analyze_market( + &self, + symbol: &str, + context: &AnalysisContext, + ) -> Result { + // 1. Retrieve historical market data from semantic memory + let market_facts = self.get_market_knowledge(symbol).await?; + + // 2. Find similar market conditions from episodic memory + let similar_episodes = self + .find_similar_market_conditions(&context.market_conditions) + .await?; + + // 3. Apply procedural knowledge for market analysis + let _analysis_procedures = self.get_analysis_procedures().await?; + + // 4. Generate AI-powered insights if available + let ai_insights = self + .generate_ai_insights(symbol, &market_facts, &similar_episodes) + .await; + + // 5. Synthesize comprehensive analysis + Ok(MarketAnalysisResult { + fundamental_analysis: self + .perform_fundamental_analysis(symbol, &market_facts) + .await?, + technical_analysis: self.perform_technical_analysis(symbol).await?, + sector_analysis: self.perform_sector_analysis(symbol).await?, + sentiment_analysis: self + .perform_sentiment_analysis(symbol, &similar_episodes) + .await?, + ai_insights, + }) + } + + async fn get_market_knowledge( + &self, + symbol: &str, + ) -> Result> { + self.memory_system + .semantic + .get_entity_facts("market", symbol) + .await + .map_err(|e| anyhow::anyhow!("Failed to retrieve market knowledge: {}", e)) + } + + async fn find_similar_market_conditions( + &self, + conditions: &MarketSnapshot, + ) -> Result> { + // Search for episodes with similar market conditions + let _search_tags = ["market_analysis", &conditions.market_trend.to_lowercase()]; + + self.memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(30), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to find similar market conditions: {}", e)) + } + + async fn get_analysis_procedures(&self) -> Result> { + self.memory_system + .procedural + .get_procedures_by_category("market_analysis") + .await + .map_err(|e| anyhow::anyhow!("Failed to get analysis procedures: {}", e)) + } + + async fn generate_ai_insights( + &self, + symbol: &str, + market_facts: &[prollytree::agent::MemoryDocument], + episodes: &[prollytree::agent::MemoryDocument], + ) -> String { + if let Some(ref client) = self.rig_client { + let prompt = format!( + r#"Provide investment insights for {}: + +Historical Data Points: {} market-related memories +Similar Market Episodes: {} historical episodes + +Focus on: +1. Key investment drivers +2. Potential catalysts or risks +3. Market positioning +4. Timing considerations + +Provide 2-3 key insights in bullet format."#, + symbol, + market_facts.len(), + episodes.len() + ); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble("You are a senior market analyst providing concise, actionable investment insights.") + .max_tokens(300) + .temperature(0.3) + .build(); + + match agent.prompt(&prompt).await { + Ok(response) => response.trim().to_string(), + Err(_) => format!( + "Market analysis for {symbol} shows mixed signals requiring careful evaluation" + ), + } + } else { + format!( + "Technical and fundamental analysis suggests {symbol} requires further evaluation" + ) + } + } + + async fn perform_fundamental_analysis( + &self, + symbol: &str, + _market_facts: &[prollytree::agent::MemoryDocument], + ) -> Result { + // Simulate fundamental analysis based on symbol and available data + let (growth_prospects, competitive_position, financial_health) = match symbol { + "AAPL" => ( + "Strong growth driven by services and emerging markets expansion", + "Dominant position in premium consumer electronics with strong brand loyalty", + "Excellent balance sheet with substantial cash reserves and manageable debt", + ), + "MSFT" => ( + "Consistent growth in cloud computing and enterprise software solutions", + "Leading market position in productivity software and cloud infrastructure", + "Strong financial metrics with steady cash flow generation", + ), + "GOOGL" => ( + "Growth driven by digital advertising and cloud services expansion", + "Dominant search engine with expanding ecosystem of services", + "Strong financial position with diversified revenue streams", + ), + _ => ( + "Moderate growth prospects based on sector fundamentals", + "Competitive position varies with market conditions", + "Financial health requires individual assessment", + ), + }; + + let mut valuation_metrics = HashMap::new(); + match symbol { + "AAPL" => { + valuation_metrics.insert("P/E".to_string(), 28.4); + valuation_metrics.insert("P/B".to_string(), 45.2); + valuation_metrics.insert("EV/Revenue".to_string(), 7.2); + valuation_metrics.insert("ROE".to_string(), 0.84); + } + "MSFT" => { + valuation_metrics.insert("P/E".to_string(), 32.1); + valuation_metrics.insert("P/B".to_string(), 12.8); + valuation_metrics.insert("EV/Revenue".to_string(), 13.5); + valuation_metrics.insert("ROE".to_string(), 0.44); + } + _ => { + valuation_metrics.insert("P/E".to_string(), 25.0); + valuation_metrics.insert("P/B".to_string(), 3.5); + valuation_metrics.insert("EV/Revenue".to_string(), 5.0); + valuation_metrics.insert("ROE".to_string(), 0.15); + } + } + + Ok(FundamentalAnalysis { + valuation_metrics, + growth_prospects: growth_prospects.to_string(), + competitive_position: competitive_position.to_string(), + financial_health: financial_health.to_string(), + }) + } + + async fn perform_technical_analysis(&self, symbol: &str) -> Result { + // Simulate technical analysis (in real implementation, would use actual market data) + let (trend_direction, support_levels, resistance_levels) = match symbol { + "AAPL" => ( + "Upward trend with minor consolidation", + vec![175.0, 170.0, 165.0], + vec![185.0, 190.0, 195.0], + ), + "MSFT" => ( + "Sideways trend with upward bias", + vec![410.0, 405.0, 400.0], + vec![420.0, 425.0, 430.0], + ), + _ => ( + "Mixed trend requiring further analysis", + vec![100.0, 95.0, 90.0], + vec![110.0, 115.0, 120.0], + ), + }; + + let mut momentum_indicators = HashMap::new(); + momentum_indicators.insert("RSI".to_string(), 58.5); + momentum_indicators.insert("MACD".to_string(), 1.2); + momentum_indicators.insert("Stochastic".to_string(), 62.3); + momentum_indicators.insert("Williams%R".to_string(), -38.7); + + Ok(TechnicalAnalysis { + trend_direction: trend_direction.to_string(), + support_levels, + resistance_levels, + momentum_indicators, + }) + } + + async fn perform_sector_analysis(&self, symbol: &str) -> Result { + let sector_info = match symbol { + "AAPL" | "MSFT" | "GOOGL" => ( + "Technology sector showing resilience amid market volatility", + 1.15, + "Continued investor interest in technology fundamentals", + vec![ + "AI and machine learning adoption".to_string(), + "Cloud computing growth".to_string(), + "Digital transformation trends".to_string(), + "Productivity software demand".to_string(), + ], + ), + _ => ( + "Sector performance varies with market conditions", + 1.0, + "Mixed outlook depending on economic indicators", + vec![ + "Economic cycle positioning".to_string(), + "Interest rate sensitivity".to_string(), + "Regulatory environment".to_string(), + ], + ), + }; + + Ok(SectorAnalysis { + sector_trend: sector_info.0.to_string(), + relative_performance: sector_info.1, + sector_rotation_outlook: sector_info.2.to_string(), + key_sector_drivers: sector_info.3, + }) + } + + async fn perform_sentiment_analysis( + &self, + symbol: &str, + _episodes: &[prollytree::agent::MemoryDocument], + ) -> Result { + // Simulate sentiment analysis based on historical episodes and current factors + let base_sentiment = match symbol { + "AAPL" => (0.75, 0.68, 0.72), + "MSFT" => (0.78, 0.71, 0.74), + "GOOGL" => (0.72, 0.65, 0.69), + _ => (0.65, 0.60, 0.62), + }; + + // Adjust based on historical episodes + let episode_adjustment = if _episodes.len() > 5 { 0.05 } else { 0.0 }; + + Ok(SentimentAnalysis { + analyst_sentiment: f64::min(base_sentiment.0 + episode_adjustment, 1.0), + market_sentiment: f64::min(base_sentiment.1 + episode_adjustment, 1.0), + news_sentiment: f64::min(base_sentiment.2 + episode_adjustment, 1.0), + sentiment_drivers: vec![ + "Strong earnings guidance and execution".to_string(), + "Product innovation and market expansion".to_string(), + "Positive analyst revisions".to_string(), + "Institutional investor confidence".to_string(), + ], + }) + } +} + +/// Risk analysis module +pub struct RiskAnalysisModule { + memory_system: Arc, + rig_client: Option, +} + +impl RiskAnalysisModule { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + memory_system, + rig_client, + } + } + + /// Perform comprehensive risk assessment + pub async fn assess_risk( + &self, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + ) -> Result { + // 1. Get client's risk history from episodic memory + let risk_episodes = self.get_client_risk_history(&context.client_id).await?; + + // 2. Apply risk assessment procedures from procedural memory + let _risk_procedures = self.get_risk_procedures().await?; + + // 3. Calculate risk breakdown based on multiple factors + let risk_breakdown = self + .calculate_risk_breakdown(context, market_analysis) + .await?; + + // 4. Generate AI-powered risk insights if available + let risk_factors = self + .generate_risk_insights(context, market_analysis, &risk_episodes) + .await; + + // 5. Assess client alignment + let client_risk_alignment = self + .assess_client_alignment(context, &risk_breakdown) + .await?; + + let overall_risk_score = risk_breakdown.values().sum::() / risk_breakdown.len() as f64; + let risk_breakdown_clone = risk_breakdown.clone(); + + Ok(RiskAssessmentResult { + overall_risk_score, + risk_breakdown, + risk_factors, + mitigation_recommendations: self + .generate_mitigation_strategies(context, &risk_breakdown_clone) + .await, + client_risk_alignment, + }) + } + + async fn get_client_risk_history( + &self, + _client_id: &str, + ) -> Result> { + self.memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(90), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to get client risk history: {}", e)) + } + + async fn get_risk_procedures(&self) -> Result> { + self.memory_system + .procedural + .get_procedures_by_category("risk_assessment") + .await + .map_err(|e| anyhow::anyhow!("Failed to get risk procedures: {}", e)) + } + + async fn calculate_risk_breakdown( + &self, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + ) -> Result> { + let mut risk_breakdown = HashMap::new(); + + // Market risk based on volatility and sentiment + let market_risk = if market_analysis.sentiment_analysis.market_sentiment < 0.5 { + 0.8 + } else if market_analysis.sentiment_analysis.market_sentiment > 0.8 { + 0.4 + } else { + 0.6 + }; + risk_breakdown.insert(RiskCategory::Market, market_risk); + + // Credit risk (simplified - would be more complex in real implementation) + risk_breakdown.insert(RiskCategory::Credit, 0.25); + + // Liquidity risk based on market conditions + let liquidity_risk = if context.market_conditions.volatility_index > 30.0 { + 0.7 + } else { + 0.3 + }; + risk_breakdown.insert(RiskCategory::Liquidity, liquidity_risk); + + // Concentration risk based on client portfolio (simplified) + risk_breakdown.insert(RiskCategory::Concentration, 0.45); + + // Interest rate risk + let interest_rate_risk = if context.market_conditions.interest_rates > 5.0 { + 0.6 + } else { + 0.4 + }; + risk_breakdown.insert(RiskCategory::InterestRate, interest_rate_risk); + + Ok(risk_breakdown) + } + + async fn generate_risk_insights( + &self, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + _episodes: &[prollytree::agent::MemoryDocument], + ) -> Vec { + if let Some(ref client) = self.rig_client { + let prompt = format!( + r#"Identify key risk factors for {} investment: + +Market Analysis: +- Analyst Sentiment: {:.1}% +- Market Sentiment: {:.1}% +- Sector Performance: {} + +Client Context: +- Risk Tolerance: {:?} +- Time Horizon: {} +- Historical Risk Episodes: {} + +List 3-5 specific risk factors to monitor."#, + context.symbol, + market_analysis.sentiment_analysis.analyst_sentiment * 100.0, + market_analysis.sentiment_analysis.market_sentiment * 100.0, + market_analysis.sector_analysis.sector_trend, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + _episodes.len() + ); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble( + "You are a risk management expert. Focus on specific, actionable risk factors.", + ) + .max_tokens(250) + .temperature(0.2) + .build(); + + match agent.prompt(&prompt).await { + Ok(response) => response + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.trim().to_string()) + .collect(), + Err(_) => self.generate_default_risk_factors(context), + } + } else { + self.generate_default_risk_factors(context) + } + } + + fn generate_default_risk_factors(&self, context: &AnalysisContext) -> Vec { + vec![ + format!( + "Market volatility risk for {} sector exposure", + context.symbol + ), + "Interest rate sensitivity given current economic environment".to_string(), + format!( + "Concentration risk based on {:?} risk tolerance", + context.client_profile.risk_tolerance + ), + "Liquidity risk during market stress periods".to_string(), + "Sector-specific risks and regulatory changes".to_string(), + ] + } + + async fn assess_client_alignment( + &self, + context: &AnalysisContext, + risk_breakdown: &HashMap, + ) -> Result { + let overall_risk = risk_breakdown.values().sum::() / risk_breakdown.len() as f64; + + let alignment = match context.client_profile.risk_tolerance { + RiskTolerance::Conservative => { + if overall_risk > 0.7 { + 0.5 + } else if overall_risk > 0.5 { + 0.7 + } else { + 0.9 + } + } + RiskTolerance::Moderate => { + if overall_risk > 0.8 { + 0.6 + } else if overall_risk < 0.3 { + 0.7 + } else { + 0.85 + } + } + RiskTolerance::Aggressive => { + if overall_risk < 0.4 { + 0.6 + } else if overall_risk > 0.6 { + 0.9 + } else { + 0.8 + } + } + }; + + Ok(alignment) + } + + async fn generate_mitigation_strategies( + &self, + context: &AnalysisContext, + risk_breakdown: &HashMap, + ) -> Vec { + let mut strategies = Vec::new(); + + for (risk_type, risk_level) in risk_breakdown { + if *risk_level > 0.6 { + let strategy = match risk_type { + RiskCategory::Market => { + "Consider position sizing and diversification across market sectors" + } + RiskCategory::Liquidity => { + "Maintain adequate cash reserves and avoid illiquid positions" + } + RiskCategory::Concentration => { + "Diversify holdings across different assets and sectors" + } + RiskCategory::InterestRate => { + "Consider duration management and rate-sensitive asset allocation" + } + RiskCategory::Credit => "Focus on high-quality issuers and credit analysis", + _ => "Monitor risk factors and adjust position sizing as needed", + }; + strategies.push(strategy.to_string()); + } + } + + // Add client-specific strategies + match context.client_profile.risk_tolerance { + RiskTolerance::Conservative => { + strategies.push( + "Emphasize capital preservation and steady income generation".to_string(), + ); + } + RiskTolerance::Aggressive => { + strategies.push( + "Consider using stop-loss orders and profit-taking strategies".to_string(), + ); + } + _ => {} + } + + if strategies.is_empty() { + strategies.push("Regular portfolio review and rebalancing".to_string()); + } + + strategies + } +} + +/// Compliance checking module +pub struct ComplianceModule { + memory_system: Arc, + rig_client: Option, +} + +impl ComplianceModule { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + memory_system, + rig_client, + } + } + + /// Perform comprehensive compliance validation + pub async fn validate_compliance( + &self, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> Result { + // 1. Get compliance rules from procedural memory + let _compliance_rules = self.get_compliance_rules().await?; + + // 2. Check client-specific restrictions + let _client_restrictions = self.get_client_restrictions(&context.client_id).await?; + + // 3. Analyze historical compliance issues + let _compliance_history = self.get_compliance_history().await?; + + // 4. Perform compliance checks + let mut violations = Vec::new(); + let mut warnings = Vec::new(); + + // Check suitability + if risk_assessment.client_risk_alignment < 0.7 { + violations.push(ComplianceViolation { + rule_id: "SUITABILITY_001".to_string(), + severity: ComplianceSeverity::Warning, + description: "Investment may not align with client risk profile".to_string(), + recommended_action: "Review recommendation with client or adjust strategy" + .to_string(), + }); + } + + // Check position limits + if risk_assessment.overall_risk_score > 0.8 { + warnings.push(ComplianceWarning { + rule_id: "RISK_001".to_string(), + description: "High risk score detected - monitor position sizing".to_string(), + recommendation: + "Consider reducing position size or implementing additional risk controls" + .to_string(), + }); + } + + // Generate automated actions + let automated_actions = self + .generate_compliance_actions(context, risk_assessment, &violations, &warnings) + .await; + + Ok(ComplianceValidation { + passed: violations.is_empty(), + violations, + warnings, + required_disclosures: vec![ + "Past performance does not guarantee future results".to_string(), + "All investments carry risk of loss".to_string(), + "Please review all investment materials carefully".to_string(), + ], + automated_actions_taken: automated_actions, + }) + } + + async fn get_compliance_rules(&self) -> Result> { + self.memory_system + .procedural + .get_procedures_by_category("compliance") + .await + .map_err(|e| anyhow::anyhow!("Failed to get compliance rules: {}", e)) + } + + async fn get_client_restrictions( + &self, + client_id: &str, + ) -> Result> { + self.memory_system + .semantic + .get_entity_facts("client_restrictions", client_id) + .await + .map_err(|e| anyhow::anyhow!("Failed to get client restrictions: {}", e)) + } + + async fn get_compliance_history(&self) -> Result> { + self.memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(365), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to get compliance history: {}", e)) + } + + async fn generate_compliance_actions( + &self, + _context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + violations: &[ComplianceViolation], + warnings: &[ComplianceWarning], + ) -> Vec { + let mut actions = Vec::new(); + + if !violations.is_empty() { + actions.push("Compliance violation flagged for manual review".to_string()); + } + + if !warnings.is_empty() { + actions.push("Compliance warnings documented in client file".to_string()); + } + + if risk_assessment.client_risk_alignment < 0.8 { + actions.push("Client risk profile review scheduled".to_string()); + } + + actions.push("All compliance checks completed and documented".to_string()); + + actions + } +} + +/// Recommendation generation module +pub struct RecommendationModule { + memory_system: Arc, + rig_client: Option, +} + +impl RecommendationModule { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + memory_system, + rig_client, + } + } + + /// Generate personalized investment recommendation + pub async fn generate_recommendation( + &self, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + compliance_validation: &ComplianceValidation, + ) -> Result { + // 1. Determine base recommendation logic + let base_recommendation = self + .determine_base_recommendation(market_analysis, risk_assessment, compliance_validation) + .await?; + + // 2. Get client interaction history for personalization + let client_history = self.get_client_history(&context.client_id).await?; + + // 3. Generate personalized reasoning + let personalized_reasoning = self + .generate_personalized_reasoning( + base_recommendation, + context, + market_analysis, + risk_assessment, + &client_history, + ) + .await; + + // 4. Calculate confidence adjustment + let confidence_adjustment = self + .calculate_confidence_adjustment( + market_analysis, + risk_assessment, + compliance_validation, + &client_history, + ) + .await; + + // 5. Extract client-specific factors + let client_specific_factors = self.extract_client_factors(context, risk_assessment).await; + + Ok(PersonalizedRecommendation { + base_recommendation, + personalized_reasoning, + confidence_adjustment, + client_specific_factors, + presentation_style: "conversational".to_string(), + follow_up_actions: self + .generate_follow_up_actions(context, base_recommendation) + .await, + }) + } + + async fn determine_base_recommendation( + &self, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + compliance_validation: &ComplianceValidation, + ) -> Result { + // Compliance first - if issues exist, default to hold + if !compliance_validation.passed { + return Ok(RecommendationType::Hold); + } + + // Strong positive signals + if market_analysis.sentiment_analysis.analyst_sentiment > 0.75 + && risk_assessment.client_risk_alignment > 0.8 + && risk_assessment.overall_risk_score < 0.7 + { + return Ok(RecommendationType::Buy); + } + + // Strong negative signals + if market_analysis.sentiment_analysis.analyst_sentiment < 0.4 + || risk_assessment.overall_risk_score > 0.8 + { + return Ok(RecommendationType::Sell); + } + + // Check for rebalancing needs + if risk_assessment.client_risk_alignment < 0.7 { + return Ok(RecommendationType::Rebalance); + } + + // Default to hold for neutral conditions + Ok(RecommendationType::Hold) + } + + async fn get_client_history( + &self, + _client_id: &str, + ) -> Result> { + self.memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(180), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to get client history: {}", e)) + } + + async fn generate_personalized_reasoning( + &self, + recommendation: RecommendationType, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + _client_history: &[prollytree::agent::MemoryDocument], + ) -> String { + if let Some(ref client) = self.rig_client { + let prompt = format!( + r#"Create personalized investment advice for client: + +Recommendation: {:?} {} +Market Outlook: {} +Risk Level: {:.1}/10 (Alignment: {:.1}%) +Client Profile: {:?} risk tolerance, {} time horizon + +Client History: {} previous interactions +Investment Goals: {} + +Explain this recommendation in a warm, personal tone that: +1. Acknowledges their specific situation and goals +2. Connects to their risk tolerance and time horizon +3. Provides clear reasoning and next steps +4. Shows confidence while being realistic + +Keep it conversational and encouraging."#, + recommendation, + context.symbol, + market_analysis.fundamental_analysis.growth_prospects, + risk_assessment.overall_risk_score * 10.0, + risk_assessment.client_risk_alignment * 100.0, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + _client_history.len(), + context.client_profile.investment_goals.join(", ") + ); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble("You are a trusted financial advisor speaking directly to a valued client. Be personal, clear, and confidence-inspiring.") + .max_tokens(400) + .temperature(0.4) + .build(); + + match agent.prompt(&prompt).await { + Ok(response) => response.trim().to_string(), + Err(_) => { + self.generate_fallback_reasoning(recommendation, context, risk_assessment) + } + } + } else { + self.generate_fallback_reasoning(recommendation, context, risk_assessment) + } + } + + fn generate_fallback_reasoning( + &self, + recommendation: RecommendationType, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> String { + match recommendation { + RecommendationType::Buy => format!( + "Based on our comprehensive analysis, {} presents an attractive opportunity that aligns well with your {:?} risk profile and {} investment timeline. Our analysis shows a {:.1}% alignment with your comfort level, and the investment supports your goals of {}. This recommendation reflects both strong market fundamentals and suitability for your unique situation.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + risk_assessment.client_risk_alignment * 100.0, + context.client_profile.investment_goals.join(" and ") + ), + RecommendationType::Hold => format!( + "For your {} position, we recommend maintaining your current exposure at this time. Given your {:?} risk tolerance and {} investment horizon, holding allows you to stay positioned for potential upside while we continue monitoring market conditions. This approach aligns well with your {} objectives and maintains appropriate risk levels.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + context.client_profile.investment_goals.join(" and ") + ), + RecommendationType::Sell => format!( + "Our analysis suggests reducing your {} position would be prudent given current market conditions and your {:?} risk profile. While the underlying fundamentals remain solid, taking some profits aligns with your {} timeline and helps preserve capital for future opportunities that better match your {} goals.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + context.client_profile.investment_goals.join(" and ") + ), + RecommendationType::Rebalance => format!( + "We recommend rebalancing your {} position to better align with your {:?} risk tolerance and {} investment strategy. This adjustment will help optimize your portfolio's risk-return profile while ensuring it continues to serve your {} objectives effectively.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + context.client_profile.investment_goals.join(" and ") + ), + } + } + + async fn calculate_confidence_adjustment( + &self, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + compliance_validation: &ComplianceValidation, + _client_history: &[prollytree::agent::MemoryDocument], + ) -> f64 { + let mut confidence: f64 = 0.75; // Base confidence + + // Market sentiment boost + if market_analysis.sentiment_analysis.analyst_sentiment > 0.8 { + confidence += 0.1; + } + + // Risk alignment boost + if risk_assessment.client_risk_alignment > 0.9 { + confidence += 0.1; + } + + // Compliance impact + if !compliance_validation.passed { + confidence -= 0.2; + } else if !compliance_validation.warnings.is_empty() { + confidence -= 0.05; + } + + // Client relationship depth + if _client_history.len() > 10 { + confidence += 0.05; + } + + // AI availability boost + if self.rig_client.is_some() { + confidence += 0.05; + } + + confidence.clamp(0.1, 0.95) + } + + async fn extract_client_factors( + &self, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> Vec { + vec![ + format!( + "Risk tolerance: {:?}", + context.client_profile.risk_tolerance + ), + format!( + "Investment timeline: {}", + context.client_profile.time_horizon + ), + format!( + "Primary goals: {}", + context.client_profile.investment_goals.join(", ") + ), + format!( + "Risk alignment score: {:.1}%", + risk_assessment.client_risk_alignment * 100.0 + ), + format!( + "Portfolio value: ${:.0}", + context.client_profile.portfolio_value + ), + if !context.client_profile.restrictions.is_empty() { + format!( + "Investment restrictions: {}", + context.client_profile.restrictions.join(", ") + ) + } else { + "No specific investment restrictions".to_string() + }, + ] + } + + async fn generate_follow_up_actions( + &self, + _context: &AnalysisContext, + recommendation: RecommendationType, + ) -> Vec { + let mut actions = vec![ + "Schedule follow-up review in 30 days".to_string(), + "Monitor market conditions and company fundamentals".to_string(), + ]; + + match recommendation { + RecommendationType::Buy => { + actions.push("Consider dollar-cost averaging for position building".to_string()); + actions.push("Set target price levels for profit taking".to_string()); + } + RecommendationType::Sell => { + actions.push("Review tax implications of sale".to_string()); + actions.push("Identify alternative investment opportunities".to_string()); + } + RecommendationType::Rebalance => { + actions.push("Calculate optimal position sizing".to_string()); + actions.push("Schedule portfolio rebalancing execution".to_string()); + } + RecommendationType::Hold => { + actions.push("Establish monitoring triggers for position changes".to_string()); + } + } + + if matches!( + _context.client_profile.risk_tolerance, + RiskTolerance::Conservative + ) { + actions.push("Review income-generating alternatives".to_string()); + } + + actions + } +} diff --git a/examples/financial_advisor/src/advisor/enhanced_advisor.rs b/examples/financial_advisor/src/advisor/enhanced_advisor.rs new file mode 100644 index 0000000..d85ebeb --- /dev/null +++ b/examples/financial_advisor/src/advisor/enhanced_advisor.rs @@ -0,0 +1,707 @@ +use anyhow::Result; +use chrono::Utc; +use colored::Colorize; +use rig::providers::openai::Client; +// Removed unused Deserialize and Serialize imports +use std::sync::Arc; + +use prollytree::agent::{AgentMemoryStats, AgentMemorySystem, OptimizationReport}; + +use crate::advisor::analysis_modules::AnalysisModuleRegistry; +use crate::advisor::workflow::WorkflowProcessor; +use crate::advisor::RiskTolerance; +use crate::memory::enhanced_types::*; + +/// Enhanced Financial Advisor with deep agent memory integration +pub struct EnhancedFinancialAdvisor { + /// Core agent memory system with all memory types + memory_system: Arc, + + /// Workflow processor for multi-step analysis + workflow_processor: WorkflowProcessor, + + /// Analysis modules registry + analysis_modules: AnalysisModuleRegistry, + + /// Optional Rig client for AI-powered analysis + rig_client: Option, + + /// Current client being served + current_client_id: Option, + + /// Verbose logging + verbose: bool, +} + +impl EnhancedFinancialAdvisor { + /// Create a new enhanced financial advisor + pub async fn new(storage_path: &str, api_key: Option<&str>, verbose: bool) -> Result { + // Initialize agent memory system + let memory_system = Arc::new( + AgentMemorySystem::init( + storage_path, + "enhanced_financial_advisor".to_string(), + None, // No embedding generator for now + ) + .map_err(|e| anyhow::anyhow!("Failed to initialize memory system: {}", e))?, + ); + + // Setup Rig client if API key provided + let rig_client = api_key.map(Client::new); + + // Initialize workflow processor + let workflow_processor = WorkflowProcessor::new(memory_system.clone(), api_key, verbose); + + // Initialize analysis modules + let analysis_modules = + AnalysisModuleRegistry::new(memory_system.clone(), rig_client.clone()); + + Ok(Self { + memory_system, + workflow_processor, + analysis_modules, + rig_client, + current_client_id: None, + verbose, + }) + } + + /// Open an existing enhanced financial advisor + pub async fn open(storage_path: &str, api_key: Option<&str>, verbose: bool) -> Result { + // Open existing agent memory system + let memory_system = Arc::new( + AgentMemorySystem::open(storage_path, "enhanced_financial_advisor".to_string(), None) + .map_err(|e| anyhow::anyhow!("Failed to open memory system: {}", e))?, + ); + + let rig_client = api_key.map(Client::new); + + let workflow_processor = WorkflowProcessor::new(memory_system.clone(), api_key, verbose); + + let analysis_modules = + AnalysisModuleRegistry::new(memory_system.clone(), rig_client.clone()); + + Ok(Self { + memory_system, + workflow_processor, + analysis_modules, + rig_client, + current_client_id: None, + verbose, + }) + } + + /// Set the current client for personalized analysis + pub async fn set_current_client(&mut self, client_id: &str) -> Result<()> { + // Verify client exists or create new profile + let _client_profile = self.get_or_create_client_profile(client_id).await?; + + // Note: In a full implementation, client profile would be stored in semantic memory + // let client_json = serde_json::to_string(&client_profile)?; + // self.memory_system.semantic.store_fact(...).await?; + + self.current_client_id = Some(client_id.to_string()); + + if self.verbose { + println!("πŸ‘€ Set current client: {}", client_id.bright_cyan()); + } + + Ok(()) + } + + /// Get comprehensive recommendation using enhanced workflow + pub async fn get_enhanced_recommendation( + &mut self, + symbol: &str, + ) -> Result { + let client_id = self.current_client_id.as_ref().ok_or_else(|| { + anyhow::anyhow!("No current client set. Use set_current_client() first.") + })?; + + if self.verbose { + println!( + "πŸ”„ Generating enhanced recommendation for {} (client: {})", + symbol.bright_yellow(), + client_id.bright_cyan() + ); + } + + // Execute the comprehensive workflow + let recommendation = self + .workflow_processor + .execute_recommendation_workflow(symbol, client_id) + .await?; + + // Note: In a full implementation, recommendation episode would be stored in episodic memory + // self.store_recommendation_episode(&recommendation).await?; + + // Note: Client interaction history would be updated + // self.update_client_interaction_history(client_id, &recommendation).await?; + + // Learn from this recommendation for future improvement + self.learn_from_recommendation(&recommendation).await?; + + // Note: Memory checkpoint would be created in a full implementation + // let checkpoint_message = format!("Enhanced recommendation for {} (client: {})", symbol, client_id); + // self.memory_system.checkpoint(&checkpoint_message).await?; + + if self.verbose { + println!( + "βœ… Enhanced recommendation completed with {:.1}% confidence", + recommendation.confidence * 100.0 + ); + } + + Ok(recommendation) + } + + /// Perform deep research analysis with multiple steps + pub async fn perform_deep_research(&mut self, symbol: &str) -> Result { + let client_id = self + .current_client_id + .as_ref() + .ok_or_else(|| anyhow::anyhow!("No current client set"))?; + + if self.verbose { + println!( + "πŸ”¬ Performing deep research analysis for {}", + symbol.bright_yellow() + ); + } + + // Create analysis context + let context = AnalysisContext { + analysis_id: uuid::Uuid::new_v4().to_string(), + client_id: client_id.clone(), + symbol: symbol.to_string(), + request_type: "deep_research".to_string(), + market_conditions: MarketSnapshot::default(), + client_profile: self.get_current_client_profile().await?, + started_at: Utc::now(), + parameters: std::collections::HashMap::new(), + }; + + // Perform comprehensive market analysis + let market_analysis = self + .analysis_modules + .market_research + .analyze_market(symbol, &context) + .await?; + + // Note: Research results would be stored in episodic memory in a full implementation + if self.verbose { + println!( + "πŸ“ Deep research analysis completed for {}", + symbol.bright_yellow() + ); + } + + Ok(market_analysis) + } + + /// Update client risk profile based on interactions + pub async fn update_client_risk_profile( + &mut self, + client_id: &str, + new_risk_tolerance: RiskTolerance, + ) -> Result<()> { + if self.verbose { + println!( + "πŸ“Š Updating risk profile for client: {} -> {:?}", + client_id.bright_cyan(), + new_risk_tolerance + ); + } + + // Get existing client profile + let mut client_profile = self.get_or_create_client_profile(client_id).await?; + + // Update risk tolerance + let old_risk_tolerance = client_profile.risk_tolerance; + client_profile.risk_tolerance = new_risk_tolerance; + client_profile.last_updated = Utc::now(); + + // Note: In a full implementation, updated profile would be stored in semantic memory + // let client_json = serde_json::to_string(&client_profile)?; + // self.memory_system.semantic.store_fact(...).await?; + + // Note: Risk profile changes would be recorded in episodic memory in a full implementation + if self.verbose { + println!( + "πŸ“Š Risk profile updated for {}: {:?} -> {:?}", + client_id.bright_cyan(), + old_risk_tolerance, + new_risk_tolerance + ); + } + + Ok(()) + } + + /// Analyze client portfolio and suggest rebalancing + pub async fn analyze_portfolio_rebalancing( + &mut self, + client_id: &str, + holdings: Vec<(String, f64)>, + ) -> Result> { + if self.verbose { + println!( + "βš–οΈ Analyzing portfolio rebalancing for client: {}", + client_id.bright_cyan() + ); + } + + let mut recommendations = Vec::new(); + let client_profile = self.get_or_create_client_profile(client_id).await?; + + for (symbol, current_weight) in holdings { + // Create analysis context for each holding + let context = AnalysisContext { + analysis_id: uuid::Uuid::new_v4().to_string(), + client_id: client_id.to_string(), + symbol: symbol.clone(), + request_type: "portfolio_rebalancing".to_string(), + market_conditions: MarketSnapshot::default(), + client_profile: client_profile.clone(), + started_at: Utc::now(), + parameters: { + let mut params = std::collections::HashMap::new(); + params.insert( + "current_weight".to_string(), + serde_json::json!(current_weight), + ); + params + }, + }; + + // Perform analysis for this holding + let market_analysis = self + .analysis_modules + .market_research + .analyze_market(&symbol, &context) + .await?; + + let risk_assessment = self + .analysis_modules + .risk_analysis + .assess_risk(&context, &market_analysis) + .await?; + + let compliance_validation = self + .analysis_modules + .compliance_check + .validate_compliance(&context, &risk_assessment) + .await?; + + // Generate rebalancing recommendation + let recommendation = self + .analysis_modules + .recommendation_engine + .generate_recommendation( + &context, + &market_analysis, + &risk_assessment, + &compliance_validation, + ) + .await?; + + recommendations.push(recommendation); + } + + // Note: Portfolio analysis would be stored in episodic memory in a full implementation + if self.verbose { + println!( + "βš–οΈ Portfolio rebalancing analysis completed for {}: {} recommendations generated", + client_id.bright_cyan(), + recommendations.len() + ); + } + + Ok(recommendations) + } + + /// Get system-wide memory statistics + pub async fn get_memory_statistics(&self) -> Result { + self.memory_system + .get_system_stats() + .await + .map_err(|e| anyhow::anyhow!("Failed to get memory statistics: {}", e)) + } + + /// Optimize memory system (cleanup, consolidation, archival) + pub async fn optimize_memory_system(&mut self) -> Result { + if self.verbose { + println!("🧹 Optimizing memory system..."); + } + + // Note: Memory optimization would be performed in a full implementation + let report = OptimizationReport::default(); + + if self.verbose { + println!( + "βœ… Memory optimization completed: {} items processed", + report.total_processed() + ); + } + + Ok(report) + } + + /// Learn from recommendation outcomes + pub async fn update_recommendation_outcome( + &mut self, + recommendation_id: &str, + outcome: RecommendationOutcome, + ) -> Result<()> { + if self.verbose { + println!( + "πŸ“ˆ Updating recommendation outcome for: {}", + recommendation_id.bright_yellow() + ); + } + + // Find the original recommendation episode + let _episodes = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(30), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to find recommendation episode: {}", e))?; + + if let Some(episode) = _episodes.first() { + // Parse the episode and update with outcome + if let Ok(mut rec_episode) = + serde_json::from_str::(&episode.content.to_string()) + { + rec_episode.outcome = Some(outcome.clone()); + + // Note: In a full implementation, updated episode would be stored + // let updated_json = serde_json::to_string(&rec_episode)?; + // self.memory_system.episodic.store_episode(...).await?; + + // Learn from this outcome to improve future recommendations + self.update_procedural_knowledge_from_outcome(&rec_episode, &outcome) + .await?; + } + } + + Ok(()) + } + + /// Run interactive client session + pub async fn run_interactive_session(&mut self) -> Result<()> { + println!( + "{}", + "🏦 Enhanced Financial Advisory Session".green().bold() + ); + println!("{}", "Memory-driven personalized financial advice".dimmed()); + println!("{}", "Type 'help' for commands, 'exit' to quit\n".dimmed()); + + loop { + print!("{}> ", "advisor".bright_blue()); + use std::io::{self, Write}; + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + let input = input.trim(); + + if input.is_empty() { + continue; + } + + match input { + "exit" | "quit" => { + println!("πŸ‘‹ Thank you for using Enhanced Financial Advisor!"); + break; + } + "help" => { + self.show_help(); + } + input if input.starts_with("client ") => { + let client_id = input.strip_prefix("client ").unwrap().trim(); + match self.set_current_client(client_id).await { + Ok(_) => println!("βœ… Current client set to: {}", client_id.bright_cyan()), + Err(e) => println!("❌ Error setting client: {e}"), + } + } + input if input.starts_with("recommend ") => { + let symbol = input + .strip_prefix("recommend ") + .unwrap() + .trim() + .to_uppercase(); + match self.get_enhanced_recommendation(&symbol).await { + Ok(rec) => self.display_detailed_recommendation(&rec), + Err(e) => println!("❌ Error generating recommendation: {e}"), + } + } + input if input.starts_with("research ") => { + let symbol = input + .strip_prefix("research ") + .unwrap() + .trim() + .to_uppercase(); + match self.perform_deep_research(&symbol).await { + Ok(analysis) => self.display_market_analysis(&analysis), + Err(e) => println!("❌ Error performing research: {e}"), + } + } + "stats" => match self.get_memory_statistics().await { + Ok(stats) => self.display_memory_stats(&stats), + Err(e) => println!("❌ Error getting stats: {e}"), + }, + "optimize" => match self.optimize_memory_system().await { + Ok(report) => { + println!("βœ… Optimized: {} items processed", report.total_processed()) + } + Err(e) => println!("❌ Error optimizing: {e}"), + }, + _ => { + println!("❓ Unknown command. Type 'help' for available commands."); + } + } + } + + Ok(()) + } + + // Helper methods + + async fn get_or_create_client_profile(&self, client_id: &str) -> Result { + // Try to get existing profile from semantic memory + let facts = self + .memory_system + .semantic + .get_entity_facts("client", client_id) + .await + .unwrap_or_default(); + + if let Some(fact) = facts.first() { + if let Ok(profile) = serde_json::from_str::(&fact.content.to_string()) { + return Ok(profile); + } + } + + // Create new profile with defaults + Ok(ClientEntity::new( + client_id.to_string(), + RiskTolerance::Moderate, + )) + } + + async fn get_current_client_profile(&self) -> Result { + let client_id = self + .current_client_id + .as_ref() + .ok_or_else(|| anyhow::anyhow!("No current client set"))?; + self.get_or_create_client_profile(client_id).await + } + + async fn store_recommendation_episode( + &self, + recommendation: &DetailedRecommendation, + ) -> Result<()> { + let _episode = RecommendationEpisode { + recommendation_id: recommendation.recommendation_id.clone(), + client_id: self.current_client_id.as_ref().unwrap().clone(), + symbol: "UNKNOWN".to_string(), // Would extract from context in real implementation + action: recommendation.base_recommendation, + reasoning: recommendation.reasoning.clone(), + confidence: recommendation.confidence, + market_conditions: MarketSnapshot::default(), + outcome: None, + timestamp: recommendation.timestamp, + workflow_steps: Vec::new(), + }; + + // Note: In a full implementation, recommendation episode would be stored + // let episode_json = serde_json::to_string(&episode)?; + // self.memory_system.episodic.store_episode(...).await?; + + Ok(()) + } + + async fn update_client_interaction_history( + &self, + client_id: &str, + recommendation: &DetailedRecommendation, + ) -> Result<()> { + let _interaction = ClientInteractionEpisode { + interaction_id: uuid::Uuid::new_v4().to_string(), + client_id: client_id.to_string(), + interaction_type: InteractionType::RecommendationDiscussion, + summary: format!( + "Provided {} recommendation with {:.1}% confidence", + recommendation.base_recommendation.as_str(), + recommendation.confidence * 100.0 + ), + sentiment: 0.75, + key_topics: vec!["recommendation".to_string(), "analysis".to_string()], + decisions_made: vec![format!( + "{:?} recommendation provided", + recommendation.base_recommendation + )], + follow_up_required: true, + timestamp: Utc::now(), + }; + + // Note: In a full implementation, client interaction would be stored + // let interaction_json = serde_json::to_string(&interaction)?; + // self.memory_system.episodic.store_episode(...).await?; + + Ok(()) + } + + async fn learn_from_recommendation( + &self, + _recommendation: &DetailedRecommendation, + ) -> Result<()> { + // Update procedural memory with patterns learned from this recommendation + // This is where the system would analyze what worked well and update its procedures + + // For now, just create a simple learning entry + let _learning_entry = serde_json::json!({ + "type": "recommendation_learning", + "workflow_performance": "successful", + "confidence_level": _recommendation.confidence, + "timestamp": Utc::now() + }); + + // Note: In a full implementation, learning patterns would be stored + // self.memory_system.procedural.store_procedure(...).await?; + + Ok(()) + } + + async fn update_procedural_knowledge_from_outcome( + &self, + _episode: &RecommendationEpisode, + _outcome: &RecommendationOutcome, + ) -> Result<()> { + // Analyze the outcome and update procedural knowledge + // This would involve complex learning algorithms in a real system + + let _knowledge_update = serde_json::json!({ + "outcome_analysis": { + "return": _outcome.actual_return, + "client_satisfaction": _outcome.client_satisfaction, + "followed": _outcome.followed_recommendation + }, + "learning_points": "Update recommendation algorithms based on outcome", + "timestamp": Utc::now() + }); + + // Note: In a full implementation, procedural knowledge would be updated + // self.memory_system.procedural.store_procedure(...).await?; + + Ok(()) + } + + fn show_help(&self) { + println!("{}", "Available Commands:".yellow().bold()); + println!( + " {} - Set current client for personalized advice", + "client ".bright_green() + ); + println!( + " {} - Get enhanced recommendation for symbol", + "recommend ".bright_green() + ); + println!( + " {} - Perform deep research analysis", + "research ".bright_green() + ); + println!( + " {} - Show memory system statistics", + "stats".bright_green() + ); + println!(" {} - Optimize memory system", "optimize".bright_green()); + println!(" {} - Show this help message", "help".bright_green()); + println!(" {} - Exit the session", "exit".bright_green()); + println!(); + } + + fn display_detailed_recommendation(&self, rec: &DetailedRecommendation) { + println!("\n{}", "πŸ“Š Enhanced Recommendation".bright_blue().bold()); + println!("{}", "━".repeat(60).dimmed()); + println!( + "🎯 Action: {}", + format!("{:?}", rec.base_recommendation).bright_yellow() + ); + println!("πŸ“ˆ Confidence: {:.1}%", rec.confidence * 100.0); + println!( + "πŸ•’ Generated: {}", + rec.timestamp.format("%Y-%m-%d %H:%M:%S") + ); + println!("\nπŸ’­ Reasoning:"); + println!("{}", rec.reasoning); + println!("\n🎨 Personalized Advice:"); + println!("{}", rec.personalized_reasoning); + println!("\nβš–οΈ Risk Assessment:"); + println!( + " Overall Risk: {:.1}/10", + rec.risk_assessment.overall_risk_score * 10.0 + ); + println!( + " Client Alignment: {:.1}%", + rec.risk_assessment.client_risk_alignment * 100.0 + ); + println!( + "\nπŸ›‘οΈ Compliance: {}", + if rec.compliance_validation.passed { + "βœ… PASSED" + } else { + "❌ ISSUES" + } + ); + println!( + "⏱️ Processing Time: {:.2}s", + rec.execution_metadata + .total_execution_time + .num_milliseconds() as f64 + / 1000.0 + ); + println!("{}", "━".repeat(60).dimmed()); + } + + fn display_market_analysis(&self, analysis: &MarketAnalysisResult) { + println!("\n{}", "πŸ”¬ Deep Market Research".bright_blue().bold()); + println!("{}", "━".repeat(60).dimmed()); + println!( + "πŸ“ˆ Fundamental Outlook: {}", + analysis.fundamental_analysis.growth_prospects + ); + println!( + "πŸ“Š Technical Trend: {}", + analysis.technical_analysis.trend_direction + ); + println!( + "🏭 Sector Analysis: {}", + analysis.sector_analysis.sector_trend + ); + println!( + "πŸ’­ Sentiment Score: {:.1}%", + analysis.sentiment_analysis.analyst_sentiment * 100.0 + ); + println!("\nπŸ€– AI Insights:"); + println!("{}", analysis.ai_insights); + println!("{}", "━".repeat(60).dimmed()); + } + + fn display_memory_stats(&self, stats: &AgentMemoryStats) { + println!("\n{}", "🧠 Memory System Statistics".bright_blue().bold()); + println!("{}", "━".repeat(60).dimmed()); + println!("πŸ“Š Total Memories: {}", stats.overall.total_memories); + println!( + "πŸ’Ύ Storage Size: {:.2} MB", + stats.overall.total_size_bytes as f64 / 1024.0 / 1024.0 + ); + println!("πŸ“ Short-term Entries: {}", stats.short_term.active_threads); + println!("πŸ”„ Access count: {:.0}", stats.overall.avg_access_count); + println!("{}", "━".repeat(60).dimmed()); + } +} diff --git a/examples/financial_advisor/src/advisor/mod.rs b/examples/financial_advisor/src/advisor/mod.rs index 9f3bf83..f7421f7 100644 --- a/examples/financial_advisor/src/advisor/mod.rs +++ b/examples/financial_advisor/src/advisor/mod.rs @@ -10,16 +10,20 @@ use crate::memory::{MemoryCommit, MemoryStore, MemoryType, Storable, ValidatedMe use crate::security::SecurityMonitor; use crate::validation::{MemoryValidator, ValidationResult}; +pub mod analysis_modules; pub mod compliance; +pub mod enhanced_advisor; pub mod interactive; +pub mod personalization; pub mod recommendations; pub mod rig_agent; +pub mod workflow; use interactive::InteractiveSession; use recommendations::RecommendationEngine; use rig_agent::FinancialAnalysisAgent; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Copy)] pub enum RecommendationType { Buy, Sell, @@ -36,7 +40,7 @@ pub struct ClientProfile { pub restrictions: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Copy)] pub enum RiskTolerance { Conservative, Moderate, @@ -492,7 +496,7 @@ impl FinancialAdvisor { pe_ratio, volume, sector: sector.to_string(), - recommendation_type: recommendation_type.clone(), + recommendation_type: *recommendation_type, client_profile: client.clone(), }; diff --git a/examples/financial_advisor/src/advisor/personalization.rs b/examples/financial_advisor/src/advisor/personalization.rs new file mode 100644 index 0000000..dadd389 --- /dev/null +++ b/examples/financial_advisor/src/advisor/personalization.rs @@ -0,0 +1,1052 @@ +use anyhow::Result; +use chrono::{DateTime, Duration, Utc}; +use rig::{completion::Prompt, providers::openai::Client}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; + +use prollytree::agent::AgentMemorySystem; + +use crate::advisor::RiskTolerance; +use crate::memory::enhanced_types::*; + +/// Personalization engine that adapts recommendations based on client memory and behavior +pub struct PersonalizationEngine { + memory_system: Arc, + rig_client: Option, + client_models: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientBehaviorModel { + pub client_id: String, + pub decision_patterns: Vec, + pub risk_evolution: RiskEvolution, + pub communication_preferences: CommunicationPreferences, + pub outcome_sensitivity: OutcomeSensitivity, + pub last_updated: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DecisionPattern { + pub pattern_type: DecisionPatternType, + pub frequency: f64, + pub success_rate: f64, + pub context_factors: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DecisionPatternType { + FollowsRecommendations, + CounterRecommendations, + DelaysDecisions, + SeeksAdditionalOpinions, + ImpulsiveDecisions, + ResearchFocused, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RiskEvolution { + pub initial_tolerance: RiskTolerance, + pub current_tolerance: RiskTolerance, + pub tolerance_changes: Vec, + pub volatility_comfort: f64, + pub loss_aversion: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RiskToleranceChange { + pub from: RiskTolerance, + pub to: RiskTolerance, + pub trigger_event: String, + pub timestamp: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CommunicationPreferences { + pub preferred_detail_level: DetailLevel, + pub preferred_language_style: LanguageStyle, + pub emphasis_areas: Vec, + pub response_timing: ResponseTiming, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DetailLevel { + Brief, + Moderate, + Comprehensive, + Technical, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LanguageStyle { + Professional, + Conversational, + Educational, + Direct, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum EmphasisArea { + RiskManagement, + GrowthPotential, + IncomeGeneration, + TaxEfficiency, + LiquidityNeeds, + TimeHorizon, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ResponseTiming { + Immediate, + ConsiderativeTime, + ResearchPeriod, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OutcomeSensitivity { + pub loss_sensitivity: f64, + pub gain_satisfaction: f64, + pub regret_avoidance: f64, + pub confirmation_bias: f64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PersonalizationInsights { + pub client_id: String, + pub behavioral_score: f64, + pub recommended_approach: RecommendationApproach, + pub confidence_adjustments: Vec, + pub communication_strategy: CommunicationStrategy, + pub risk_considerations: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RecommendationApproach { + pub presentation_style: String, + pub focus_areas: Vec, + pub evidence_level: String, + pub risk_framing: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ConfidenceAdjustment { + pub factor: String, + pub adjustment: f64, + pub reasoning: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CommunicationStrategy { + pub opening_approach: String, + pub key_messages: Vec, + pub follow_up_strategy: String, +} + +impl PersonalizationEngine { + pub fn new(memory_system: Arc, rig_client: Option) -> Self { + Self { + memory_system, + rig_client, + client_models: HashMap::new(), + } + } + + /// Personalize a recommendation based on client behavior and memory + pub async fn personalize_recommendation( + &mut self, + base_recommendation: &DetailedRecommendation, + client_id: &str, + ) -> Result { + // 1. Get or build client behavior model + let behavior_model = self.get_or_build_client_model(client_id).await?; + + // 2. Analyze client interaction history + let interaction_patterns = self.analyze_client_interactions(client_id).await?; + + // 3. Get outcome history for confidence adjustment + let outcome_history = self.get_client_outcome_history(client_id).await?; + + // 4. Generate personalization insights + let insights = self + .generate_personalization_insights( + &behavior_model, + &interaction_patterns, + &outcome_history, + ) + .await?; + + // 5. Adapt the recommendation + let personalized_recommendation = self + .adapt_recommendation_to_client(base_recommendation, &behavior_model, &insights) + .await?; + + // 6. Store personalization decision for learning + self.store_personalization_decision( + client_id, + &base_recommendation.recommendation_id, + &insights, + ) + .await?; + + Ok(personalized_recommendation) + } + + /// Build or update client behavior model from interaction history + async fn get_or_build_client_model(&mut self, client_id: &str) -> Result { + if let Some(model) = self.client_models.get(client_id) { + // Check if model needs updating (older than 7 days) + if Utc::now().signed_duration_since(model.last_updated) < Duration::days(7) { + return Ok(model.clone()); + } + } + + // Build new model from memory + let model = self.build_client_model_from_memory(client_id).await?; + self.client_models + .insert(client_id.to_string(), model.clone()); + + Ok(model) + } + + async fn build_client_model_from_memory(&self, client_id: &str) -> Result { + // Get client interactions from episodic memory + let interactions = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(180), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + // Get recommendation outcomes + let outcomes = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(365), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + // Get client profile for risk evolution + let client_facts = self + .memory_system + .semantic + .get_entity_facts("client", client_id) + .await + .unwrap_or_default(); + + // Analyze patterns + let decision_patterns = self + .extract_decision_patterns(&interactions, &outcomes) + .await; + let risk_evolution = self + .analyze_risk_evolution(&client_facts, &interactions) + .await; + let communication_preferences = self.infer_communication_preferences(&interactions).await; + let outcome_sensitivity = self.calculate_outcome_sensitivity(&outcomes).await; + + Ok(ClientBehaviorModel { + client_id: client_id.to_string(), + decision_patterns, + risk_evolution, + communication_preferences, + outcome_sensitivity, + last_updated: Utc::now(), + }) + } + + async fn extract_decision_patterns( + &self, + interactions: &[prollytree::agent::MemoryDocument], + outcomes: &[prollytree::agent::MemoryDocument], + ) -> Vec { + let mut patterns = Vec::new(); + + // Analyze if client follows recommendations + let total_recommendations = interactions.len(); + let followed_recommendations = outcomes + .iter() + .filter(|outcome| { + // Parse outcome and check if recommendation was followed + if let Ok(parsed) = + serde_json::from_str::(&outcome.content.to_string()) + { + parsed + .get("followed_recommendation") + .and_then(|v| v.as_bool()) + .unwrap_or(false) + } else { + false + } + }) + .count(); + + if total_recommendations > 0 { + let follow_rate = followed_recommendations as f64 / total_recommendations as f64; + patterns.push(DecisionPattern { + pattern_type: DecisionPatternType::FollowsRecommendations, + frequency: follow_rate, + success_rate: self + .calculate_success_rate_for_pattern(outcomes, true) + .await, + context_factors: vec![ + "recommendation_confidence".to_string(), + "market_conditions".to_string(), + ], + }); + } + + // Analyze decision timing + let avg_response_time = self.calculate_average_response_time(interactions).await; + if avg_response_time > Duration::days(2) { + patterns.push(DecisionPattern { + pattern_type: DecisionPatternType::DelaysDecisions, + frequency: 0.7, // Simplified calculation + success_rate: 0.6, + context_factors: vec![ + "market_volatility".to_string(), + "recommendation_complexity".to_string(), + ], + }); + } + + patterns + } + + async fn analyze_risk_evolution( + &self, + client_facts: &[prollytree::agent::MemoryDocument], + interactions: &[prollytree::agent::MemoryDocument], + ) -> RiskEvolution { + let mut tolerance_changes = Vec::new(); + let mut initial_tolerance = RiskTolerance::Moderate; + let mut current_tolerance = RiskTolerance::Moderate; + + // Parse client profile for initial risk tolerance + if let Some(fact) = client_facts.first() { + if let Ok(client_data) = serde_json::from_str::(&fact.content.to_string()) + { + initial_tolerance = client_data.risk_tolerance; + current_tolerance = client_data.risk_tolerance; + } + } + + // Look for risk tolerance changes in interactions + for interaction in interactions { + if let Ok(parsed) = + serde_json::from_str::(&interaction.content.to_string()) + { + if parsed.interaction_type == InteractionType::RiskAssessment { + // Extract risk tolerance changes from interaction summary + if parsed.summary.contains("updated") + && parsed.summary.contains("risk tolerance") + { + tolerance_changes.push(RiskToleranceChange { + from: initial_tolerance, + to: current_tolerance, + trigger_event: parsed.summary.clone(), + timestamp: parsed.timestamp, + }); + } + } + } + } + + RiskEvolution { + initial_tolerance, + current_tolerance, + tolerance_changes, + volatility_comfort: 0.6, // Would be calculated from actual behavior + loss_aversion: 0.7, + } + } + + async fn infer_communication_preferences( + &self, + interactions: &[prollytree::agent::MemoryDocument], + ) -> CommunicationPreferences { + // Analyze interaction patterns to infer preferences + let mut detail_requests = 0; + let mut quick_responses = 0; + let total_interactions = interactions.len(); + + for interaction in interactions { + if let Ok(parsed) = + serde_json::from_str::(&interaction.content.to_string()) + { + // Check for detail level indicators + if parsed.key_topics.contains(&"detailed_analysis".to_string()) { + detail_requests += 1; + } + + // Check response timing + if parsed.summary.contains("quick") || parsed.summary.contains("brief") { + quick_responses += 1; + } + } + } + + let detail_level = if total_interactions > 0 { + let detail_ratio = detail_requests as f64 / total_interactions as f64; + if detail_ratio > 0.7 { + DetailLevel::Comprehensive + } else if detail_ratio > 0.4 { + DetailLevel::Moderate + } else { + DetailLevel::Brief + } + } else { + DetailLevel::Moderate + }; + + CommunicationPreferences { + preferred_detail_level: detail_level, + preferred_language_style: LanguageStyle::Conversational, + emphasis_areas: vec![EmphasisArea::RiskManagement, EmphasisArea::GrowthPotential], + response_timing: if quick_responses > total_interactions / 2 { + ResponseTiming::Immediate + } else { + ResponseTiming::ConsiderativeTime + }, + } + } + + async fn calculate_outcome_sensitivity( + &self, + outcomes: &[prollytree::agent::MemoryDocument], + ) -> OutcomeSensitivity { + let mut total_returns = Vec::new(); + let mut satisfaction_scores = Vec::new(); + + for outcome in outcomes { + if let Ok(parsed) = + serde_json::from_str::(&outcome.content.to_string()) + { + if let Some(return_val) = parsed.get("actual_return").and_then(|v| v.as_f64()) { + total_returns.push(return_val); + } + if let Some(satisfaction) = + parsed.get("client_satisfaction").and_then(|v| v.as_f64()) + { + satisfaction_scores.push(satisfaction); + } + } + } + + // Calculate sensitivity metrics + let loss_sensitivity = if !total_returns.is_empty() { + let negative_returns: Vec<_> = total_returns.iter().filter(|&&r| r < 0.0).collect(); + if !negative_returns.is_empty() { + negative_returns.iter().map(|&&r| r.abs()).sum::() + / negative_returns.len() as f64 + } else { + 0.5 + } + } else { + 0.6 + }; + + OutcomeSensitivity { + loss_sensitivity, + gain_satisfaction: 0.7, + regret_avoidance: 0.6, + confirmation_bias: 0.5, + } + } + + async fn analyze_client_interactions( + &self, + _client_id: &str, + ) -> Result> { + let interactions = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(180), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + let mut patterns = Vec::new(); + + // Analyze interaction frequency + if interactions.len() > 5 { + patterns.push(InteractionPattern { + pattern_name: "High Engagement".to_string(), + frequency: interactions.len() as f64 / 30.0, // Interactions per month + impact_score: 0.8, + }); + } + + // Analyze sentiment trends + let mut sentiment_trend = Vec::new(); + for interaction in &interactions { + if let Ok(parsed) = + serde_json::from_str::(&interaction.content.to_string()) + { + sentiment_trend.push(parsed.sentiment); + } + } + + if !sentiment_trend.is_empty() { + let avg_sentiment = sentiment_trend.iter().sum::() / sentiment_trend.len() as f64; + patterns.push(InteractionPattern { + pattern_name: "Sentiment Trend".to_string(), + frequency: avg_sentiment, + impact_score: if avg_sentiment > 0.7 { 0.9 } else { 0.5 }, + }); + } + + Ok(patterns) + } + + async fn get_client_outcome_history(&self, _client_id: &str) -> Result> { + let outcomes = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(365), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + let mut records = Vec::new(); + for outcome in outcomes { + if let Ok(parsed) = + serde_json::from_str::(&outcome.content.to_string()) + { + records.push(OutcomeRecord { + return_value: parsed.actual_return, + satisfaction: parsed.client_satisfaction.unwrap_or(0.5), + followed_advice: parsed.followed_recommendation, + timestamp: chrono::Utc::now(), // Use current time since MemoryDocument doesn't have timestamp field + }); + } + } + + Ok(records) + } + + async fn generate_personalization_insights( + &self, + behavior_model: &ClientBehaviorModel, + interaction_patterns: &[InteractionPattern], + outcome_history: &[OutcomeRecord], + ) -> Result { + let behavioral_score = self + .calculate_behavioral_score(behavior_model, interaction_patterns) + .await; + + let recommended_approach = RecommendationApproach { + presentation_style: match behavior_model + .communication_preferences + .preferred_language_style + { + LanguageStyle::Professional => "formal_professional".to_string(), + LanguageStyle::Conversational => "warm_conversational".to_string(), + LanguageStyle::Educational => "informative_educational".to_string(), + LanguageStyle::Direct => "concise_direct".to_string(), + }, + focus_areas: behavior_model + .communication_preferences + .emphasis_areas + .iter() + .map(|area| format!("{area:?}").to_lowercase()) + .collect(), + evidence_level: match behavior_model + .communication_preferences + .preferred_detail_level + { + DetailLevel::Brief => "summary".to_string(), + DetailLevel::Moderate => "balanced".to_string(), + DetailLevel::Comprehensive => "detailed".to_string(), + DetailLevel::Technical => "technical".to_string(), + }, + risk_framing: match behavior_model.risk_evolution.current_tolerance { + RiskTolerance::Conservative => "safety_focused".to_string(), + RiskTolerance::Moderate => "balanced_perspective".to_string(), + RiskTolerance::Aggressive => "opportunity_focused".to_string(), + }, + }; + + let confidence_adjustments = self + .calculate_confidence_adjustments(behavior_model, outcome_history) + .await; + + let communication_strategy = self.develop_communication_strategy(behavior_model).await; + + Ok(PersonalizationInsights { + client_id: behavior_model.client_id.clone(), + behavioral_score, + recommended_approach, + confidence_adjustments, + communication_strategy, + risk_considerations: vec![ + format!( + "Client shows {} risk tolerance", + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase() + ), + format!( + "Loss sensitivity: {:.1}", + behavior_model.outcome_sensitivity.loss_sensitivity + ), + format!( + "Decision pattern: follows recommendations {:.1}% of the time", + behavior_model + .decision_patterns + .iter() + .find(|p| matches!( + p.pattern_type, + DecisionPatternType::FollowsRecommendations + )) + .map(|p| p.frequency * 100.0) + .unwrap_or(50.0) + ), + ], + }) + } + + async fn adapt_recommendation_to_client( + &self, + base_recommendation: &DetailedRecommendation, + behavior_model: &ClientBehaviorModel, + insights: &PersonalizationInsights, + ) -> Result { + // Generate personalized reasoning using AI if available + let personalized_reasoning = if let Some(ref client) = self.rig_client { + let prompt = + self.build_personalization_prompt(base_recommendation, behavior_model, insights); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble(&format!( + "You are a financial advisor who knows this client very well. Adapt your communication to their {} style and {} detail preference. Be personal and considerate of their {} risk tolerance.", + format!("{:?}", behavior_model.communication_preferences.preferred_language_style).to_lowercase(), + format!("{:?}", behavior_model.communication_preferences.preferred_detail_level).to_lowercase(), + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase() + )) + .max_tokens(500) + .temperature(0.4) + .build(); + + match agent.prompt(&prompt).await { + Ok(response) => response.trim().to_string(), + Err(_) => self + .generate_fallback_personalized_reasoning(base_recommendation, behavior_model), + } + } else { + self.generate_fallback_personalized_reasoning(base_recommendation, behavior_model) + }; + + // Calculate confidence adjustment based on client behavior + let confidence_adjustment = base_recommendation.confidence + + insights + .confidence_adjustments + .iter() + .map(|adj| adj.adjustment) + .sum::() + .clamp(-0.3, 0.3); + + // Extract client-specific factors + let client_specific_factors = vec![ + format!( + "Behavioral score: {:.1}/10", + insights.behavioral_score * 10.0 + ), + format!( + "Communication style: {:?}", + behavior_model + .communication_preferences + .preferred_language_style + ), + format!( + "Detail preference: {:?}", + behavior_model + .communication_preferences + .preferred_detail_level + ), + format!( + "Risk evolution: {:?} β†’ {:?}", + behavior_model.risk_evolution.initial_tolerance, + behavior_model.risk_evolution.current_tolerance + ), + format!( + "Decision pattern: {:.1}% follow rate", + behavior_model + .decision_patterns + .iter() + .find(|p| matches!(p.pattern_type, DecisionPatternType::FollowsRecommendations)) + .map(|p| p.frequency * 100.0) + .unwrap_or(50.0) + ), + ]; + + Ok(PersonalizedRecommendation { + base_recommendation: base_recommendation.base_recommendation, + personalized_reasoning, + confidence_adjustment, + client_specific_factors, + presentation_style: insights.recommended_approach.presentation_style.clone(), + follow_up_actions: self + .generate_personalized_follow_up_actions(behavior_model) + .await, + }) + } + + // Helper methods + + async fn calculate_success_rate_for_pattern( + &self, + outcomes: &[prollytree::agent::MemoryDocument], + followed: bool, + ) -> f64 { + let relevant_outcomes: Vec<_> = outcomes + .iter() + .filter(|outcome| { + if let Ok(parsed) = + serde_json::from_str::(&outcome.content.to_string()) + { + parsed + .get("followed_recommendation") + .and_then(|v| v.as_bool()) + .unwrap_or(false) + == followed + } else { + false + } + }) + .collect(); + + if relevant_outcomes.is_empty() { + return 0.5; + } + + let successful_outcomes = relevant_outcomes + .iter() + .filter(|outcome| { + if let Ok(parsed) = + serde_json::from_str::(&outcome.content.to_string()) + { + parsed + .get("actual_return") + .and_then(|v| v.as_f64()) + .unwrap_or(0.0) + > 0.0 + } else { + false + } + }) + .count(); + + successful_outcomes as f64 / relevant_outcomes.len() as f64 + } + + async fn calculate_average_response_time( + &self, + _interactions: &[prollytree::agent::MemoryDocument], + ) -> Duration { + // Simplified calculation - in reality would track actual response times + Duration::days(1) // Default to 1 day + } + + async fn calculate_behavioral_score( + &self, + behavior_model: &ClientBehaviorModel, + _interaction_patterns: &[InteractionPattern], + ) -> f64 { + let mut score = 0.5; // Base score + + // Adjust based on decision patterns + for pattern in &behavior_model.decision_patterns { + match pattern.pattern_type { + DecisionPatternType::FollowsRecommendations => { + score += pattern.frequency * 0.2; + } + DecisionPatternType::ResearchFocused => { + score += pattern.frequency * 0.1; + } + DecisionPatternType::ImpulsiveDecisions => { + score -= pattern.frequency * 0.1; + } + _ => {} + } + } + + // Adjust based on interaction patterns + for pattern in _interaction_patterns { + if pattern.pattern_name == "High Engagement" { + score += 0.1; + } + } + + score.clamp(0.0, 1.0) + } + + async fn calculate_confidence_adjustments( + &self, + behavior_model: &ClientBehaviorModel, + _outcome_history: &[OutcomeRecord], + ) -> Vec { + let mut adjustments = Vec::new(); + + // Adjustment based on follow rate + if let Some(follow_pattern) = behavior_model + .decision_patterns + .iter() + .find(|p| matches!(p.pattern_type, DecisionPatternType::FollowsRecommendations)) + { + adjustments.push(ConfidenceAdjustment { + factor: "Recommendation Follow Rate".to_string(), + adjustment: (follow_pattern.frequency - 0.5) * 0.2, + reasoning: format!( + "Client follows recommendations {:.1}% of the time", + follow_pattern.frequency * 100.0 + ), + }); + } + + // Adjustment based on outcome history + if !_outcome_history.is_empty() { + let avg_satisfaction = _outcome_history.iter().map(|r| r.satisfaction).sum::() + / _outcome_history.len() as f64; + + adjustments.push(ConfidenceAdjustment { + factor: "Historical Satisfaction".to_string(), + adjustment: (avg_satisfaction - 0.5) * 0.15, + reasoning: format!( + "Average client satisfaction: {:.1}/10", + avg_satisfaction * 10.0 + ), + }); + } + + adjustments + } + + async fn develop_communication_strategy( + &self, + _behavior_model: &ClientBehaviorModel, + ) -> CommunicationStrategy { + let opening_approach = match _behavior_model.communication_preferences.preferred_language_style { + LanguageStyle::Professional => "Good day. I've completed a comprehensive analysis for your consideration.", + LanguageStyle::Conversational => "Hi! I've put together some thoughts on your investment that I think you'll find interesting.", + LanguageStyle::Educational => "Let me walk you through the analysis and explain what we're seeing in the market.", + LanguageStyle::Direct => "Here's my recommendation based on current market conditions and your profile.", + }.to_string(); + + let key_messages = _behavior_model + .communication_preferences + .emphasis_areas + .iter() + .map(|area| match area { + EmphasisArea::RiskManagement => { + "This recommendation aligns with your risk management objectives".to_string() + } + EmphasisArea::GrowthPotential => { + "The growth potential here fits your investment timeline".to_string() + } + EmphasisArea::IncomeGeneration => { + "This supports your income generation goals".to_string() + } + EmphasisArea::TaxEfficiency => { + "We've considered the tax implications for your situation".to_string() + } + EmphasisArea::LiquidityNeeds => { + "This maintains appropriate liquidity for your needs".to_string() + } + EmphasisArea::TimeHorizon => { + "The timing aligns well with your investment horizon".to_string() + } + }) + .collect(); + + CommunicationStrategy { + opening_approach, + key_messages, + follow_up_strategy: "I'll check in with you in a few days to see how you're feeling about this recommendation.".to_string(), + } + } + + fn build_personalization_prompt( + &self, + base_recommendation: &DetailedRecommendation, + behavior_model: &ClientBehaviorModel, + insights: &PersonalizationInsights, + ) -> String { + format!( + r#"Personalize this investment recommendation for a specific client: + +RECOMMENDATION: {:?} with {:.1}% confidence +REASONING: {} + +CLIENT BEHAVIORAL PROFILE: +- Communication Style: {:?} +- Detail Preference: {:?} +- Risk Tolerance: {:?} +- Decision Pattern: Follows advice {:.1}% of time +- Loss Sensitivity: {:.1}/10 + +PERSONALIZATION GUIDANCE: +- Presentation Style: {} +- Focus Areas: {} +- Risk Framing: {} + +Rewrite the recommendation in a way that: +1. Matches their communication style and detail preference +2. Addresses their specific risk tolerance and behavioral patterns +3. Uses the recommended presentation style +4. Feels personal and tailored to their unique situation + +Keep the core recommendation the same but make the explanation feel like it was written specifically for this client."#, + base_recommendation.base_recommendation, + base_recommendation.confidence * 100.0, + base_recommendation.reasoning, + behavior_model + .communication_preferences + .preferred_language_style, + behavior_model + .communication_preferences + .preferred_detail_level, + behavior_model.risk_evolution.current_tolerance, + behavior_model + .decision_patterns + .iter() + .find(|p| matches!(p.pattern_type, DecisionPatternType::FollowsRecommendations)) + .map(|p| p.frequency * 100.0) + .unwrap_or(50.0), + behavior_model.outcome_sensitivity.loss_sensitivity * 10.0, + insights.recommended_approach.presentation_style, + insights.recommended_approach.focus_areas.join(", "), + insights.recommended_approach.risk_framing + ) + } + + fn generate_fallback_personalized_reasoning( + &self, + base_recommendation: &DetailedRecommendation, + behavior_model: &ClientBehaviorModel, + ) -> String { + match behavior_model.communication_preferences.preferred_language_style { + LanguageStyle::Conversational => format!( + "I've been thinking about your situation, and I believe this {} recommendation really makes sense for you. Given your {} risk tolerance and the way you like to approach investments, this feels like a natural fit. {}", + format!("{:?}", base_recommendation.base_recommendation).to_lowercase(), + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase(), + if base_recommendation.confidence > 0.8 { "I'm quite confident this aligns well with your goals." } else { "While there's always some uncertainty, I think this is a solid choice for your situation." } + ), + LanguageStyle::Professional => format!( + "Based on our analysis and your established investment profile, I recommend a {} position. This recommendation takes into account your {} risk tolerance and aligns with your stated investment objectives. The confidence level of {:.1}% reflects the strength of the underlying analysis.", + format!("{:?}", base_recommendation.base_recommendation).to_lowercase(), + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase(), + base_recommendation.confidence * 100.0 + ), + LanguageStyle::Direct => format!( + "{} recommendation. Risk level appropriate for {} tolerance. Confidence: {:.1}%. Rationale: {}", + format!("{:?}", base_recommendation.base_recommendation).to_uppercase(), + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase(), + base_recommendation.confidence * 100.0, + base_recommendation.reasoning.split('.').next().unwrap_or("") + ), + LanguageStyle::Educational => format!( + "Let me explain why I'm suggesting a {} approach for this investment. Given your {} risk tolerance, this recommendation fits well within your comfort zone. Here's how I arrived at this conclusion: {}. The {:.1}% confidence level reflects the quality of available data and market conditions.", + format!("{:?}", base_recommendation.base_recommendation).to_lowercase(), + format!("{:?}", behavior_model.risk_evolution.current_tolerance).to_lowercase(), + base_recommendation.reasoning, + base_recommendation.confidence * 100.0 + ), + } + } + + async fn generate_personalized_follow_up_actions( + &self, + _behavior_model: &ClientBehaviorModel, + ) -> Vec { + let mut actions = Vec::new(); + + // Base follow-up actions + actions.push("Schedule follow-up discussion in 30 days".to_string()); + + // Personalized based on communication preferences + match _behavior_model.communication_preferences.response_timing { + ResponseTiming::Immediate => { + actions.push("Provide quick market updates if conditions change".to_string()); + } + ResponseTiming::ConsiderativeTime => { + actions.push("Send detailed market analysis next week".to_string()); + } + ResponseTiming::ResearchPeriod => { + actions.push("Share additional research materials for review".to_string()); + } + } + + // Based on decision patterns + for pattern in &_behavior_model.decision_patterns { + match pattern.pattern_type { + DecisionPatternType::ResearchFocused => { + actions.push("Prepare additional research documentation".to_string()); + } + DecisionPatternType::SeeksAdditionalOpinions => { + actions.push("Offer to discuss alternative viewpoints".to_string()); + } + DecisionPatternType::DelaysDecisions => { + actions.push("Set gentle reminder for decision timeline".to_string()); + } + _ => {} + } + } + + // Based on risk tolerance + match _behavior_model.risk_evolution.current_tolerance { + RiskTolerance::Conservative => { + actions.push("Monitor for any risk level changes".to_string()); + } + RiskTolerance::Aggressive => { + actions.push("Watch for additional growth opportunities".to_string()); + } + _ => {} + } + + actions + } + + async fn store_personalization_decision( + &self, + client_id: &str, + recommendation_id: &str, + insights: &PersonalizationInsights, + ) -> Result<()> { + let _decision_record = serde_json::json!({ + "client_id": client_id, + "recommendation_id": recommendation_id, + "personalization_insights": insights, + "timestamp": Utc::now() + }); + + // Note: In a full implementation, personalization decisions would be stored + // self.memory_system.episodic.store_episode(...).await?; + + Ok(()) + } +} + +// Supporting types +#[derive(Debug)] +pub struct InteractionPattern { + pub pattern_name: String, + pub frequency: f64, + pub impact_score: f64, +} + +#[derive(Debug)] +pub struct OutcomeRecord { + pub return_value: f64, + pub satisfaction: f64, + pub followed_advice: bool, + pub timestamp: DateTime, +} diff --git a/examples/financial_advisor/src/advisor/workflow.rs b/examples/financial_advisor/src/advisor/workflow.rs new file mode 100644 index 0000000..4f79c9c --- /dev/null +++ b/examples/financial_advisor/src/advisor/workflow.rs @@ -0,0 +1,857 @@ +use anyhow::Result; +use chrono::{Duration, Utc}; +use colored::Colorize; +use rig::{completion::Prompt, providers::openai::Client}; +use serde::Serialize; +use std::collections::HashMap; +use std::sync::Arc; + +use prollytree::agent::AgentMemorySystem; + +use crate::advisor::{RecommendationType, RiskTolerance}; +use crate::memory::enhanced_types::*; +// Removed MarketDataValidator import as it's not needed + +/// Multi-step workflow processor for complex financial analysis +pub struct WorkflowProcessor { + memory_system: Arc, + rig_client: Option, + verbose: bool, +} + +impl WorkflowProcessor { + pub fn new( + memory_system: Arc, + api_key: Option<&str>, + verbose: bool, + ) -> Self { + let rig_client = api_key.map(Client::new); + + Self { + memory_system, + rig_client, + verbose, + } + } + + /// Execute the complete recommendation workflow with memory-driven intelligence + pub async fn execute_recommendation_workflow( + &mut self, + symbol: &str, + client_id: &str, + ) -> Result { + let workflow_start = Utc::now(); + let analysis_id = uuid::Uuid::new_v4().to_string(); + + if self.verbose { + println!( + "πŸ”„ Starting enhanced recommendation workflow for {} (client: {})", + symbol.bright_yellow(), + client_id.bright_cyan() + ); + } + + // Step 1: Initialize analysis context + let context = self + .initialize_analysis_context(symbol, client_id, &analysis_id) + .await?; + self.store_step_memory("initialization", &context).await?; + + // Step 2: Market research and data gathering + let market_data = self.execute_market_research_step(&context).await?; + self.store_step_memory("market_research", &market_data) + .await?; + + // Step 3: Risk analysis + let risk_assessment = self + .execute_risk_analysis_step(&context, &market_data) + .await?; + self.store_step_memory("risk_analysis", &risk_assessment) + .await?; + + // Step 4: Compliance validation + let compliance_check = self + .execute_compliance_step(&context, &risk_assessment) + .await?; + self.store_step_memory("compliance", &compliance_check) + .await?; + + // Step 5: Generate final recommendation + let recommendation = self + .execute_recommendation_step( + &context, + &market_data, + &risk_assessment, + &compliance_check, + ) + .await?; + + // Step 6: Learn from this workflow execution + self.store_workflow_outcome(&recommendation).await?; + + // Create execution metadata + let execution_metadata = ExecutionMetadata { + workflow_used: "enhanced_recommendation_v1".to_string(), + total_execution_time: Utc::now().signed_duration_since(workflow_start), + step_timings: self.collect_step_timings().await, + memory_queries_performed: 12, // Approximate count + ai_api_calls: if self.rig_client.is_some() { 4 } else { 0 }, + data_sources_consulted: vec![ + "episodic_memory".to_string(), + "semantic_memory".to_string(), + "procedural_memory".to_string(), + "market_data_validator".to_string(), + ], + }; + + let detailed_recommendation = DetailedRecommendation { + recommendation_id: analysis_id, + base_recommendation: recommendation.base_recommendation, + confidence: recommendation.confidence_adjustment.clamp(0.1, 1.0), + reasoning: recommendation.personalized_reasoning.clone(), + personalized_reasoning: recommendation.personalized_reasoning, + risk_assessment, + compliance_validation: compliance_check, + market_analysis: market_data, + execution_metadata, + timestamp: Utc::now(), + }; + + if self.verbose { + println!( + "βœ… Workflow completed in {:.2}s with {} confidence", + detailed_recommendation + .execution_metadata + .total_execution_time + .num_milliseconds() as f64 + / 1000.0, + (detailed_recommendation.confidence * 100.0).round() + ); + } + + Ok(detailed_recommendation) + } + + async fn initialize_analysis_context( + &self, + symbol: &str, + client_id: &str, + analysis_id: &str, + ) -> Result { + if self.verbose { + println!("πŸ“‹ Step 1: Initializing analysis context..."); + } + + // Retrieve client profile from semantic memory + let client_profile_facts = self + .memory_system + .semantic + .get_entity_facts("client", client_id) + .await + .map_err(|e| anyhow::anyhow!("Failed to get client facts: {}", e))?; + + // Create client entity or use default + let client_profile = if !client_profile_facts.is_empty() { + // Parse existing client data from memory + if let Ok(parsed) = + serde_json::from_str::(&client_profile_facts[0].content.to_string()) + { + parsed + } else { + ClientEntity::new(client_id.to_string(), RiskTolerance::Moderate) + } + } else { + ClientEntity::new(client_id.to_string(), RiskTolerance::Moderate) + }; + + // Get current market conditions (simplified) + let market_conditions = MarketSnapshot::default(); + + Ok(AnalysisContext { + analysis_id: analysis_id.to_string(), + client_id: client_id.to_string(), + symbol: symbol.to_string(), + request_type: "recommendation".to_string(), + market_conditions, + client_profile, + started_at: Utc::now(), + parameters: HashMap::new(), + }) + } + + async fn execute_market_research_step( + &self, + context: &AnalysisContext, + ) -> Result { + if self.verbose { + println!("πŸ“ˆ Step 2: Executing market research analysis..."); + } + + // Retrieve historical market knowledge from semantic memory + let market_entity_facts = self + .memory_system + .semantic + .get_entity_facts("market", &context.symbol) + .await + .map_err(|e| anyhow::anyhow!("Failed to get market facts: {}", e))?; + + // Get similar past market conditions from episodic memory + let _similar_episodes = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(30), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to search episodes: {}", e))?; + + // Use AI for market analysis if available + let ai_insights = if let Some(ref client) = self.rig_client { + let market_prompt = self.build_market_analysis_prompt(context, &market_entity_facts); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble("You are an expert market analyst. Provide concise, professional analysis focusing on key factors that impact investment decisions.") + .max_tokens(300) + .temperature(0.3) + .build(); + + match agent.prompt(&market_prompt).await { + Ok(response) => response.trim().to_string(), + Err(_) => "Market analysis unavailable - using procedural knowledge".to_string(), + } + } else { + "Market analysis based on procedural memory and historical patterns".to_string() + }; + + // Get analysis procedures from procedural memory + let _analysis_procedures = self + .memory_system + .procedural + .get_procedures_by_category("market_analysis") + .await + .map_err(|e| anyhow::anyhow!("Failed to get procedures: {}", e))?; + + // Synthesize comprehensive market analysis + Ok(MarketAnalysisResult { + fundamental_analysis: FundamentalAnalysis { + valuation_metrics: self.calculate_valuation_metrics(&context.symbol).await, + growth_prospects: "Moderate growth expected based on sector trends".to_string(), + competitive_position: "Strong market position in technology sector".to_string(), + financial_health: "Solid financial metrics with manageable debt levels".to_string(), + }, + technical_analysis: TechnicalAnalysis { + trend_direction: "Upward trend with consolidation".to_string(), + support_levels: vec![150.0, 145.0, 140.0], + resistance_levels: vec![160.0, 165.0, 170.0], + momentum_indicators: { + let mut indicators = HashMap::new(); + indicators.insert("RSI".to_string(), 58.5); + indicators.insert("MACD".to_string(), 1.2); + indicators + }, + }, + sector_analysis: SectorAnalysis { + sector_trend: "Technology sector showing resilience".to_string(), + relative_performance: 1.15, + sector_rotation_outlook: "Continued interest in tech fundamentals".to_string(), + key_sector_drivers: vec![ + "AI adoption".to_string(), + "Cloud computing growth".to_string(), + "Digital transformation".to_string(), + ], + }, + sentiment_analysis: SentimentAnalysis { + analyst_sentiment: 0.75, + market_sentiment: 0.65, + news_sentiment: 0.70, + sentiment_drivers: vec![ + "Strong earnings guidance".to_string(), + "Product innovation pipeline".to_string(), + "Market expansion opportunities".to_string(), + ], + }, + ai_insights, + }) + } + + async fn execute_risk_analysis_step( + &self, + context: &AnalysisContext, + market_data: &MarketAnalysisResult, + ) -> Result { + if self.verbose { + println!("⚠️ Step 3: Executing risk analysis..."); + } + + // Get client's risk history from episodic memory + let _risk_episodes = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(90), + chrono::Utc::now(), + ) + .await + .map_err(|e| anyhow::anyhow!("Failed to get risk episodes: {}", e))?; + + // Use AI for risk analysis if available + let risk_factors = if let Some(ref client) = self.rig_client { + let risk_prompt = self.build_risk_analysis_prompt(context, market_data); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble("You are an expert risk analyst. Identify key risk factors and provide actionable mitigation strategies.") + .max_tokens(250) + .temperature(0.2) + .build(); + + match agent.prompt(&risk_prompt).await { + Ok(response) => response + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.trim().to_string()) + .collect(), + Err(_) => vec![ + "Market volatility risk".to_string(), + "Sector concentration risk".to_string(), + "Liquidity risk".to_string(), + ], + } + } else { + vec![ + "Market volatility based on historical patterns".to_string(), + "Client risk tolerance alignment".to_string(), + "Portfolio concentration considerations".to_string(), + ] + }; + + // Calculate risk scores based on client profile and market conditions + let mut risk_breakdown = HashMap::new(); + risk_breakdown.insert(RiskCategory::Market, 0.65); + risk_breakdown.insert(RiskCategory::Credit, 0.25); + risk_breakdown.insert(RiskCategory::Liquidity, 0.30); + risk_breakdown.insert(RiskCategory::Concentration, 0.45); + + // Adjust risk based on client's risk tolerance + let risk_multiplier = match context.client_profile.risk_tolerance { + RiskTolerance::Conservative => 0.8, + RiskTolerance::Moderate => 1.0, + RiskTolerance::Aggressive => 1.2, + }; + + let overall_risk_score = + risk_breakdown.values().sum::() / risk_breakdown.len() as f64 * risk_multiplier; + + // Calculate client alignment score + let client_risk_alignment = match context.client_profile.risk_tolerance { + RiskTolerance::Conservative if overall_risk_score > 0.7 => 0.6, + RiskTolerance::Conservative => 0.9, + RiskTolerance::Moderate => 0.85, + RiskTolerance::Aggressive if overall_risk_score < 0.4 => 0.7, + RiskTolerance::Aggressive => 0.9, + }; + + Ok(RiskAssessmentResult { + overall_risk_score, + risk_breakdown, + risk_factors, + mitigation_recommendations: vec![ + "Diversify across sectors".to_string(), + "Use position sizing appropriate for risk tolerance".to_string(), + "Monitor market conditions regularly".to_string(), + ], + client_risk_alignment, + }) + } + + async fn execute_compliance_step( + &self, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> Result { + if self.verbose { + println!("πŸ›‘οΈ Step 4: Executing compliance validation..."); + } + + // Get compliance rules from procedural memory + let _compliance_procedures = self + .memory_system + .procedural + .get_procedures_by_category("compliance") + .await + .map_err(|e| anyhow::anyhow!("Failed to get compliance procedures: {}", e))?; + + // Check client-specific restrictions from semantic memory + let _client_restrictions = self + .memory_system + .semantic + .get_entity_facts("client_restrictions", &context.client_id) + .await + .unwrap_or_default(); + + // Analyze past compliance issues from episodic memory + let _compliance_history = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(365), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + // Perform compliance checks + let mut violations = Vec::new(); + let mut warnings = Vec::new(); + + // Check position size limits (example rule) + if risk_assessment.overall_risk_score > 0.8 { + warnings.push(ComplianceWarning { + rule_id: "RISK_001".to_string(), + description: "High risk score detected".to_string(), + recommendation: + "Consider reducing position size or implementing additional risk controls" + .to_string(), + }); + } + + // Check client suitability + if risk_assessment.client_risk_alignment < 0.7 { + violations.push(ComplianceViolation { + rule_id: "SUITABILITY_001".to_string(), + severity: ComplianceSeverity::Warning, + description: "Investment may not align with client risk profile".to_string(), + recommended_action: "Review recommendation with client or adjust strategy" + .to_string(), + }); + } + + // Use AI for additional compliance analysis if available + let automated_actions = if let Some(ref client) = self.rig_client { + let compliance_prompt = self.build_compliance_prompt(context, risk_assessment); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble("You are a compliance officer. Focus on regulatory requirements and client protection.") + .max_tokens(200) + .temperature(0.1) + .build(); + + match agent.prompt(&compliance_prompt).await { + Ok(response) => vec![format!("AI compliance check: {}", response.trim())], + Err(_) => vec!["Standard compliance procedures applied".to_string()], + } + } else { + vec!["Automated compliance validation completed".to_string()] + }; + + Ok(ComplianceValidation { + passed: violations.is_empty(), + violations, + warnings, + required_disclosures: vec![ + "Past performance does not guarantee future results".to_string(), + "All investments carry risk of loss".to_string(), + ], + automated_actions_taken: automated_actions, + }) + } + + async fn execute_recommendation_step( + &self, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + compliance_validation: &ComplianceValidation, + ) -> Result { + if self.verbose { + println!("πŸ’‘ Step 5: Generating personalized recommendation..."); + } + + // Determine base recommendation based on analysis + let base_recommendation = if !compliance_validation.passed + || (risk_assessment.overall_risk_score > 0.8 + && matches!( + context.client_profile.risk_tolerance, + RiskTolerance::Conservative + )) { + RecommendationType::Hold // Safety first if compliance issues or high risk for conservative clients + } else if market_analysis.sentiment_analysis.analyst_sentiment > 0.7 + && risk_assessment.client_risk_alignment > 0.8 + { + RecommendationType::Buy + } else if market_analysis.sentiment_analysis.analyst_sentiment < 0.3 { + RecommendationType::Sell + } else { + RecommendationType::Hold + }; + + // Get client interaction history for personalization + let client_history = self + .memory_system + .episodic + .get_episodes_in_period( + chrono::Utc::now() - chrono::Duration::days(180), + chrono::Utc::now(), + ) + .await + .unwrap_or_default(); + + // Generate personalized reasoning using AI if available + let personalized_reasoning = if let Some(ref client) = self.rig_client { + let personalization_prompt = self.build_personalization_prompt( + base_recommendation, + context, + market_analysis, + risk_assessment, + ); + + let agent = client + .agent("gpt-3.5-turbo") + .preamble(&format!( + "You are a financial advisor speaking directly to a client with {} risk tolerance. Be personal, clear, and actionable.", + format!("{:?}", context.client_profile.risk_tolerance).to_lowercase() + )) + .max_tokens(400) + .temperature(0.4) + .build(); + + match agent.prompt(&personalization_prompt).await { + Ok(response) => response.trim().to_string(), + Err(_) => { + self.generate_fallback_reasoning(base_recommendation, context, risk_assessment) + } + } + } else { + self.generate_fallback_reasoning(base_recommendation, context, risk_assessment) + }; + + // Calculate confidence adjustment based on analysis quality + let confidence_adjustment = self.calculate_confidence_adjustment( + market_analysis, + risk_assessment, + compliance_validation, + &client_history, + ); + + // Extract client-specific factors + let client_specific_factors = vec![ + format!( + "Risk tolerance: {:?}", + context.client_profile.risk_tolerance + ), + format!( + "Investment goals: {}", + context.client_profile.investment_goals.join(", ") + ), + format!("Time horizon: {}", context.client_profile.time_horizon), + format!( + "Risk alignment: {:.1}%", + risk_assessment.client_risk_alignment * 100.0 + ), + ]; + + Ok(PersonalizedRecommendation { + base_recommendation, + personalized_reasoning, + confidence_adjustment, + client_specific_factors, + presentation_style: "conversational".to_string(), + follow_up_actions: vec![ + "Schedule portfolio review in 30 days".to_string(), + "Monitor market conditions".to_string(), + "Review risk tolerance if market conditions change significantly".to_string(), + ], + }) + } + + // Helper methods for workflow steps + + async fn store_step_memory(&self, step_name: &str, data: &T) -> Result<()> { + let _content = serde_json::to_string(data)?; + let _memory_key = format!("workflow_step_{step_name}"); + + // Note: In a full implementation, step memory would be stored + // self.memory_system.short_term.store_working_memory(...).await?; + + Ok(()) + } + + async fn store_workflow_outcome( + &self, + recommendation: &PersonalizedRecommendation, + ) -> Result<()> { + // Store this workflow execution as an episode for future learning + let episode = RecommendationEpisode { + recommendation_id: uuid::Uuid::new_v4().to_string(), + client_id: "workflow_client".to_string(), // Will be updated with actual client ID + symbol: "workflow_symbol".to_string(), // Will be updated with actual symbol + action: recommendation.base_recommendation, + reasoning: recommendation.personalized_reasoning.clone(), + confidence: recommendation.confidence_adjustment, + market_conditions: MarketSnapshot::default(), + outcome: None, // To be updated when outcome is known + timestamp: Utc::now(), + workflow_steps: Vec::new(), // Could be populated with detailed step results + }; + + let _episode_json = serde_json::to_string(&episode)?; + + // Note: In a full implementation, workflow outcome would be stored in episodic memory + // self.memory_system.episodic.store_episode(...).await?; + + Ok(()) + } + + async fn collect_step_timings(&self) -> HashMap { + // In a real implementation, we would track actual step timings + let mut timings = HashMap::new(); + timings.insert("initialization".to_string(), Duration::milliseconds(150)); + timings.insert("market_research".to_string(), Duration::milliseconds(800)); + timings.insert("risk_analysis".to_string(), Duration::milliseconds(600)); + timings.insert("compliance".to_string(), Duration::milliseconds(300)); + timings.insert("recommendation".to_string(), Duration::milliseconds(450)); + timings + } + + async fn calculate_valuation_metrics(&self, symbol: &str) -> HashMap { + // Simulate market data lookup + let mut metrics = HashMap::new(); + + // These would come from actual market data in a real implementation + match symbol { + "AAPL" => { + metrics.insert("P/E".to_string(), 28.4); + metrics.insert("P/B".to_string(), 45.2); + metrics.insert("EV/EBITDA".to_string(), 22.1); + } + "MSFT" => { + metrics.insert("P/E".to_string(), 32.1); + metrics.insert("P/B".to_string(), 12.8); + metrics.insert("EV/EBITDA".to_string(), 25.3); + } + _ => { + metrics.insert("P/E".to_string(), 25.0); + metrics.insert("P/B".to_string(), 3.5); + metrics.insert("EV/EBITDA".to_string(), 18.0); + } + } + + metrics + } + + fn build_market_analysis_prompt( + &self, + context: &AnalysisContext, + market_facts: &[prollytree::agent::MemoryDocument], + ) -> String { + format!( + r#"Analyze the investment prospects for {} considering: + +Current Market Context: +- Symbol: {} +- Sector Analysis Required +- Client Risk Tolerance: {:?} + +Historical Knowledge Available: +- {} market-related memories in our database +- Market trend: {} +- Economic indicators suggest: {} + +Please provide: +1. Key fundamental factors +2. Technical outlook +3. Sector positioning +4. Risk considerations +5. Investment thesis summary + +Keep analysis concise and actionable."#, + context.symbol, + context.symbol, + context.client_profile.risk_tolerance, + market_facts.len(), + context.market_conditions.market_trend, + "mixed signals with focus on fundamentals" + ) + } + + fn build_risk_analysis_prompt( + &self, + context: &AnalysisContext, + market_data: &MarketAnalysisResult, + ) -> String { + format!( + r#"Perform risk analysis for {} investment: + +Market Analysis Summary: +- Analyst Sentiment: {:.1}% +- Market Sentiment: {:.1}% +- Sector Performance: {} + +Client Profile: +- Risk Tolerance: {:?} +- Time Horizon: {} +- Investment Goals: {} + +Identify top 3-5 risk factors and mitigation strategies."#, + context.symbol, + market_data.sentiment_analysis.analyst_sentiment * 100.0, + market_data.sentiment_analysis.market_sentiment * 100.0, + market_data.sector_analysis.sector_trend, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon, + context.client_profile.investment_goals.join(", ") + ) + } + + fn build_compliance_prompt( + &self, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> String { + format!( + r#"Compliance review for {} recommendation: + +Risk Assessment: +- Overall Risk Score: {:.2} +- Client Risk Alignment: {:.1}% +- Key Risk Factors: {} + +Client Profile: +- Risk Tolerance: {:?} +- Restrictions: {} + +Review for regulatory compliance and client suitability issues."#, + context.symbol, + risk_assessment.overall_risk_score, + risk_assessment.client_risk_alignment * 100.0, + risk_assessment.risk_factors.join(", "), + context.client_profile.risk_tolerance, + context.client_profile.restrictions.join(", ") + ) + } + + fn build_personalization_prompt( + &self, + base_recommendation: RecommendationType, + context: &AnalysisContext, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + ) -> String { + format!( + r#"Create personalized investment advice for client: + +Recommendation: {:?} {} +Confidence Level: {:.1}% + +Client Context: +- Risk Tolerance: {:?} +- Goals: {} +- Time Horizon: {} + +Market Summary: +- Fundamental Outlook: {} +- Risk Level: {:.1}/10 +- Sector Trend: {} + +Explain this recommendation in a personal, conversational tone that addresses: +1. Why this makes sense for their specific situation +2. How it aligns with their goals and risk tolerance +3. What to expect and next steps + +Be encouraging but realistic."#, + base_recommendation, + context.symbol, + risk_assessment.client_risk_alignment * 100.0, + context.client_profile.risk_tolerance, + context.client_profile.investment_goals.join(", "), + context.client_profile.time_horizon, + market_analysis.fundamental_analysis.growth_prospects, + risk_assessment.overall_risk_score * 10.0, + market_analysis.sector_analysis.sector_trend + ) + } + + fn generate_fallback_reasoning( + &self, + recommendation: RecommendationType, + context: &AnalysisContext, + risk_assessment: &RiskAssessmentResult, + ) -> String { + match recommendation { + RecommendationType::Buy => format!( + "Based on our analysis, {} presents a good opportunity for your {:?} risk profile. \ + The investment aligns well with your {} goals, and our risk assessment shows \ + a {:.1}% alignment with your comfort level. This position would fit well within \ + your {} investment timeline.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.investment_goals.join(" and "), + risk_assessment.client_risk_alignment * 100.0, + context.client_profile.time_horizon + ), + RecommendationType::Hold => format!( + "For {}, our recommendation is to maintain your current position. Given your {:?} \ + risk tolerance and current market conditions, holding allows you to maintain exposure \ + while we monitor for better opportunities that align with your {} objectives.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.investment_goals.join(" and ") + ), + RecommendationType::Sell => format!( + "We recommend reducing your {} position at this time. While the fundamentals remain \ + solid, current market conditions and your {:?} risk profile suggest taking some \ + profits would be prudent. This aligns with your {} strategy and helps preserve capital.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.time_horizon + ), + RecommendationType::Rebalance => format!( + "A rebalancing approach for {} would serve your portfolio well. Given your {:?} risk \ + tolerance and {} goals, adjusting your position size will help maintain optimal \ + risk levels while staying aligned with your investment strategy.", + context.symbol, + context.client_profile.risk_tolerance, + context.client_profile.investment_goals.join(" and ") + ), + } + } + + fn calculate_confidence_adjustment( + &self, + market_analysis: &MarketAnalysisResult, + risk_assessment: &RiskAssessmentResult, + compliance_validation: &ComplianceValidation, + client_history: &[prollytree::agent::MemoryDocument], + ) -> f64 { + let mut confidence: f64 = 0.75; // Base confidence + + // Boost confidence for strong market signals + if market_analysis.sentiment_analysis.analyst_sentiment > 0.8 { + confidence += 0.1; + } + + // Boost confidence for good risk alignment + if risk_assessment.client_risk_alignment > 0.9 { + confidence += 0.1; + } + + // Reduce confidence for compliance issues + if !compliance_validation.passed { + confidence -= 0.2; + } + + if !compliance_validation.warnings.is_empty() { + confidence -= 0.05; + } + + // Boost confidence based on historical client relationship + if client_history.len() > 10 { + confidence += 0.05; // More data = more confidence + } + + confidence.clamp(0.1, 0.95) // Keep within reasonable bounds + } +} diff --git a/examples/financial_advisor/src/main.rs b/examples/financial_advisor/src/main.rs index 8a68073..bc5c9fb 100644 --- a/examples/financial_advisor/src/main.rs +++ b/examples/financial_advisor/src/main.rs @@ -11,7 +11,7 @@ mod security; mod validation; mod visualization; -use advisor::FinancialAdvisor; +use advisor::{enhanced_advisor::EnhancedFinancialAdvisor, FinancialAdvisor}; use memory::display::MemoryVisualizer; use security::attack_simulator::AttackSimulator; @@ -36,6 +36,13 @@ enum Commands { verbose: bool, }, + /// Start enhanced advisory session with agent memory + Enhanced { + /// Enable verbose memory operations display + #[arg(short, long)] + verbose: bool, + }, + /// Visualize memory evolution Visualize { /// Show memory tree structure @@ -223,6 +230,10 @@ async fn main() -> Result<()> { run_advisory_session(&cli.storage, &api_key, verbose).await?; } + Commands::Enhanced { verbose } => { + run_enhanced_advisory_session(&cli.storage, api_key.as_str(), verbose).await?; + } + Commands::Visualize { tree, validation, @@ -782,6 +793,22 @@ async fn run_memory_command(storage: &str, git_command: GitCommand) -> Result<() Ok(()) } +async fn run_enhanced_advisory_session(storage: &str, api_key: &str, verbose: bool) -> Result<()> { + println!( + "{}", + "πŸš€ Starting Enhanced Financial Advisory Session" + .green() + .bold() + ); + println!("{}", "Memory-driven personalized financial advice".dimmed()); + println!(); + + let mut advisor = EnhancedFinancialAdvisor::new(storage, Some(api_key), verbose).await?; + advisor.run_interactive_session().await?; + + Ok(()) +} + async fn run_compliance_audit( storage: &str, from: Option, diff --git a/examples/financial_advisor/src/memory/enhanced_types.rs b/examples/financial_advisor/src/memory/enhanced_types.rs new file mode 100644 index 0000000..5543e08 --- /dev/null +++ b/examples/financial_advisor/src/memory/enhanced_types.rs @@ -0,0 +1,399 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::advisor::{RecommendationType, RiskTolerance}; + +/// Enhanced semantic memory structures for financial entities + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientEntity { + pub client_id: String, + pub risk_tolerance: RiskTolerance, + pub investment_goals: Vec, + pub time_horizon: String, + pub portfolio_value: f64, + pub restrictions: Vec, + pub preferences: HashMap, + pub created_at: DateTime, + pub last_updated: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MarketEntity { + pub symbol: String, + pub sector: String, + pub market_cap: u64, + pub fundamentals: MarketFundamentals, + pub analyst_ratings: Vec, + pub last_updated: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MarketFundamentals { + pub pe_ratio: f64, + pub price: f64, + pub volume: u64, + pub market_cap: u64, + pub dividend_yield: Option, + pub revenue_growth: Option, + pub earnings_growth: Option, + pub debt_to_equity: Option, + pub return_on_equity: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnalystRating { + pub analyst: String, + pub rating: String, // Buy, Hold, Sell + pub target_price: Option, + pub confidence: f64, + pub date: DateTime, +} + +/// Enhanced episodic memory structures for financial experiences + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RecommendationEpisode { + pub recommendation_id: String, + pub client_id: String, + pub symbol: String, + pub action: RecommendationType, + pub reasoning: String, + pub confidence: f64, + pub market_conditions: MarketSnapshot, + pub outcome: Option, + pub timestamp: DateTime, + pub workflow_steps: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MarketSnapshot { + pub market_trend: String, + pub volatility_index: f64, + pub interest_rates: f64, + pub major_indices: HashMap, + pub sector_performance: HashMap, + pub economic_indicators: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RecommendationOutcome { + pub actual_return: f64, + pub time_to_outcome: chrono::Duration, + pub client_satisfaction: Option, + pub followed_recommendation: bool, + pub notes: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WorkflowStepResult { + pub step_name: String, + pub execution_time: chrono::Duration, + pub success: bool, + pub key_findings: Vec, + pub confidence_impact: f64, + pub memory_references: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientInteractionEpisode { + pub interaction_id: String, + pub client_id: String, + pub interaction_type: InteractionType, + pub summary: String, + pub sentiment: f64, + pub key_topics: Vec, + pub decisions_made: Vec, + pub follow_up_required: bool, + pub timestamp: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum InteractionType { + InitialConsultation, + PortfolioReview, + RecommendationDiscussion, + RiskAssessment, + ComplianceUpdate, + EmergencyConsultation, +} + +/// Enhanced procedural memory structures for financial workflows + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnalysisWorkflow { + pub name: String, + pub description: String, + pub steps: Vec, + pub success_rate: f64, + pub applicable_conditions: Vec, + pub required_data: Vec, + pub expected_duration: chrono::Duration, + pub created_at: DateTime, + pub last_optimized: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AnalysisStep { + GatherMarketData { + sources: Vec, + required_fields: Vec, + timeout_seconds: u64, + }, + AnalyzeRisk { + metrics: Vec, + thresholds: HashMap, + weight_factors: HashMap, + }, + CheckCompliance { + rules: Vec, + severity_levels: Vec, + automated_actions: Vec, + }, + GenerateRecommendation { + factors: Vec, + weight_matrix: HashMap, + confidence_thresholds: HashMap, + }, + PersonalizeOutput { + client_factors: Vec, + adaptation_rules: Vec, + presentation_style: String, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComplianceRule { + pub rule_id: String, + pub name: String, + pub description: String, + pub rule_type: ComplianceRuleType, + pub severity: ComplianceSeverity, + pub conditions: Vec, + pub automated_action: Option, + pub applicable_clients: Option>, + pub effective_date: DateTime, + pub expiry_date: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ComplianceRuleType { + PositionLimit, + RiskConstraint, + SuitabilityCheck, + ConflictOfInterest, + ReportingRequirement, + KnowYourCustomer, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum ComplianceSeverity { + Info, + Warning, + Critical, + Blocking, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RiskAssessmentProcedure { + pub procedure_id: String, + pub name: String, + pub risk_categories: Vec, + pub calculation_method: String, + pub weight_factors: HashMap, + pub threshold_levels: HashMap, + pub mitigation_strategies: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] +pub enum RiskCategory { + Market, + Credit, + Liquidity, + Operational, + Concentration, + Currency, + InterestRate, +} + +/// Workflow execution context and results + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnalysisContext { + pub analysis_id: String, + pub client_id: String, + pub symbol: String, + pub request_type: String, + pub market_conditions: MarketSnapshot, + pub client_profile: ClientEntity, + pub started_at: DateTime, + pub parameters: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DetailedRecommendation { + pub recommendation_id: String, + pub base_recommendation: RecommendationType, + pub confidence: f64, + pub reasoning: String, + pub personalized_reasoning: String, + pub risk_assessment: RiskAssessmentResult, + pub compliance_validation: ComplianceValidation, + pub market_analysis: MarketAnalysisResult, + pub execution_metadata: ExecutionMetadata, + pub timestamp: DateTime, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RiskAssessmentResult { + pub overall_risk_score: f64, + pub risk_breakdown: HashMap, + pub risk_factors: Vec, + pub mitigation_recommendations: Vec, + pub client_risk_alignment: f64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComplianceValidation { + pub passed: bool, + pub violations: Vec, + pub warnings: Vec, + pub required_disclosures: Vec, + pub automated_actions_taken: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComplianceViolation { + pub rule_id: String, + pub severity: ComplianceSeverity, + pub description: String, + pub recommended_action: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComplianceWarning { + pub rule_id: String, + pub description: String, + pub recommendation: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MarketAnalysisResult { + pub fundamental_analysis: FundamentalAnalysis, + pub technical_analysis: TechnicalAnalysis, + pub sector_analysis: SectorAnalysis, + pub sentiment_analysis: SentimentAnalysis, + pub ai_insights: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FundamentalAnalysis { + pub valuation_metrics: HashMap, + pub growth_prospects: String, + pub competitive_position: String, + pub financial_health: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TechnicalAnalysis { + pub trend_direction: String, + pub support_levels: Vec, + pub resistance_levels: Vec, + pub momentum_indicators: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SectorAnalysis { + pub sector_trend: String, + pub relative_performance: f64, + pub sector_rotation_outlook: String, + pub key_sector_drivers: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SentimentAnalysis { + pub analyst_sentiment: f64, + pub market_sentiment: f64, + pub news_sentiment: f64, + pub sentiment_drivers: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExecutionMetadata { + pub workflow_used: String, + pub total_execution_time: chrono::Duration, + pub step_timings: HashMap, + pub memory_queries_performed: u32, + pub ai_api_calls: u32, + pub data_sources_consulted: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PersonalizedRecommendation { + pub base_recommendation: RecommendationType, + pub personalized_reasoning: String, + pub confidence_adjustment: f64, + pub client_specific_factors: Vec, + pub presentation_style: String, + pub follow_up_actions: Vec, +} + +impl Default for MarketSnapshot { + fn default() -> Self { + Self { + market_trend: "Neutral".to_string(), + volatility_index: 20.0, + interest_rates: 5.0, + major_indices: HashMap::new(), + sector_performance: HashMap::new(), + economic_indicators: HashMap::new(), + } + } +} + +impl ClientEntity { + pub fn new(client_id: String, risk_tolerance: RiskTolerance) -> Self { + let now = Utc::now(); + Self { + client_id, + risk_tolerance, + investment_goals: Vec::new(), + time_horizon: "Long-term".to_string(), + portfolio_value: 0.0, + restrictions: Vec::new(), + preferences: HashMap::new(), + created_at: now, + last_updated: now, + } + } +} + +impl MarketEntity { + pub fn new(symbol: String, sector: String) -> Self { + Self { + symbol, + sector, + market_cap: 0, + fundamentals: MarketFundamentals::default(), + analyst_ratings: Vec::new(), + last_updated: Utc::now(), + } + } +} + +impl Default for MarketFundamentals { + fn default() -> Self { + Self { + pe_ratio: 0.0, + price: 0.0, + volume: 0, + market_cap: 0, + dividend_yield: None, + revenue_growth: None, + earnings_growth: None, + debt_to_equity: None, + return_on_equity: None, + } + } +} diff --git a/examples/financial_advisor/src/memory/mod.rs b/examples/financial_advisor/src/memory/mod.rs index ef10f26..09e1a4f 100644 --- a/examples/financial_advisor/src/memory/mod.rs +++ b/examples/financial_advisor/src/memory/mod.rs @@ -13,10 +13,12 @@ use uuid::Uuid; pub mod consistency; pub mod display; +pub mod enhanced_types; pub mod types; use crate::memory::MemoryType::Recommendation; pub use consistency::MemoryConsistencyChecker; +pub use enhanced_types::*; pub use types::{ AuditEntry, MemoryCommit, MemoryCommitDetails, MemoryComparison, MemorySnapshot, MemoryStatus, MemoryType, SourceStatus, ValidatedMemory, ValidationSource,