"""
空头研究员代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData, ResearchReport

from camel.messages import BaseMessage

class ResearcherBearAgent(BaseAgent):
    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        role_agent = create_role_agent("researcher_bear", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("ResearcherBearAgent")
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        stock_data = data.get("stock_data")
        technical_analysis = data.get("technical_analysis")
        fundamentals_analysis = data.get("fundamentals_analysis")
        sentiment_analysis = data.get("sentiment_analysis")
        valuation_analysis = data.get("valuation_analysis")
        if not stock_data:
            raise ValueError("缺少股票数据")
        self.logger.info(f"正在生成空头研究报告")
        try:
            ticker = stock_data.ticker
            analysis_data = {
                "ticker": ticker,
                "technical_analysis": technical_analysis.dict() if technical_analysis else None,
                "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None,
                "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None,
                "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None
            }
            prompt = f
            analysis_result = self._process_data_with_agent(prompt, analysis_data)
            bear_research = self._create_research_report(analysis_result, ticker)
            return {
                "bear_research": bear_research,
                "messages": []
            }
        except Exception as e:
            self.logger.error(f"生成空头研究报告过程中发生错误: {str(e)}")
            default_report = ResearchReport(
                stance="bearish",
                key_points=["数据不足以支持详细分析"],
                confidence=0.5,
                reasoning="处理过程中发生错误，无法生成完整研究报告"
            )
            return {
                "bear_research": default_report,
                "messages": []
            }
    def _create_research_report(self, analysis_result: Dict[str, Any], ticker: str) -> ResearchReport:
        key_points = analysis_result.get("key_points", ["无关键点"])
        confidence = analysis_result.get("confidence", 0.5)
        technical_summary = analysis_result.get("technical_summary")
        fundamental_summary = analysis_result.get("fundamental_summary")
        sentiment_summary = analysis_result.get("sentiment_summary")
        valuation_summary = analysis_result.get("valuation_summary")
        reasoning = analysis_result.get("reasoning", "未提供分析理由")
        return ResearchReport(
            stance="bearish",
            key_points=key_points,
            confidence=confidence,
            technical_summary=technical_summary,
            fundamental_summary=fundamental_summary,
            sentiment_summary=sentiment_summary,
            valuation_summary=valuation_summary,
            reasoning=reasoning
        )
    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        data_str = self.format_data(data)
        full_prompt = f
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])
        result = self.parse_json_response(response.msgs[0].content)
        if not result:
            result = {
                "key_points": ["无法解析分析结果"],
                "confidence": 0.5,
                "technical_summary": "无法获取技术分析总结",
                "fundamental_summary": "无法获取基本面分析总结",
                "sentiment_summary": "无法获取情绪分析总结",
                "valuation_summary": "无法获取估值分析总结",
                "reasoning": "无法解析空头研究报告"
            }
        return result

"""
多头研究员代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData, ResearchReport

from camel.messages import BaseMessage

class ResearcherBullAgent(BaseAgent):
    """多头研究员代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化多头研究员代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("researcher_bull", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("ResearcherBullAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理多头研究报告生成

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - technical_analysis: 技术分析结果
                - fundamentals_analysis: 基本面分析结果
                - sentiment_analysis: 情绪分析结果
                - valuation_analysis: 估值分析结果
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - bull_research: 多头研究报告
                - messages: 处理过程中的消息
        """
        # 提取股票数据和各类分析结果
        stock_data = data.get("stock_data")
        technical_analysis = data.get("technical_analysis")
        fundamentals_analysis = data.get("fundamentals_analysis")
        sentiment_analysis = data.get("sentiment_analysis")
        valuation_analysis = data.get("valuation_analysis")

        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在生成多头研究报告")

        try:
            # 提取股票基本信息
            ticker = stock_data.ticker

            # 组织各种分析结果
            analysis_data = {
                "ticker": ticker,
                "technical_analysis": technical_analysis.dict() if technical_analysis else None,
                "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None,
                "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None,
                "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None
            }

            # 使用代理处理数据分析请求
            prompt = f"""请作为持有看多观点的研究员，寻找支持买入股票 {ticker} 的最有力证据和论据。
                重点关注以下方面:
                1. 技术分析中的看涨信号
                2. 基本面分析中的积极因素
                3. 市场情绪分析中的乐观迹象
                4. 估值分析中的低估证据
                5. 可能被市场忽视的积极因素

                请提供一份全面的看多研究报告，以JSON格式返回:
                {{
                    "key_points": ["关键点1", "关键点2", ...],
                    "confidence": 0.8,
                    "technical_summary": "技术分析总结...",
                    "fundamental_summary": "基本面分析总结...",
                    "sentiment_summary": "情绪分析总结...",
                    "valuation_summary": "估值分析总结...",
                    "reasoning": "整体推理过程和看多理由..."
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, analysis_data)

            # 创建研究报告
            bull_research = self._create_research_report(analysis_result, ticker)

            # 返回处理结果
            return {
                "bull_research": bull_research,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"生成多头研究报告过程中发生错误: {str(e)}")

            # 返回默认研究报告
            default_report = ResearchReport(
                stance="bullish",
                key_points=["数据不足以支持详细分析"],
                confidence=0.5,
                reasoning="处理过程中发生错误，无法生成完整研究报告"
            )

            return {
                "bull_research": default_report,
                "messages": []
            }

    def _create_research_report(self, analysis_result: Dict[str, Any], ticker: str) -> ResearchReport:
        """创建研究报告

        Args:
            analysis_result: 分析结果
            ticker: 股票代码

        Returns:
            ResearchReport: 研究报告
        """
        key_points = analysis_result.get("key_points", ["无关键点"])
        confidence = analysis_result.get("confidence", 0.5)
        technical_summary = analysis_result.get("technical_summary")
        fundamental_summary = analysis_result.get("fundamental_summary")
        sentiment_summary = analysis_result.get("sentiment_summary")
        valuation_summary = analysis_result.get("valuation_summary")
        reasoning = analysis_result.get("reasoning", "未提供分析理由")

        return ResearchReport(
            stance="bullish",
            key_points=key_points,
            confidence=confidence,
            technical_summary=technical_summary,
            fundamental_summary=fundamental_summary,
            sentiment_summary=sentiment_summary,
            valuation_summary=valuation_summary,
            reasoning=reasoning
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含各类分析结果的数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "key_points": ["无法解析分析结果"],
                "confidence": 0.5,
                "technical_summary": "无法获取技术分析总结",
                "fundamental_summary": "无法获取基本面分析总结",
                "sentiment_summary": "无法获取情绪分析总结",
                "valuation_summary": "无法获取估值分析总结",
                "reasoning": "无法解析多头研究报告"
            }

        return result

"""
投资组合管理代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd
import math

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData, RiskAnalysis, TradingDecision

from camel.messages import BaseMessage

class PortfolioManagerAgent(BaseAgent):
    """投资组合管理代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化投资组合管理代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("portfolio_manager", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("PortfolioManagerAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理投资决策

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - technical_analysis: 技术分析结果
                - fundamentals_analysis: 基本面分析结果
                - sentiment_analysis: 情绪分析结果
                - valuation_analysis: 估值分析结果
                - debate_result: 辩论结果
                - risk_analysis: 风险分析结果
                - portfolio: 投资组合信息
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - trading_decision: 交易决策
                - messages: 处理过程中的消息
        """
        # 提取各种分析结果和股票数据
        stock_data = data.get("stock_data")
        technical_analysis = data.get("technical_analysis")
        fundamentals_analysis = data.get("fundamentals_analysis")
        sentiment_analysis = data.get("sentiment_analysis")
        valuation_analysis = data.get("valuation_analysis")
        debate_result = data.get("debate_result")
        risk_analysis = data.get("risk_analysis")
        portfolio = data.get("portfolio", {"cash": 100000.0, "stock": 0})

        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在制定投资决策")

        try:
            # 提取股票基本信息和最新价格
            ticker = stock_data.ticker
            historical_data = stock_data.historical_data
            latest_price = self._get_latest_price(historical_data)

            # 收集所有分析信号
            agent_signals = []
            if technical_analysis:
                agent_signals.append(technical_analysis)
            if fundamentals_analysis:
                agent_signals.append(fundamentals_analysis)
            if sentiment_analysis:
                agent_signals.append(sentiment_analysis)
            if valuation_analysis:
                agent_signals.append(valuation_analysis)
            if debate_result:
                agent_signals.append(debate_result)

            # 组织投资决策数据
            decision_data = {
                "ticker": ticker,
                "latest_price": latest_price,
                "portfolio": portfolio,
                "technical_analysis": technical_analysis.dict() if technical_analysis else None,
                "fundamentals_analysis": fundamentals_analysis.dict() if fundamentals_analysis else None,
                "sentiment_analysis": sentiment_analysis.dict() if sentiment_analysis else None,
                "valuation_analysis": valuation_analysis.dict() if valuation_analysis else None,
                "debate_result": debate_result.dict() if debate_result else None,
                "risk_analysis": risk_analysis.dict() if risk_analysis else None
            }

            # 使用代理处理数据分析请求
            prompt = f"""请作为投资组合经理，对股票 {ticker} 制定最终的投资决策。
                综合考虑以下因素:
                1. 各类分析师的交易信号
                2. 辩论结果
                3. 风险分析
                4. 当前投资组合状况
                5. 最新市场价格

                请给出明确的交易行动（buy/sell/hold）、交易数量和详细理由。
                最新价格: {latest_price}元/股

                返回格式为JSON:
                {{
                    "action": "buy/sell/hold",
                    "quantity": 数量,
                    "confidence": 0.8,
                    "reasoning": "投资决策详细理由..."
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, decision_data)

            # 创建交易决策
            trading_decision = self._create_trading_decision(analysis_result, agent_signals)

            # 返回处理结果
            return {
                "trading_decision": trading_decision,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"制定投资决策过程中发生错误: {str(e)}")

            # 返回默认交易决策
            default_decision = TradingDecision(
                action="hold",
                quantity=0,
                confidence=0.5,
                agent_signals=[],
                reasoning="决策过程中发生错误，默认保持不变"
            )

            return {
                "trading_decision": default_decision,
                "messages": []
            }

    def _get_latest_price(self, historical_data: Dict[str, Any]) -> float:
        """获取最新价格

        Args:
            historical_data: 历史数据

        Returns:
            float: 最新价格
        """
        try:
            # 假设历史数据中有'close'字段，并且是按时间顺序排列的
            if isinstance(historical_data, list) and len(historical_data) > 0:
                return historical_data[-1].get("close", 0.0)
            elif isinstance(historical_data, dict) and "close" in historical_data:
                if isinstance(historical_data["close"], list):
                    return historical_data["close"][-1]
                return historical_data["close"]
        except Exception as e:
            self.logger.error(f"获取最新价格时发生错误: {str(e)}")

        return 0.0

    def _create_trading_decision(self, analysis_result: Dict[str, Any], agent_signals: List[AnalysisSignal]) -> TradingDecision:
        """创建交易决策

        Args:
            analysis_result: 分析结果
            agent_signals: 分析师信号列表

        Returns:
            TradingDecision: 交易决策
        """
        action = analysis_result.get("action", "hold")
        quantity = analysis_result.get("quantity", 0)
        confidence = analysis_result.get("confidence", 0.5)
        reasoning = analysis_result.get("reasoning", "未提供决策理由")

        # 确保数量是整数
        if not isinstance(quantity, int):
            try:
                quantity = int(quantity)
            except:
                quantity = 0

        # 确保数量非负
        quantity = max(0, quantity)

        return TradingDecision(
            action=action,
            quantity=quantity,
            confidence=confidence,
            agent_signals=agent_signals,
            reasoning=reasoning
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含各种分析数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "action": "hold",
                "quantity": 0,
                "confidence": 0.5,
                "reasoning": "无法解析投资决策，默认保持不变"
            }

        return result

"""
估值分析代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData

from camel.messages import BaseMessage

class ValuationAnalystAgent(BaseAgent):
    """估值分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化估值分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("valuation_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("ValuationAnalystAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理估值分析

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - fundamentals_analysis: 基本面分析结果
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - valuation_analysis: 估值分析信号
                - messages: 处理过程中的消息
        """
        # 提取股票数据和基本面分析
        stock_data = data.get("stock_data")
        fundamentals_analysis = data.get("fundamentals_analysis")

        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在进行估值分析")

        try:
            # 提取基本面数据和历史数据
            fundamental_data = stock_data.fundamental_data
            historical_data = stock_data.historical_data

            # 使用代理处理数据分析请求
            prompt = f"""请对以下股票进行估值分析，给出明确的交易信号（bullish/bearish/neutral）。
                分析以下方面:
                1. 当前市场估值（如PE、PB、PS等）
                2. 估值相对于历史水平
                3. 估值相对于行业平均水平
                4. 使用不同估值模型（如DCF、相对估值法）
                5. 内在价值与当前市场价格的比较

                请给出明确的交易信号、置信度(0-1)和详细理由。
                返回格式为JSON:
                {{
                    "signal": "bullish/bearish/neutral",
                    "confidence": 0.7,
                    "reasoning": "分析理由...",
                    "fair_value": 数值,
                    "key_metrics": ["指标1", "指标2"]
                }}
                """

            analysis_data = {
                "fundamental_data": fundamental_data,
                "historical_data": historical_data
            }

            # 如果有基本面分析结果，添加到分析数据中
            if fundamentals_analysis:
                analysis_data["fundamentals_analysis"] = fundamentals_analysis.dict()

            analysis_result = self._process_data_with_agent(prompt, analysis_data)

            # 创建估值分析信号
            valuation_analysis = self._create_valuation_signal(analysis_result, stock_data)

            # 返回处理结果
            return {
                "valuation_analysis": valuation_analysis,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"估值分析过程中发生错误: {str(e)}")

            # 返回默认分析结果
            default_signal = AnalysisSignal(
                agent="估值分析师",
                signal="neutral",
                confidence=0.5,
                reasoning="分析过程中发生错误，返回中性信号"
            )

            return {
                "valuation_analysis": default_signal,
                "messages": []
            }

    def _create_valuation_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal:
        """创建估值分析信号

        Args:
            analysis_result: 分析结果
            stock_data: 股票数据

        Returns:
            AnalysisSignal: 分析信号
        """
        signal = analysis_result.get("signal", "neutral")
        confidence = analysis_result.get("confidence", 0.5)
        reasoning = analysis_result.get("reasoning", "未提供分析理由")
        fair_value = analysis_result.get("fair_value", 0)
        key_metrics = analysis_result.get("key_metrics", [])

        details = {
            "ticker": stock_data.ticker,
            "fair_value": fair_value,
            "key_metrics": key_metrics,
        }

        return AnalysisSignal(
            agent="估值分析师",
            signal=signal,
            confidence=confidence,
            reasoning=reasoning,
            details=details
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含基本面数据和历史数据的数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "signal": "neutral",
                "confidence": 0.5,
                "reasoning": "无法解析分析结果",
                "fair_value": 0,
                "key_metrics": []
            }

        return result

"""
辩论室代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData, ResearchReport

from camel.messages import BaseMessage

class DebateRoomAgent(BaseAgent):
    """辩论室代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化辩论室代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("debate_room", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("DebateRoomAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理多空观点辩论

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - bull_research: 多头研究报告
                - bear_research: 空头研究报告
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - debate_result: 辩论结果信号
                - messages: 处理过程中的消息
        """
        # 提取股票数据和研究报告
        stock_data = data.get("stock_data")
        bull_research = data.get("bull_research")
        bear_research = data.get("bear_research")

        if not stock_data:
            raise ValueError("缺少股票数据")

        if not bull_research or not bear_research:
            raise ValueError("缺少多头或空头研究报告")

        self.logger.info(f"正在召开辩论会议")

        try:
            # 提取股票基本信息
            ticker = stock_data.ticker

            # 组织研究报告数据
            debate_data = {
                "ticker": ticker,
                "bull_research": bull_research.dict(),
                "bear_research": bear_research.dict(),
            }

            # 使用代理处理数据分析请求
            prompt = f"""请作为辩论室主持人，整合多头和空头研究员对股票 {ticker} 的观点，形成一个平衡的投资视角。
                任务要求:
                1. 公正评估多头和空头论据的优点和弱点
                2. 识别最具说服力的论点
                3. 权衡不同因素的重要性
                4. 形成综合性的市场观点
                5. 提出平衡的投资建议

                请给出明确的交易信号、置信度和详细理由。
                返回格式为JSON:
                {{
                    "signal": "bullish/bearish/neutral",
                    "confidence": 0.7,
                    "reasoning": "辩论总结和推理...",
                    "bull_key_strengths": ["优势1", "优势2"],
                    "bull_key_weaknesses": ["弱点1", "弱点2"],
                    "bear_key_strengths": ["优势1", "优势2"],
                    "bear_key_weaknesses": ["弱点1", "弱点2"],
                    "final_verdict": "最终投资建议..."
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, debate_data)

            # 创建辩论结果信号
            debate_result = self._create_debate_signal(analysis_result, ticker)

            # 返回处理结果
            return {
                "debate_result": debate_result,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"辩论过程中发生错误: {str(e)}")

            # 返回默认辩论结果
            default_signal = AnalysisSignal(
                agent="辩论室",
                signal="neutral",
                confidence=0.5,
                reasoning="辩论过程中发生错误，返回中性信号"
            )

            return {
                "debate_result": default_signal,
                "messages": []
            }

    def _create_debate_signal(self, analysis_result: Dict[str, Any], ticker: str) -> AnalysisSignal:
        """创建辩论结果信号

        Args:
            analysis_result: 分析结果
            ticker: 股票代码

        Returns:
            AnalysisSignal: 分析信号
        """
        signal = analysis_result.get("signal", "neutral")
        confidence = analysis_result.get("confidence", 0.5)
        reasoning = analysis_result.get("reasoning", "未提供分析理由")
        final_verdict = analysis_result.get("final_verdict", "")

        # 整合多空优缺点
        bull_strengths = analysis_result.get("bull_key_strengths", [])
        bull_weaknesses = analysis_result.get("bull_key_weaknesses", [])
        bear_strengths = analysis_result.get("bear_key_strengths", [])
        bear_weaknesses = analysis_result.get("bear_key_weaknesses", [])

        details = {
            "ticker": ticker,
            "bull_strengths": bull_strengths,
            "bull_weaknesses": bull_weaknesses,
            "bear_strengths": bear_strengths,
            "bear_weaknesses": bear_weaknesses,
            "final_verdict": final_verdict
        }

        return AnalysisSignal(
            agent="辩论室",
            signal=signal,
            confidence=confidence,
            reasoning=reasoning,
            details=details
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含多空研究报告的数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "signal": "neutral",
                "confidence": 0.5,
                "reasoning": "无法解析辩论结果",
                "bull_key_strengths": [],
                "bull_key_weaknesses": [],
                "bear_key_strengths": [],
                "bear_key_weaknesses": [],
                "final_verdict": "由于分析错误，无法给出最终判断"
            }

        return result

"""
情绪分析代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData

from camel.messages import BaseMessage

class SentimentAnalystAgent(BaseAgent):
    """情绪分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化情绪分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("sentiment_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("SentimentAnalystAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理情绪分析

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - sentiment_analysis: 情绪分析信号
                - messages: 处理过程中的消息
        """
        # 提取股票数据
        stock_data = data.get("stock_data")

        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在进行情绪分析")

        try:
            # 提取股票基本信息和新闻数据
            ticker = stock_data.ticker
            news_data = stock_data.news_data

            # 使用代理处理数据分析请求
            prompt = f"""请对以下与股票相关的新闻和社交媒体数据进行分析，给出明确的市场情绪信号（bullish/bearish/neutral）。
                分析以下方面:
                1. 整体市场情绪（积极、中性或消极）
                2. 重要事件或新闻的影响
                3. 机构投资者和分析师观点
                4. 社交媒体讨论的热度和倾向性
                5. 情绪变化趋势

                请给出明确的交易信号、置信度(0-1)和详细理由。
                返回格式为JSON:
                {{
                    "signal": "bullish/bearish/neutral",
                    "confidence": 0.7,
                    "reasoning": "分析理由...",
                    "key_events": ["事件1", "事件2"]
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, {
                "ticker": ticker,
                "news_data": news_data
            })

            # 创建情绪分析信号
            sentiment_analysis = self._create_sentiment_signal(analysis_result, stock_data)

            # 返回处理结果
            return {
                "sentiment_analysis": sentiment_analysis,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"情绪分析过程中发生错误: {str(e)}")

            # 返回默认分析结果
            default_signal = AnalysisSignal(
                agent="情绪分析师",
                signal="neutral",
                confidence=0.5,
                reasoning="分析过程中发生错误，返回中性信号"
            )

            return {
                "sentiment_analysis": default_signal,
                "messages": []
            }

    def _create_sentiment_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal:
        """创建情绪分析信号

        Args:
            analysis_result: 分析结果
            stock_data: 股票数据

        Returns:
            AnalysisSignal: 分析信号
        """
        signal = analysis_result.get("signal", "neutral")
        confidence = analysis_result.get("confidence", 0.5)
        reasoning = analysis_result.get("reasoning", "未提供分析理由")
        key_events = analysis_result.get("key_events", [])

        details = {
            "ticker": stock_data.ticker,
            "key_events": key_events,
        }

        return AnalysisSignal(
            agent="情绪分析师",
            signal=signal,
            confidence=confidence,
            reasoning=reasoning,
            details=details
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含新闻数据的字典

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "signal": "neutral",
                "confidence": 0.5,
                "reasoning": "无法解析分析结果",
                "key_events": []
            }

        return result

"""
基本面分析代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import AnalysisSignal, StockData

from camel.messages import BaseMessage

class FundamentalsAnalystAgent(BaseAgent):
    """基本面分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化基本面分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("fundamentals_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("FundamentalsAnalystAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理基本面分析

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - fundamentals_analysis: 基本面分析信号
                - messages: 处理过程中的消息
        """
        # 提取股票数据
        stock_data = data.get("stock_data")
        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在进行基本面分析")

        try:
            # 提取基本面数据和历史数据
            fundamental_data = stock_data.fundamental_data
            historical_data = stock_data.historical_data

            # 使用代理处理数据分析请求
            prompt = f"""请对以下股票的基本面数据进行分析，给出明确的交易信号（bullish/bearish/neutral）。
                分析以下方面:
                1. 财务指标评估（净利润、毛利率、ROE等）
                2. 收入和盈利增长趋势
                3. 估值水平（市盈率、市净率等）
                4. 财务健康状况（资产负债率、流动性等）
                5. 行业地位与竞争优势

                请给出明确的交易信号、置信度(0-1)和详细理由。
                返回格式为JSON:
                {{
                    "signal": "bullish/bearish/neutral",
                    "confidence": 0.7,
                    "reasoning": "分析理由...",
                    "key_financials": ["指标1", "指标2"]
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, {
                "fundamental_data": fundamental_data,
                "historical_data": historical_data
            })

            # 创建基本面分析信号
            fundamentals_analysis = self._create_fundamentals_signal(analysis_result, stock_data)

            # 返回处理结果
            return {
                "fundamentals_analysis": fundamentals_analysis,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"基本面分析过程中发生错误: {str(e)}")

            # 返回默认分析结果
            default_signal = AnalysisSignal(
                agent="基本面分析师",
                signal="neutral",
                confidence=0.5,
                reasoning="分析过程中发生错误，返回中性信号"
            )

            return {
                "fundamentals_analysis": default_signal,
                "messages": []
            }

    def _create_fundamentals_signal(self, analysis_result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal:
        """创建基本面分析信号

        Args:
            analysis_result: 分析结果
            stock_data: 股票数据

        Returns:
            AnalysisSignal: 分析信号
        """
        signal = analysis_result.get("signal", "neutral")
        confidence = analysis_result.get("confidence", 0.5)
        reasoning = analysis_result.get("reasoning", "未提供分析理由")
        key_financials = analysis_result.get("key_financials", [])

        details = {
            "ticker": stock_data.ticker,
            "key_financials": key_financials,
        }

        return AnalysisSignal(
            agent="基本面分析师",
            signal=signal,
            confidence=confidence,
            reasoning=reasoning,
            details=details
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含基本面数据和历史数据的数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "signal": "neutral",
                "confidence": 0.5,
                "reasoning": "无法解析分析结果",
                "key_financials": []
            }

        return result

"""
基于Camel框架的A股投资代理系统主程序
"""
import argparse
import logging
import json
import os
import sys
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional

# 添加项目根目录到Python路径
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
sys.path.append(project_root)

from src.agents.market_data_agent import MarketDataAgent
from src.agents.technical_analyst import TechnicalAnalystAgent
from src.agents.fundamentals_analyst import FundamentalsAnalystAgent
from src.agents.sentiment_analyst import SentimentAnalystAgent
from src.agents.valuation_analyst import ValuationAnalystAgent
from src.agents.researcher_bull import ResearcherBullAgent
from src.agents.researcher_bear import ResearcherBearAgent
from src.agents.debate_room import DebateRoomAgent
from src.agents.risk_manager import RiskManagerAgent
from src.agents.portfolio_manager import PortfolioManagerAgent
from src.models import Portfolio, TradingDecision, AnalysisSignal, StockData

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("logs/main.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger("Main")

def run_investment_analysis(
    ticker: str,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    portfolio: Optional[Dict[str, Any]] = None,
    show_reasoning: bool = False,
    num_of_news: int = 5,
    model_name: str = "gemini"
) -> TradingDecision:
    """
    运行投资分析流程

    Args:
        ticker: 股票代码
        start_date: 开始日期 (YYYY-MM-DD)
        end_date: 结束日期 (YYYY-MM-DD)
        portfolio: 当前投资组合状态
        show_reasoning: 是否显示推理过程
        num_of_news: 情绪分析使用的新闻数量
        model_name: 使用的模型名称 (gemini, openai, qwen)

    Returns:
        TradingDecision: 交易决策
    """
    logger.info(f"开始对 {ticker} 进行投资分析，使用模型: {model_name}")

    # 设置默认日期
    if not end_date:
        end_date = datetime.now().strftime("%Y-%m-%d")
    if not start_date:
        start_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=365)
        start_date = start_date_obj.strftime("%Y-%m-%d")

    # 设置默认投资组合
    if not portfolio:
        portfolio = {"cash": 100000.0, "stock": 0}

    # 初始数据
    data = {
        "ticker": ticker,
        "start_date": start_date,
        "end_date": end_date,
        "portfolio": portfolio,
        "num_of_news": num_of_news
    }

    # 创建代理
    market_data_agent = MarketDataAgent(show_reasoning=show_reasoning, model_name=model_name)
    technical_analyst = TechnicalAnalystAgent(show_reasoning=show_reasoning, model_name=model_name)
    fundamentals_analyst = FundamentalsAnalystAgent(show_reasoning=show_reasoning, model_name=model_name)
    sentiment_analyst = SentimentAnalystAgent(show_reasoning=show_reasoning, model_name=model_name)
    valuation_analyst = ValuationAnalystAgent(show_reasoning=show_reasoning, model_name=model_name)
    researcher_bull = ResearcherBullAgent(show_reasoning=show_reasoning, model_name=model_name)
    researcher_bear = ResearcherBearAgent(show_reasoning=show_reasoning, model_name=model_name)
    debate_room = DebateRoomAgent(show_reasoning=show_reasoning, model_name=model_name)
    risk_manager = RiskManagerAgent(show_reasoning=show_reasoning, model_name=model_name)
    portfolio_manager = PortfolioManagerAgent(show_reasoning=show_reasoning, model_name=model_name)

    try:
        # 第一步: 获取市场数据
        logger.info("步骤1: 获取市场数据")
        market_data_result = market_data_agent.process(data)

        # 提取股票数据
        stock_data = market_data_result.get("stock_data")
        if not stock_data:
            raise ValueError("市场数据代理未返回股票数据")

        # 第二步: 技术分析
        logger.info("步骤2: 进行技术分析")
        technical_data = {
            "stock_data": stock_data,
            "messages": market_data_result.get("messages", [])
        }
        technical_result = technical_analyst.process(technical_data)
        technical_analysis = technical_result.get("technical_analysis")

        # 第三步: 基本面分析
        logger.info("步骤3: 进行基本面分析")
        fundamentals_data = {
            "stock_data": stock_data,
            "messages": technical_result.get("messages", [])
        }
        fundamentals_result = fundamentals_analyst.process(fundamentals_data)
        fundamentals_analysis = fundamentals_result.get("fundamentals_analysis")

        # 第四步: 情绪分析
        logger.info("步骤4: 进行情绪分析")
        sentiment_data = {
            "stock_data": stock_data,
            "messages": fundamentals_result.get("messages", [])
        }
        sentiment_result = sentiment_analyst.process(sentiment_data)
        sentiment_analysis = sentiment_result.get("sentiment_analysis")

        # 第五步: 估值分析
        logger.info("步骤5: 进行估值分析")
        valuation_data = {
            "stock_data": stock_data,
            "fundamentals_analysis": fundamentals_analysis,
            "messages": sentiment_result.get("messages", [])
        }
        valuation_result = valuation_analyst.process(valuation_data)
        valuation_analysis = valuation_result.get("valuation_analysis")

        # 第六步: 多头研究员报告
        logger.info("步骤6: 生成多头研究报告")
        bull_data = {
            "stock_data": stock_data,
            "technical_analysis": technical_analysis,
            "fundamentals_analysis": fundamentals_analysis,
            "sentiment_analysis": sentiment_analysis,
            "valuation_analysis": valuation_analysis,
            "messages": valuation_result.get("messages", [])
        }
        bull_result = researcher_bull.process(bull_data)
        bull_research = bull_result.get("bull_research")

        # 第七步: 空头研究员报告
        logger.info("步骤7: 生成空头研究报告")
        bear_data = {
            "stock_data": stock_data,
            "technical_analysis": technical_analysis,
            "fundamentals_analysis": fundamentals_analysis,
            "sentiment_analysis": sentiment_analysis,
            "valuation_analysis": valuation_analysis,
            "messages": bull_result.get("messages", [])
        }
        bear_result = researcher_bear.process(bear_data)
        bear_research = bear_result.get("bear_research")

        # 第八步: 辩论室
        logger.info("步骤8: 举行辩论会")
        debate_data = {
            "stock_data": stock_data,
            "bull_research": bull_research,
            "bear_research": bear_research,
            "messages": bear_result.get("messages", [])
        }
        debate_result = debate_room.process(debate_data)
        debate_signal = debate_result.get("debate_result")

        # 第九步: 风险评估
        logger.info("步骤9: 进行风险评估")
        risk_data = {
            "stock_data": stock_data,
            "debate_result": debate_signal,
            "portfolio": portfolio,
            "messages": debate_result.get("messages", [])
        }
        risk_result = risk_manager.process(risk_data)
        risk_analysis = risk_result.get("risk_analysis")

        # 第十步: 投资组合管理
        logger.info("步骤10: 制定最终投资决策")
        portfolio_data = {
            "stock_data": stock_data,
            "technical_analysis": technical_analysis,
            "fundamentals_analysis": fundamentals_analysis,
            "sentiment_analysis": sentiment_analysis,
            "valuation_analysis": valuation_analysis,
            "debate_result": debate_signal,
            "risk_analysis": risk_analysis,
            "portfolio": portfolio,
            "messages": risk_result.get("messages", [])
        }
        portfolio_result = portfolio_manager.process(portfolio_data)
        trading_decision = portfolio_result.get("trading_decision")

        logger.info(f"投资分析完成，决策: {trading_decision.action}, 数量: {trading_decision.quantity}")
        return trading_decision

    except Exception as e:
        logger.error(f"投资分析过程中发生错误: {str(e)}")

        # 返回默认决策
        default_decision = TradingDecision(
            action="hold",
            quantity=0,
            confidence=0.5,
            agent_signals=[],
            reasoning=f"分析过程中发生错误: {str(e)}"
        )
        return default_decision

def test(ticker: str = "000001", model_name: str = "gemini"):
    """
    测试函数，使用预设参数快速测试系统功能

    Args:
        ticker: 股票代码，默认为"000001"（平安银行）
        model_name: 使用的模型名称 (gemini, openai, qwen)
    """
    logger.info(f"开始测试功能，分析股票: {ticker}")

    # 预设参数
    show_reasoning = True  # 显示推理过程
    num_of_news = 5  # 获取5条新闻
    initial_capital = 100000.0  # 初始资金10万元

    # 日期设置（默认分析最近30天）
    end_date = datetime.now().strftime("%Y-%m-%d")
    start_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=30)
    start_date = start_date_obj.strftime("%Y-%m-%d")

    # 创建投资组合
    portfolio = {
        "cash": initial_capital,
        "stock": 0
    }

    print(f"测试配置:")
    print(f"- 股票: {ticker}")
    print(f"- 模型: {model_name}")
    print(f"- 时间范围: {start_date} 至 {end_date}")
    print(f"- 初始资金: {initial_capital}")
    print(f"- 新闻数量: {num_of_news}")
    print("\n开始分析...\n")

    # 运行投资分析
    decision = run_investment_analysis(
        ticker=ticker,
        start_date=start_date,
        end_date=end_date,
        portfolio=portfolio,
        show_reasoning=show_reasoning,
        num_of_news=num_of_news,
        model_name=model_name
    )

    # 输出结果
    print("\n测试结果 - 交易决策:")
    print(json.dumps(decision.dict(), indent=2, ensure_ascii=False))
    return decision

def main():
    """主函数"""
    # 解析命令行参数
    parser = argparse.ArgumentParser(description="基于Camel框架的A股投资代理系统")
    parser.add_argument("--ticker", type=str, required=True, help="股票代码")
    parser.add_argument("--start-date", type=str, help="开始日期 (YYYY-MM-DD)")
    parser.add_argument("--end-date", type=str, help="结束日期 (YYYY-MM-DD)")
    parser.add_argument("--cash", type=float, default=100000.0, help="初始现金")
    parser.add_argument("--stock", type=int, default=0, help="初始股票数量")
    parser.add_argument("--model", type=str, default="qwen", choices=["gemini", "openai", "qwen"], help="使用的模型")
    parser.add_argument("--news", type=int, default=10, help="情绪分析的新闻数量")
    parser.add_argument("--show-reasoning", action="store_true", help="显示详细推理过程")
    parser.add_argument("--test", action="store_true", help="以测试模式运行，使用默认参数")

    args = parser.parse_args()

    # 测试模式
    if args.test:
        test(ticker=args.ticker, model_name=args.model)
        return

    # 正常模式，使用命令行参数
    portfolio = {
        "cash": args.cash,
        "stock": args.stock
    }

    decision = run_investment_analysis(
        ticker=args.ticker,
        start_date=args.start_date,
        end_date=args.end_date,
        portfolio=portfolio,
        show_reasoning=args.show_reasoning,
        num_of_news=args.news,
        model_name=args.model
    )

    print(json.dumps(decision.dict(), indent=2, ensure_ascii=False))

if __name__ == "__main__":
    main()

"""
技术分析代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd
import numpy as np
from datetime import datetime
import json

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import StockData, AnalysisSignal

from camel.messages import BaseMessage

class TechnicalAnalystAgent(BaseAgent):
    """技术分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化技术分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("technical_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("TechnicalAnalystAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理技术分析

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - technical_analysis: 技术分析信号
                - messages: 处理过程中的消息
        """
        # 提取股票数据
        stock_data = data.get("stock_data")
        if not stock_data:
            raise ValueError("缺少股票数据")

        self.logger.info(f"正在进行技术分析")

        try:
            # 提取技术指标
            technical_indicators = stock_data.technical_indicators
            historical_data = stock_data.historical_data

            # 使用代理处理数据分析请求
            prompt = f"""请对以下股票的技术指标进行分析，给出明确的交易信号（bullish/bearish/neutral）。
                分析以下方面:
                1. 趋势指标 (移动平均线, MACD等)
                2. 动量指标 (RSI, 随机指标等)
                3. 波动性指标 (布林带, ATR等)
                4. 量价关系
                5. 支撑位和阻力位

                请给出明确的交易信号、置信度(0-1)和详细理由。
                返回格式为JSON:
                {{
                    "signal": "bullish/bearish/neutral",
                    "confidence": 0.7,
                    "reasoning": "分析理由...",
                    "key_indicators": ["指标1", "指标2"]
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, {
                "technical_indicators": technical_indicators,
                "historical_data": historical_data
            })

            # 创建技术分析信号
            technical_analysis = self._create_technical_signal(analysis_result, stock_data)

            # 返回处理结果
            return {
                "technical_analysis": technical_analysis,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"技术分析过程中发生错误: {str(e)}")

            # 返回默认分析结果
            default_signal = AnalysisSignal(
                agent="技术分析师",
                signal="neutral",
                confidence=0.5,
                reasoning="分析过程中发生错误，返回中性信号"
            )

            return {
                "technical_analysis": default_signal,
                "messages": []
            }

    def _prepare_analysis_prompt(self, stock_data: StockData) -> str:
        """准备技术分析提示

        Args:
            stock_data: 股票数据对象

        Returns:
            str: 技术分析提示
        """
        # 提取技术指标
        technical_indicators = stock_data.technical_indicators
        historical_data = stock_data.historical_data

        # 构建提示
        prompt = f"""请对以下股票的技术指标进行分析，给出明确的交易信号（bullish/bearish/neutral）。
            分析以下方面:
            1. 趋势指标 (移动平均线, MACD等)
            2. 动量指标 (RSI, 随机指标等)
            3. 波动性指标 (布林带, ATR等)
            4. 量价关系
            5. 支撑位和阻力位

            请给出明确的交易信号、置信度(0-1)和详细理由。
            返回格式为JSON:
            {{
                "signal": "bullish/bearish/neutral",
                "confidence": 0.7,
                "reasoning": "分析理由...",
                "key_indicators": ["指标1", "指标2"]
            }}
            """
        return prompt

    def _create_technical_signal(self, result: Dict[str, Any], stock_data: StockData) -> AnalysisSignal:
        """创建技术分析信号

        Args:
            result: 分析结果
            stock_data: 股票数据对象

        Returns:
            AnalysisSignal: 技术分析信号
        """
        signal = result.get("signal", "neutral")
        confidence = result.get("confidence", 0.5)
        reasoning = result.get("reasoning", "")
        key_indicators = result.get("key_indicators", [])

        return AnalysisSignal(
            agent="Technical Analysis",
            signal=signal,
            confidence=confidence,
            reasoning=reasoning,
            details={
                "key_indicators": key_indicators
            }
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含技术指标和历史数据的数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "signal": "neutral",
                "confidence": 0.5,
                "reasoning": "无法解析分析结果",
                "key_indicators": []
            }

        return result

"""
风险管理代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import pandas as pd

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import RiskAnalysis, StockData, Portfolio

from camel.messages import BaseMessage

class RiskManagerAgent(BaseAgent):
    """风险管理代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化风险管理代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("risk_manager", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("RiskManagerAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理风险评估

        Args:
            data: 包含以下键的字典:
                - stock_data: 股票数据对象
                - debate_result: 辩论结果
                - portfolio: 投资组合信息
                - messages: 处理过程中的消息

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - risk_analysis: 风险分析结果
                - messages: 处理过程中的消息
        """
        # 提取股票数据和辩论结果
        stock_data = data.get("stock_data")
        debate_result = data.get("debate_result")
        portfolio = data.get("portfolio")

        if not stock_data:
            raise ValueError("缺少股票数据")

        if not portfolio:
            portfolio = {"cash": 100000.0, "stock": 0}

        self.logger.info(f"正在进行风险评估")

        try:
            # 提取股票基本信息和历史数据
            ticker = stock_data.ticker
            historical_data = stock_data.historical_data

            # 组织风险评估数据
            risk_data = {
                "ticker": ticker,
                "historical_data": historical_data,
                "debate_result": debate_result.dict() if debate_result else None,
                "portfolio": portfolio
            }

            # 使用代理处理数据分析请求
            prompt = f"""请作为风险管理经理，评估投资股票 {ticker} 的风险水平，并提供风险管理建议。
                分析以下方面:
                1. 投资组合风险指标（波动率、最大回撤等）
                2. 市场和特定股票的风险水平
                3. 适当的持仓限制
                4. 止损水平建议
                5. 风险分散策略

                请根据分析提供详细的风险管理建议。
                返回格式为JSON:
                {{
                    "max_position_size": 0.2, // 建议最大持仓比例
                    "volatility": 0.15, // 预估股票波动率
                    "risk_score": 0.7, // 0-1之间的风险分数
                    "max_drawdown": 0.25, // 预估最大回撤
                    "suggested_position_size": 0.15, // 建议持仓比例
                    "reasoning": "风险评估理由..."
                }}
                """

            analysis_result = self._process_data_with_agent(prompt, risk_data)

            # 创建风险分析结果
            risk_analysis = self._create_risk_analysis(analysis_result)

            # 返回处理结果
            return {
                "risk_analysis": risk_analysis,
                "messages": []
            }

        except Exception as e:
            self.logger.error(f"风险评估过程中发生错误: {str(e)}")

            # 返回默认风险分析
            default_analysis = RiskAnalysis(
                max_position_size=0.1,
                volatility=0.2,
                risk_score=0.5,
                max_drawdown=0.2,
                suggested_position_size=0.05,
                reasoning="风险评估过程中发生错误，使用保守默认值"
            )

            return {
                "risk_analysis": default_analysis,
                "messages": []
            }

    def _create_risk_analysis(self, analysis_result: Dict[str, Any]) -> RiskAnalysis:
        """创建风险分析结果

        Args:
            analysis_result: 分析结果

        Returns:
            RiskAnalysis: 风险分析结果
        """
        max_position_size = analysis_result.get("max_position_size", 0.1)
        volatility = analysis_result.get("volatility", 0.2)
        risk_score = analysis_result.get("risk_score", 0.5)
        max_drawdown = analysis_result.get("max_drawdown", 0.2)
        suggested_position_size = analysis_result.get("suggested_position_size", 0.05)
        reasoning = analysis_result.get("reasoning", "未提供风险评估理由")

        return RiskAnalysis(
            max_position_size=max_position_size,
            volatility=volatility,
            risk_score=risk_score,
            max_drawdown=max_drawdown,
            suggested_position_size=suggested_position_size,
            reasoning=reasoning
        )

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含风险评估相关数据

        Returns:
            Dict[str, Any]: 分析结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 解析结果
        result = self.parse_json_response(response.msgs[0].content)

        # 如果解析结果为空，使用默认值
        if not result:
            result = {
                "max_position_size": 0.1,
                "volatility": 0.2,
                "risk_score": 0.5,
                "max_drawdown": 0.2,
                "suggested_position_size": 0.05,
                "reasoning": "无法解析风险分析结果，使用保守默认值"
            }

        return result

"""
市场数据分析代理实现
"""
import os
import logging
from typing import Dict, Any, List, Optional
import pandas as pd
import akshare as ak
from datetime import datetime, timedelta

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import StockData

from camel.messages import BaseMessage, OpenAIUserMessage, OpenAIAssistantMessage

class MarketDataAgent(BaseAgent):
    """市场数据分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化市场数据分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("market_data_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("MarketDataAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理市场数据获取和预处理

        Args:
            data: 包含以下键的字典:
                - ticker: 股票代码
                - start_date: 开始日期
                - end_date: 结束日期
                - num_of_news: 新闻数量

        Returns:
            Dict[str, Any]: 处理后的数据，包含以下内容:
                - stock_data: 股票数据对象
                - messages: 处理过程中的消息
        """
        # 提取参数
        ticker = data.get("ticker")
        start_date = data.get("start_date")
        end_date = data.get("end_date")
        num_of_news = data.get("num_of_news", 5)

        if not ticker:
            raise ValueError("缺少股票代码")

        self.logger.info(f"正在获取 {ticker} 的市场数据")

        try:
            # 获取历史价格数据
            historical_data = self._get_historical_data(ticker, start_date, end_date)

            # 计算技术指标
            technical_indicators = self._calculate_technical_indicators(historical_data)

            # 获取财务数据
            fundamental_data = self._get_financial_data(ticker)

            # 获取新闻数据
            news_data = self._get_news_data(ticker, num_of_news)

            # 创建股票数据对象
            stock_data = StockData(
                ticker=ticker,
                historical_data=historical_data,
                technical_indicators=technical_indicators,
                fundamental_data=fundamental_data,
                news_data=news_data
            )

            # 使用代理处理数据分析请求
            prompt = f"""请分析以下关于股票 {ticker} 的市场数据，识别重要趋势和关键指标表现。
                提供以下方面的见解:
                1. 价格走势概要
                2. 成交量分析
                3. 主要技术指标分析（RSI、MACD、布林带）
                4. 关键支撑和阻力位
                5. 市场趋势和整体判断"""

            analysis_result = self._process_data_with_agent(prompt, {
                "ticker": ticker,
                "historical_data": historical_data,
                "technical_indicators": technical_indicators
            })

            # 返回结果
            return {
                "stock_data": stock_data,
                "analysis": analysis_result,
                "messages": []  # 暂时返回空消息列表
            }

        except Exception as e:
            self.logger.error(f"获取市场数据时发生错误: {str(e)}")
            raise e

    def _get_historical_data(self, ticker: str, start_date: str, end_date: str) -> Dict[str, Any]:
        """获取历史价格数据

        Args:
            ticker: 股票代码
            start_date: 开始日期
            end_date: 结束日期

        Returns:
            Dict[str, Any]: 历史价格数据
        """
        try:
            self.logger.info(f"获取历史价格数据: {ticker} 从 {start_date} 到 {end_date}")

            # 使用真实API获取数据
            from src.tools.api import get_price_data

            # 获取价格数据
            df = get_price_data(ticker, start_date, end_date)

            if df is None or df.empty:
                self.logger.warning(f"无法获取{ticker}的价格数据，将使用空数据继续")
                return {
                    "raw": {"dates": [], "prices": [], "volumes": []},
                    "summary": {
                        "ticker": ticker,
                        "start_date": start_date,
                        "end_date": end_date,
                        "latest_price": 0,
                        "price_change": 0,
                        "high_price": 0,
                        "low_price": 0,
                        "average_volume": 0
                    }
                }

            # 将DataFrame转换为可处理的字典格式
            dates = df['date'].dt.strftime('%Y-%m-%d').tolist()
            prices = df['close'].tolist()
            volumes = df['volume'].tolist()

            # 计算汇总数据
            latest_price = prices[-1] if prices else 0
            previous_price = prices[-2] if len(prices) > 1 else latest_price
            price_change = ((latest_price - previous_price) / previous_price * 100) if previous_price else 0
            high_price = max(prices) if prices else 0
            low_price = min(prices) if prices else 0

            # 构建结果
            result = {
                "raw": {
                    "dates": dates,
                    "prices": prices,
                    "volumes": volumes
                },
                "summary": {
                    "ticker": ticker,
                    "start_date": start_date,
                    "end_date": end_date,
                    "latest_price": latest_price,
                    "price_change": round(price_change, 2),
                    "high_price": high_price,
                    "low_price": low_price,
                    "average_volume": sum(volumes) / len(volumes) if volumes else 0
                }
            }

            return result

        except Exception as e:
            self.logger.error(f"获取历史价格数据时发生错误: {str(e)}")
            # 返回最小数据集以避免整个流程中断
            return {
                "raw": {"dates": [], "prices": [], "volumes": []},
                "summary": {
                    "ticker": ticker,
                    "start_date": start_date,
                    "end_date": end_date,
                    "latest_price": 0,
                    "price_change": 0,
                    "high_price": 0,
                    "low_price": 0,
                    "average_volume": 0,
                    "error": str(e)
                }
            }

    def _calculate_technical_indicators(self, historical_data: Dict[str, Any]) -> Dict[str, Any]:
        """计算技术指标

        Args:
            historical_data: 历史价格数据

        Returns:
            Dict[str, Any]: 技术指标数据
        """
        try:
            from src.tools.data_helper import calculate_technical_indicators

            raw_data = historical_data.get("raw", {})
            dates = raw_data.get("dates", [])
            prices = raw_data.get("prices", [])
            volumes = raw_data.get("volumes", [])

            if not prices:
                return {}

            # 将数据转换为DataFrame格式，以便使用data_helper中的函数
            df = pd.DataFrame({
                "日期": dates if dates else [],
                "收盘": prices if prices else [],
                "成交量": volumes if volumes else []
            })

            # 记录使用的列名
            self.logger.info(f"DataFrame列名: {df.columns.tolist()}")

            # 使用data_helper中的函数计算技术指标
            indicators = calculate_technical_indicators(df)

            if indicators and "error" in indicators:
                self.logger.error(f"计算技术指标时出错: {indicators['error']}")
                return {}

            # 提取最新的技术指标值用于摘要
            latest_indicators = {}

            # 提取SMA (简单移动平均线)
            for period in [5, 10, 20, 50, 200]:
                key = f"ma_{period}"
                if key in indicators and indicators[key]:
                    latest_indicators[key] = indicators[key][-1]

            # 提取RSI (相对强弱指数)
            if "rsi" in indicators and indicators["rsi"]:
                latest_indicators["rsi"] = indicators["rsi"][-1]

            # 提取MACD
            if all(k in indicators for k in ["macd", "macd_signal", "macd_histogram"]):
                latest_indicators["macd"] = indicators["macd"][-1] if indicators["macd"] else None
                latest_indicators["macd_signal"] = indicators["macd_signal"][-1] if indicators["macd_signal"] else None
                latest_indicators["macd_histogram"] = indicators["macd_histogram"][-1] if indicators["macd_histogram"] else None

            # 提取布林带
            if all(k in indicators for k in ["bollinger_ma", "bollinger_upper", "bollinger_lower"]):
                latest_indicators["bollinger_middle"] = indicators["bollinger_ma"][-1] if indicators["bollinger_ma"] else None
                latest_indicators["bollinger_upper"] = indicators["bollinger_upper"][-1] if indicators["bollinger_upper"] else None
                latest_indicators["bollinger_lower"] = indicators["bollinger_lower"][-1] if indicators["bollinger_lower"] else None

            # 分析价格位置
            if "ma_20" in latest_indicators and "ma_50" in latest_indicators and prices:
                latest_price = prices[-1]
                latest_indicators["price_vs_ma20"] = "above" if latest_price > latest_indicators["ma_20"] else "below"
                latest_indicators["price_vs_ma50"] = "above" if latest_price > latest_indicators["ma_50"] else "below"

            return {
                "full": indicators,
                "latest": latest_indicators
            }

        except Exception as e:
            self.logger.error(f"计算技术指标时发生错误: {str(e)}")
            return {}

    def _prepare_summary_prompt(self, ticker: str, stock_data: StockData) -> str:
        """准备数据摘要提示

        Args:
            ticker: 股票代码
            stock_data: 股票数据对象

        Returns:
            str: 数据摘要提示
        """
        # 提取相关数据
        historical_summary = stock_data.historical_data.get("summary", {})
        technical_indicators = stock_data.technical_indicators.get("latest", {})

        # 构建提示
        prompt = f"""
请对以下股票数据进行分析和预处理，确认数据质量并提供简要说明。

股票: {ticker}
最新价格: {historical_summary.get('latest_price')}
涨跌幅: {historical_summary.get('price_change')}%
时间范围: {historical_summary.get('start_date')} 至 {historical_summary.get('end_date')}

主要技术指标:
- MA(5): {technical_indicators.get('ma_5')}
- MA(20): {technical_indicators.get('ma_20')}
- RSI: {technical_indicators.get('rsi')}
- MACD: {technical_indicators.get('macd')}

请分析这些数据，确认数据是否合理、完整，并提供简要的市场数据状况描述。
如果发现任何数据问题，请指出并提供可能的解决方法。
"""
        return prompt

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> Dict[str, Any]:
        """使用代理处理数据并获取结果

        Args:
            prompt: 提示信息
            data: 要处理的数据

        Returns:
            Dict[str, Any]: 处理结果
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请以JSON格式返回结果。
"""
        try:
            # 创建消息 - 使用新的消息格式
            msg = self.generate_human_message(content=full_prompt)

            # 获取代理响应
            response = self.agent.step(msg)

            # 记录响应
            self.log_message(response.msgs[0])

            # 解析响应
            return self.parse_json_response(response.msgs[0].content)

        except Exception as e:
            self.logger.error(f"处理数据时发生错误: {str(e)}")
            return {}

    def _get_financial_data(self, ticker: str) -> Dict[str, Any]:
        """获取股票的财务数据

        Args:
            ticker: 股票代码

        Returns:
            Dict[str, Any]: 财务数据
        """
        try:
            from src.tools.data_helper import get_fundamental_data

            self.logger.info(f"获取财务数据: {ticker}")

            # 使用data_helper中的函数获取财务数据
            financial_data = get_fundamental_data(ticker)

            # 如果获取失败，返回空结构
            if not financial_data or "error" in financial_data:
                self.logger.error(f"获取财务数据失败: {financial_data.get('error', '未知错误')}")
                return {
                    "income_statement": {},
                    "balance_sheet": {},
                    "financial_ratios": {},
                    "quarterly_results": [],
                    "dividend_info": {},
                }

            # 提取并组织数据
            financial_indicators = financial_data.get("financial_indicators", {})
            income_statement = financial_data.get("income_statement", {})
            stock_info = financial_data.get("stock_info", {})
            summary = financial_data.get("summary", {})

            # 获取历史财务数据，默认获取最近4个季度
            historical_data = self._get_historical_financial_data(ticker, 4)

            # 构建返回结构
            result = {
                "income_statement": {
                    "revenue": income_statement.get("营业收入", 0),
                    "operating_income": income_statement.get("营业利润", 0),
                    "net_income": income_statement.get("净利润", 0),
                    "total_profit": income_statement.get("利润总额", 0),
                    "eps": income_statement.get("基本每股收益", 0),
                    "income_tax": income_statement.get("减:所得税", 0),
                    "interest_income": income_statement.get("利息收入", 0),
                    "interest_expense": income_statement.get("利息支出", 0),
                    "investment_income": income_statement.get("投资收益", 0),
                    "operating_expense": income_statement.get("营业支出", 0),
                    "rd_expense": income_statement.get("研发费用", 0),
                    "business_tax": income_statement.get("营业税金及附加", 0),
                    "management_expense": income_statement.get("业务及管理费用", 0),
                    "asset_impairment_loss": income_statement.get("资产减值损失", 0),
                    "credit_impairment_loss": income_statement.get("信用减值损失", 0),
                    "non_operating_income": income_statement.get("加:营业外收入", 0),
                    "non_operating_expense": income_statement.get("减:营业外支出", 0),
                    "reporting_date": income_statement.get("报告日", ""),
                    "year_over_year_growth": financial_indicators.get("净利润同比增长率", 0),
                    "diluted_eps": income_statement.get("稀释每股收益", 0),
                },
                "balance_sheet": {
                    "total_assets": financial_indicators.get("总资产", 0),
                    "total_liabilities": financial_indicators.get("总负债", 0),
                    "total_equity": financial_indicators.get("所有者权益", 0),
                    "cash_and_equivalents": financial_indicators.get("货币资金", 0),
                    "total_debt": financial_indicators.get("带息债务", 0),
                    "retained_earnings": income_statement.get("未分配利润", 0),
                },
                "financial_ratios": {
                    "pe_ratio": stock_info.get("市盈率-动态", 0),
                    "pb_ratio": stock_info.get("市净率", 0),
                    "roe": financial_indicators.get("净资产收益率", 0),
                    "debt_to_equity": financial_indicators.get("资产负债率", 0),
                    "profit_margin": financial_indicators.get("净利率", 0),
                    "current_ratio": financial_indicators.get("流动比率", 0),
                    "turnover_rate": stock_info.get("换手率", 0),
                    "amplitude": stock_info.get("振幅", 0),
                    "year_to_date_change": stock_info.get("年初至今涨跌幅", 0),
                    "sixty_day_change": stock_info.get("60日涨跌幅", 0),
                },
                "market_info": {
                    "market_cap": stock_info.get("总市值", 0),
                    "circulating_market_value": stock_info.get("流通市值", 0),
                    "industry": summary.get("industry", ""),
                    "name": stock_info.get("名称", ""),
                    "latest_price": stock_info.get("最新价", 0),
                    "change_percent": stock_info.get("涨跌幅", 0),
                    "change_amount": stock_info.get("涨跌额", 0),
                    "volume": stock_info.get("成交量", 0),
                    "turnover": stock_info.get("成交额", 0),
                    "highest": stock_info.get("最高", 0),
                    "lowest": stock_info.get("最低", 0),
                    "open": stock_info.get("今开", 0),
                    "prev_close": stock_info.get("昨收", 0),
                },
                "historical_data": historical_data,
                "trends": self._calculate_financial_trends(historical_data)
            }

            return result

        except Exception as e:
            self.logger.error(f"获取财务数据时发生错误: {str(e)}")
            # 返回基本数据结构以避免流程中断
            return {
                "income_statement": {},
                "balance_sheet": {},
                "financial_ratios": {},
                "market_info": {},
                "historical_data": [],
                "trends": {},
                "error": str(e)
            }

    def _get_historical_financial_data(self, ticker: str, num_quarters: int = 4) -> List[Dict[str, Any]]:
        """获取历史财务数据

        Args:
            ticker: 股票代码
            num_quarters: 要获取的季度数量

        Returns:
            List[Dict[str, Any]]: 历史财务数据列表
        """
        try:
            import akshare as ak

            self.logger.info(f"获取{ticker}的历史财务数据，共{num_quarters}个季度")

            # 确定股票前缀
            stock_prefix = 'sz' if ticker.startswith('0') or ticker.startswith('3') else 'sh'
            formatted_ticker = f"{stock_prefix}{ticker}"

            # 获取利润表数据
            income_statements = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="利润表")

            # 获取资产负债表数据
            try:
                balance_sheets = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="资产负债表")
            except Exception as e:
                self.logger.warning(f"获取{ticker}的资产负债表数据失败: {str(e)}")
                balance_sheets = pd.DataFrame()

            # 获取现金流量表数据
            try:
                cash_flows = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="现金流量表")
            except Exception as e:
                self.logger.warning(f"获取{ticker}的现金流量表数据失败: {str(e)}")
                cash_flows = pd.DataFrame()

            # 获取财务指标数据
            try:
                financial_indicators = ak.stock_financial_analysis_indicator(symbol=ticker)
            except Exception as e:
                self.logger.warning(f"获取{ticker}的财务指标数据失败: {str(e)}")
                financial_indicators = pd.DataFrame()

            # 限制数量
            if not income_statements.empty:
                income_statements = income_statements.head(num_quarters)
            if not balance_sheets.empty:
                balance_sheets = balance_sheets.head(num_quarters)
            if not cash_flows.empty:
                cash_flows = cash_flows.head(num_quarters)
            if not financial_indicators.empty:
                financial_indicators = financial_indicators.head(num_quarters)

            # 组织历史数据
            historical_data = []

            # 使用利润表的报告日期作为基准
            if not income_statements.empty:
                for i, row in income_statements.iterrows():
                    if i >= num_quarters:
                        break

                    report_date = row.get("报告日", "")

                    # 查找相应日期的资产负债表和现金流量表数据
                    balance_sheet_row = balance_sheets[balance_sheets["报告日"] == report_date].iloc[0] if not balance_sheets.empty and report_date in balance_sheets["报告日"].values else pd.Series()
                    cash_flow_row = cash_flows[cash_flows["报告日"] == report_date].iloc[0] if not cash_flows.empty and report_date in cash_flows["报告日"].values else pd.Series()

                    # 提取财务指标数据
                    financial_indicator_row = pd.Series()
                    if not financial_indicators.empty:
                        # 处理财务指标数据，通常日期格式不同，需要转换
                        for _, indicator_row in financial_indicators.iterrows():
                            indicator_date = indicator_row.get("日期")
                            if indicator_date and str(indicator_date).replace("-", "").startswith(report_date[:6]):
                                financial_indicator_row = indicator_row
                                break

                    # 组织单季度数据
                    quarter_data = {
                        "report_date": report_date,
                        "formatted_date": f"{report_date[:4]}年{report_date[4:6]}月{report_date[6:]}日",
                        "income_statement": {
                            "revenue": row.get("营业收入", 0),
                            "operating_profit": row.get("营业利润", 0),
                            "net_income": row.get("净利润", 0),
                            "total_profit": row.get("利润总额", 0),
                            "eps": row.get("基本每股收益", 0),
                        },
                        "balance_sheet": {
                            "total_assets": balance_sheet_row.get("资产总计", 0),
                            "total_liabilities": balance_sheet_row.get("负债合计", 0),
                            "equity": balance_sheet_row.get("所有者权益(或股东权益)合计", 0),
                            "cash": balance_sheet_row.get("货币资金", 0),
                        },
                        "cash_flow": {
                            "operating_cash_flow": cash_flow_row.get("经营活动产生的现金流量净额", 0),
                            "investing_cash_flow": cash_flow_row.get("投资活动产生的现金流量净额", 0),
                            "financing_cash_flow": cash_flow_row.get("筹资活动产生的现金流量净额", 0),
                        },
                        "financial_indicators": {
                            "roe": financial_indicator_row.get("净资产收益率(%)", 0),
                            "gross_margin": financial_indicator_row.get("销售毛利率(%)", 0),
                            "debt_ratio": financial_indicator_row.get("资产负债率(%)", 0),
                        }
                    }

                    historical_data.append(quarter_data)

            self.logger.info(f"成功获取{ticker}的历史财务数据，共{len(historical_data)}个季度")
            return historical_data

        except Exception as e:
            self.logger.error(f"获取历史财务数据时发生错误: {str(e)}")
            return []

    def _calculate_financial_trends(self, historical_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """计算财务趋势

        Args:
            historical_data: 历史财务数据列表

        Returns:
            Dict[str, Any]: 财务趋势数据
        """
        if not historical_data or len(historical_data) < 2:
            return {}

        # 提取关键指标的时间序列
        revenue_trend = []
        net_income_trend = []
        eps_trend = []
        roe_trend = []

        for quarter in historical_data:
            revenue_trend.append(float(quarter["income_statement"]["revenue"]))
            net_income_trend.append(float(quarter["income_statement"]["net_income"]))
            eps_trend.append(float(quarter["income_statement"]["eps"]))
            roe_trend.append(float(quarter["financial_indicators"]["roe"]))

        # 计算同比增长率
        try:
            revenue_growth = (revenue_trend[0] - revenue_trend[-1]) / revenue_trend[-1] * 100 if revenue_trend[-1] else 0
            net_income_growth = (net_income_trend[0] - net_income_trend[-1]) / net_income_trend[-1] * 100 if net_income_trend[-1] else 0
        except (IndexError, ZeroDivisionError):
            revenue_growth = 0
            net_income_growth = 0

        # 计算趋势
        trends = {
            "revenue": {
                "values": revenue_trend,
                "growth": revenue_growth,
                "trend": "上升" if revenue_growth > 0 else "下降" if revenue_growth < 0 else "持平"
            },
            "net_income": {
                "values": net_income_trend,
                "growth": net_income_growth,
                "trend": "上升" if net_income_growth > 0 else "下降" if net_income_growth < 0 else "持平"
            },
            "eps": {
                "values": eps_trend,
                "trend": "上升" if eps_trend[0] > eps_trend[-1] else "下降" if eps_trend[0] < eps_trend[-1] else "持平"
            },
            "roe": {
                "values": roe_trend,
                "trend": "上升" if roe_trend[0] > roe_trend[-1] else "下降" if roe_trend[0] < roe_trend[-1] else "持平"
            }
        }

        return trends

    def _get_news_data(self, ticker: str, num_of_news: int = 5) -> Dict[str, Any]:
        """获取股票相关的新闻数据

        Args:
            ticker: 股票代码
            num_of_news: 获取的新闻数量

        Returns:
            Dict[str, Any]: 新闻数据
        """
        try:
            from src.tools.data_helper import get_stock_news

            self.logger.info(f"获取新闻数据: {ticker}, 数量: {num_of_news}")

            # 使用data_helper中的函数获取新闻数据
            news_list = get_stock_news(ticker, num_of_news)

            # 如果没有获取到新闻，返回空结构
            if not news_list:
                return {
                    "news": [],
                    "overall_sentiment": 0,
                    "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0}
                }

            # 简单情感分析 (实际应用中应使用NLP模型进行分析)
            # 这里使用模型分析标题进行简单情感判断
            sentiment_scores = []
            sentiments = {"positive": 0, "neutral": 0, "negative": 0}

            for news in news_list:
                # 让LLM对新闻标题进行情感分析
                sentiment_prompt = f"""
                请分析以下新闻标题的情感倾向，仅回复"positive"、"neutral"或"negative"：
                {news.get('title', '')}
                """

                try:
                    # 使用LLM分析情感
                    msg = self.generate_human_message(content=sentiment_prompt)
                    response = self.agent.step(msg)
                    sentiment = response.msgs[0].content.strip().lower()

                    # 规范化情感结果
                    if "positive" in sentiment:
                        news["sentiment"] = "positive"
                        news["sentiment_score"] = 0.8
                        sentiments["positive"] += 1
                        sentiment_scores.append(0.8)
                    elif "negative" in sentiment:
                        news["sentiment"] = "negative"
                        news["sentiment_score"] = -0.7
                        sentiments["negative"] += 1
                        sentiment_scores.append(-0.7)
                    else:
                        news["sentiment"] = "neutral"
                        news["sentiment_score"] = 0.1
                        sentiments["neutral"] += 1
                        sentiment_scores.append(0.1)

                except Exception:
                    # 如果LLM分析失败，默认为中性
                    news["sentiment"] = "neutral"
                    news["sentiment_score"] = 0.0
                    sentiments["neutral"] += 1
                    sentiment_scores.append(0.0)

            # 计算总体情感得分
            overall_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else 0

            return {
                "news": news_list,
                "overall_sentiment": overall_sentiment,
                "sentiment_breakdown": sentiments
            }

        except Exception as e:
            self.logger.error(f"获取新闻数据时发生错误: {str(e)}")
            # 返回基本数据结构以避免流程中断
            return {
                "news": [],
                "overall_sentiment": 0,
                "sentiment_breakdown": {"positive": 0, "neutral": 0, "negative": 0},
                "error": str(e)
            }

"""
投资分析代理实现
"""
import logging
from typing import Dict, Any, List, Optional
import json
import re

from src.agents.base_agent import BaseAgent
from src.roles import create_role_agent
from src.models import StockData

from camel.messages import BaseMessage

class InvestmentAgent(BaseAgent):
    """投资分析代理类"""

    def __init__(self, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化投资分析代理

        Args:
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        role_agent = create_role_agent("investment_analyst", model_name)
        super().__init__(role_agent, show_reasoning, model_name)
        self.logger = logging.getLogger("InvestmentAgent")

    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理投资分析请求

        Args:
            data: 输入数据，包含股票信息

        Returns:
            Dict[str, Any]: 投资分析结果
        """
        try:
            # 获取输入数据
            stock_data = data.get("stock_data")
            if not stock_data:
                raise ValueError("缺少股票数据")

            ticker = stock_data.ticker

            # 获取财务数据
            fundamental_data = stock_data.fundamental_data

            # 获取历史数据
            historical_data = fundamental_data.get("historical_data", [])
            financial_trends = fundamental_data.get("trends", {})

            # 创建分析提示
            prompt = f"""你是一位专业的投资分析师，请根据以下数据对股票 {ticker} 进行全面的投资分析和推荐：

            1. 基本面分析：
               - 财务状况：分析公司收入、利润、现金流和资产负债情况
               - 增长趋势：评估收入增长、利润增长和ROE变化趋势
               - 估值指标：分析市盈率、市净率等估值指标的合理性

            2. 技术面分析：
               - 价格趋势：分析股票价格走势
               - 成交量：评估交易量变化
               - 技术指标：解读主要技术指标

            3. 投资建议：
               - 给出明确的投资建议（买入/持有/卖出）
               - 说明理由和风险因素
               - 给出合理的目标价格区间

            4. 风险分析：
               - 列出主要风险因素
               - 给出风险应对建议

            请基于数据给出客观、全面的分析，避免过度乐观或悲观的观点。分析应当深入浅出，使投资者容易理解。
            """

            # 构建分析输入
            analysis_input = {
                "ticker": ticker,
                "financial_data": fundamental_data,
                "historical_data": historical_data,
                "financial_trends": financial_trends,
                "technical_indicators": stock_data.technical_indicators,
                "historical_prices": stock_data.historical_data,
                "news": stock_data.news_data
            }

            # 使用代理进行分析
            analysis_result = self._process_data_with_agent(prompt, analysis_input)

            # 提取投资建议
            recommendation = self._extract_recommendation(analysis_result)

            # 返回结果
            return {
                "analysis": analysis_result,
                "recommendation": recommendation,
                "messages": []  # 暂时返回空消息列表
            }

        except Exception as e:
            self.logger.error(f"进行投资分析时发生错误: {str(e)}")
            raise e

    def _process_data_with_agent(self, prompt: str, data: Dict[str, Any]) -> str:
        """使用代理处理数据分析请求

        Args:
            prompt: 分析提示
            data: 包含基本面数据和历史数据的数据

        Returns:
            str: 分析结果文本
        """
        # 格式化数据
        data_str = self.format_data(data)

        # 创建完整提示
        full_prompt = f"""{prompt}

数据:
{data_str}

请提供详细的分析报告。
"""
        # 发送到Camel代理进行分析
        human_message = self.generate_human_message(content=full_prompt)
        response = self.agent.step(human_message)
        self.log_message(response.msgs[0])

        # 返回结果文本
        return response.msgs[0].content

    def generate_human_message(self, content: str) -> BaseMessage:
        """生成人类消息

        Args:
            content: 消息内容

        Returns:
            BaseMessage: 人类消息对象
        """
        return BaseMessage(role_name="Human", role_type="user", meta_info={}, content=content)

    def parse_json_response(self, response: str) -> Dict[str, Any]:
        """解析JSON响应

        Args:
            response: 响应文本

        Returns:
            Dict[str, Any]: 解析后的JSON数据
        """
        try:
            # 尝试提取JSON部分
            json_match = re.search(r'```json\s*([\s\S]*?)\s*```', response)
            if json_match:
                json_str = json_match.group(1)
            else:
                # 尝试找到可能的JSON对象
                json_match = re.search(r'\{[\s\S]*\}', response)
                if json_match:
                    json_str = json_match.group(0)
                else:
                    return {}

            # 解析JSON
            return json.loads(json_str)
        except Exception as e:
            self.logger.error(f"解析JSON响应时发生错误: {str(e)}")
            return {}

    def _extract_recommendation(self, analysis: str) -> Dict[str, Any]:
        """从分析结果中提取投资建议

        Args:
            analysis: 分析文本

        Returns:
            Dict[str, Any]: 投资建议
        """
        try:
            # 使用代理提取关键信息
            prompt = """从以下投资分析报告中提取关键信息，包括：
            1. 投资评级（买入/持有/卖出）
            2. 目标价格区间（如果有）
            3. 主要投资理由（不超过3点）
            4. 主要风险因素（不超过3点）

            以JSON格式返回结果，包含以下字段：
            - rating: 投资评级
            - target_price_low: 目标价格下限（如果有）
            - target_price_high: 目标价格上限（如果有）
            - reasons: 投资理由列表
            - risks: 风险因素列表

            分析报告：
            """

            extraction_result = self._process_data_with_agent(prompt + analysis, {})

            # 尝试解析JSON结果
            try:
                import json
                json_match = re.search(r'\{[\s\S]*\}', extraction_result)
                if json_match:
                    json_str = json_match.group(0)
                    recommendation = json.loads(json_str)
                    return recommendation
                else:
                    return {
                        "rating": self._determine_rating(analysis),
                        "target_price_low": 0,
                        "target_price_high": 0,
                        "reasons": [],
                        "risks": []
                    }
            except:
                # 如果解析失败，返回简化结果
                return {
                    "rating": self._determine_rating(analysis),
                    "target_price_low": 0,
                    "target_price_high": 0,
                    "reasons": [],
                    "risks": []
                }

        except Exception as e:
            self.logger.error(f"提取投资建议时发生错误: {str(e)}")
            return {
                "rating": "未知",
                "target_price_low": 0,
                "target_price_high": 0,
                "reasons": [],
                "risks": []
            }

    def _determine_rating(self, analysis: str) -> str:
        """从分析文本中确定投资评级

        Args:
            analysis: 分析文本

        Returns:
            str: 投资评级
        """
        analysis_lower = analysis.lower()

        if "买入" in analysis or "强烈推荐" in analysis or "buy" in analysis_lower:
            return "买入"
        elif "卖出" in analysis or "sell" in analysis_lower:
            return "卖出"
        elif "持有" in analysis or "观望" in analysis or "hold" in analysis_lower:
            return "持有"
        else:
            return "未知"

    def _analyze_financial_trends(self, historical_data: List[Dict[str, Any]], trends: Dict[str, Any]) -> str:
        """分析财务趋势

        Args:
            historical_data: 历史财务数据
            trends: 财务趋势数据

        Returns:
            str: 财务趋势分析
        """
        if not historical_data or len(historical_data) < 2:
            return "财务数据不足以进行趋势分析。"

        analysis = []

        # 收入趋势分析
        if "revenue" in trends:
            revenue_trend = trends["revenue"]
            revenue_values = revenue_trend.get("values", [])
            revenue_growth = revenue_trend.get("growth", 0)
            revenue_trend_dir = revenue_trend.get("trend", "未知")

            if revenue_values:
                if revenue_growth > 20:
                    analysis.append(f"公司收入呈高速{revenue_trend_dir}趋势，同比增长{revenue_growth:.2f}%，显示出强劲的业务增长。")
                elif revenue_growth > 5:
                    analysis.append(f"公司收入呈稳健{revenue_trend_dir}趋势，同比增长{revenue_growth:.2f}%，业务发展稳定。")
                elif revenue_growth >= 0:
                    analysis.append(f"公司收入略有{revenue_trend_dir}，同比增长{revenue_growth:.2f}%，业务增长放缓。")
                else:
                    analysis.append(f"公司收入呈{revenue_trend_dir}趋势，同比下降{abs(revenue_growth):.2f}%，业务面临挑战。")

        # 净利润趋势分析
        if "net_income" in trends:
            income_trend = trends["net_income"]
            income_values = income_trend.get("values", [])
            income_growth = income_trend.get("growth", 0)
            income_trend_dir = income_trend.get("trend", "未知")

            if income_values:
                if income_growth > 20:
                    analysis.append(f"公司净利润呈高速{income_trend_dir}趋势，同比增长{income_growth:.2f}%，盈利能力强劲。")
                elif income_growth > 5:
                    analysis.append(f"公司净利润呈稳健{income_trend_dir}趋势，同比增长{income_growth:.2f}%，盈利能力稳定。")
                elif income_growth >= 0:
                    analysis.append(f"公司净利润略有{income_trend_dir}，同比增长{income_growth:.2f}%，盈利增长放缓。")
                else:
                    analysis.append(f"公司净利润呈{income_trend_dir}趋势，同比下降{abs(income_growth):.2f}%，盈利能力受到挑战。")

        # ROE趋势分析
        if "roe" in trends:
            roe_trend = trends["roe"]
            roe_values = roe_trend.get("values", [])
            roe_trend_dir = roe_trend.get("trend", "未知")

            if roe_values and len(roe_values) > 0:
                latest_roe = roe_values[0]
                if latest_roe > 15:
                    analysis.append(f"公司ROE为{latest_roe:.2f}%，处于较高水平，资本回报率优秀。ROE呈{roe_trend_dir}趋势。")
                elif latest_roe > 10:
                    analysis.append(f"公司ROE为{latest_roe:.2f}%，处于良好水平，资本回报率不错。ROE呈{roe_trend_dir}趋势。")
                elif latest_roe > 5:
                    analysis.append(f"公司ROE为{latest_roe:.2f}%，处于一般水平，资本回报率尚可。ROE呈{roe_trend_dir}趋势。")
                else:
                    analysis.append(f"公司ROE为{latest_roe:.2f}%，处于较低水平，资本回报率不佳。ROE呈{roe_trend_dir}趋势。")

        # 季度对比分析
        if len(historical_data) >= 4:
            latest_quarter = historical_data[0]
            year_ago_quarter = None

            # 寻找去年同期数据（通常是第4个季度报告）
            if len(historical_data) >= 4:
                year_ago_quarter = historical_data[3]

            if latest_quarter and year_ago_quarter:
                latest_revenue = float(latest_quarter["income_statement"]["revenue"])
                latest_net_income = float(latest_quarter["income_statement"]["net_income"])

                year_ago_revenue = float(year_ago_quarter["income_statement"]["revenue"])
                year_ago_net_income = float(year_ago_quarter["income_statement"]["net_income"])

                # 计算同比增长率
                if year_ago_revenue > 0:
                    yoy_revenue_growth = (latest_revenue - year_ago_revenue) / year_ago_revenue * 100
                    analysis.append(f"同比而言，该季度营收增长{yoy_revenue_growth:.2f}%。")

                if year_ago_net_income > 0:
                    yoy_net_income_growth = (latest_net_income - year_ago_net_income) / year_ago_net_income * 100
                    analysis.append(f"同比而言，该季度净利润增长{yoy_net_income_growth:.2f}%。")

        return "\n".join(analysis)

"""
数据模型定义
"""
from datetime import datetime
from typing import Dict, List, Optional, Union, Any
from pydantic import BaseModel, Field
import json

class StockData(BaseModel):
    """股票数据模型"""
    ticker: str
    historical_data: Dict[str, Any] = Field(default_factory=dict)
    fundamental_data: Dict[str, Any] = Field(default_factory=dict)
    technical_indicators: Dict[str, Any] = Field(default_factory=dict)
    news_data: Dict[str, Any] = Field(default_factory=dict)

class AnalysisSignal(BaseModel):
    """分析信号"""
    agent: str
    signal: str  # bullish, bearish, neutral
    confidence: float  # 0.0 - 1.0
    reasoning: Optional[str] = None
    details: Optional[Dict[str, Any]] = None

class DateTimeEncoder(json.JSONEncoder):
    """处理datetime的JSON编码器"""
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

class TradingDecision(BaseModel):
    """交易决策"""
    action: str  # buy, sell, hold
    quantity: int
    confidence: float
    agent_signals: List[AnalysisSignal]
    reasoning: str
    timestamp: Optional[datetime] = None

    def __init__(self, **data):
        if 'timestamp' not in data:
            data['timestamp'] = datetime.now()
        super().__init__(**data)

    def dict(self):
        """返回字典表示，可JSON序列化"""
        base_dict = super().dict()
        # 转换datetime为ISO格式字符串
        base_dict['timestamp'] = base_dict['timestamp'].isoformat() if base_dict['timestamp'] else None
        return base_dict

class Portfolio(BaseModel):
    """投资组合"""
    cash: float = 100000.0
    stock: int = 0
    stock_value: float = 0.0
    total_value: float = Field(default=0.0)
    holdings: Dict[str, Dict[str, Any]] = Field(default_factory=dict)

    def update_total_value(self):
        """更新总价值"""
        self.total_value = self.cash + self.stock_value

class RiskAnalysis(BaseModel):
    """风险分析"""
    max_position_size: float
    volatility: float
    risk_score: float  # 0.0 - 1.0
    max_drawdown: float
    suggested_position_size: float
    reasoning: Optional[str] = None

class ResearchReport(BaseModel):
    """研究报告"""
    stance: str  # bullish, bearish
    key_points: List[str]
    confidence: float
    technical_summary: Optional[str] = None
    fundamental_summary: Optional[str] = None
    sentiment_summary: Optional[str] = None
    valuation_summary: Optional[str] = None
    reasoning: Optional[str] = None

"""
数据辅助工具模块
"""
import pandas as pd
import akshare as ak
from datetime import datetime, timedelta
import logging
from typing import Dict, Any, List, Optional

logger = logging.getLogger(__name__)

def get_stock_data(ticker: str, start_date: str, end_date: str) -> pd.DataFrame:
    """
    获取股票历史数据

    Args:
        ticker: 股票代码
        start_date: 开始日期 (YYYY-MM-DD)
        end_date: 结束日期 (YYYY-MM-DD)

    Returns:
        pd.DataFrame: 股票历史数据
    """
    try:
        logger.info(f"获取股票 {ticker} 的历史数据")

        # 转换日期格式
        start_date_fmt = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y%m%d")
        end_date_fmt = datetime.strptime(end_date, "%Y-%m-%d").strftime("%Y%m%d")

        # 使用akshare获取A股历史数据
        df = ak.stock_zh_a_hist(
            symbol=ticker,
            period="daily",
            start_date=start_date_fmt,
            end_date=end_date_fmt,
            adjust="qfq"  # 前复权
        )

        logger.info(f"成功获取历史数据，共 {len(df)} 条记录")
        return df

    except Exception as e:
        logger.error(f"获取历史数据时发生错误: {str(e)}")
        return pd.DataFrame()

def get_fundamental_data(ticker: str) -> Dict[str, Any]:
    """
    获取股票基本面数据

    Args:
        ticker: 股票代码

    Returns:
        Dict[str, Any]: 基本面数据
    """
    try:
        logger.info(f"获取股票 {ticker} 的基本面数据")

        # 获取财务指标数据
        financial_indicators = ak.stock_financial_analysis_indicator(symbol=ticker)

        # 获取利润表数据
        try:
            # 根据akshare文档修正
            # stock参数格式应该为'sh'或'sz'+股票代码，而不是直接使用数字代码
            stock_prefix = 'sz' if ticker.startswith('0') or ticker.startswith('3') else 'sh'
            formatted_ticker = f"{stock_prefix}{ticker}"
            # 必须传入symbol参数，设置为"利润表"
            income_statement = ak.stock_financial_report_sina(stock=formatted_ticker, symbol="利润表")
            logger.info(f"成功获取{ticker}的利润表数据")
        except Exception as e:
            logger.error(f"获取利润表数据时出错: {str(e)}")
            income_statement = pd.DataFrame()

        # 获取实时行情
        real_time_quote = ak.stock_zh_a_spot_em()
        stock_quote = real_time_quote[real_time_quote['代码'] == ticker]

        # 记录实时行情字段名，帮助调试
        if not stock_quote.empty:
            logger.info(f"实时行情数据字段: {stock_quote.columns.tolist()}")

        # 提取关键财务指标
        latest_indicators = {}
        if not financial_indicators.empty:
            latest_indicators = financial_indicators.iloc[-1].to_dict()

        # 提取关键利润表指标
        income_data = {}
        if not income_statement.empty:
            income_data = income_statement.iloc[-1].to_dict()

        # 股票基本信息
        stock_info = {}
        if not stock_quote.empty:
            stock_info = stock_quote.iloc[0].to_dict()

        logger.info(f"成功获取基本面数据")

        # 调整处理市场信息，确保字段名正确匹配
        market_summary = {
            "name": stock_info.get("名称", ""),
            "pe_ratio": stock_info.get("市盈率-动态", None),  # 调整为实际字段名
            "pb_ratio": stock_info.get("市净率", None),
            "market_cap": stock_info.get("总市值", None),
            "industry": ""  # 行情数据中没有行业信息
        }

        return {
            "financial_indicators": latest_indicators,
            "income_statement": income_data,
            "stock_info": stock_info,
            "summary": market_summary
        }

    except Exception as e:
        logger.error(f"获取基本面数据时发生错误: {str(e)}")
        return {
            "financial_indicators": {},
            "income_statement": {},
            "stock_info": {},
            "summary": {
                "name": "",
                "pe_ratio": None,
                "pb_ratio": None,
                "market_cap": None,
                "industry": ""
            },
            "error": str(e)
        }

def get_stock_news(ticker: str, num_of_news: int = 5) -> List[Dict[str, Any]]:
    """
    获取股票相关新闻

    Args:
        ticker: 股票代码
        num_of_news: 获取的新闻数量

    Returns:
        List[Dict[str, Any]]: 新闻列表
    """
    try:
        logger.info(f"获取股票 {ticker} 的新闻数据 (共{num_of_news}条)")

        # 获取股票名称
        real_time_quote = ak.stock_zh_a_spot_em()
        stock_quote = real_time_quote[real_time_quote['代码'] == ticker]
        stock_name = stock_quote.iloc[0]['名称'] if not stock_quote.empty else ""

        if not stock_name:
            logger.warning(f"无法获取股票 {ticker} 的名称")
            return []

        # 获取财经新闻
        news_df = ak.stock_news_em(symbol=stock_name)

        # 记录新闻数据字段名，帮助调试
        if not news_df.empty:
            logger.info(f"新闻数据字段: {news_df.columns.tolist()}")

        # 筛选最近的新闻
        recent_news = news_df.head(min(num_of_news, len(news_df)))

        news_list = []
        for _, row in recent_news.iterrows():
            news_list.append({
                "title": row.get("新闻标题", ""),
                "content": row.get("新闻内容", ""),
                "date": row.get("发布时间", ""),
                "source": row.get("文章来源", "")  # 调整为正确的字段名
            })

        logger.info(f"成功获取 {len(news_list)} 条新闻")
        return news_list

    except Exception as e:
        logger.error(f"获取新闻数据时发生错误: {str(e)}")
        return []

def calculate_technical_indicators(df: pd.DataFrame) -> Dict[str, Any]:
    """
    计算技术指标

    Args:
        df: 股票历史数据DataFrame

    Returns:
        Dict[str, Any]: 技术指标
    """
    try:
        logger.info("计算技术指标")

        if df.empty:
            logger.warning("没有历史数据用于计算技术指标")
            return {}

        # 记录实际的列名，帮助调试
        logger.info(f"传入DataFrame的列名: {df.columns.tolist()}")

        # 确保列名符合预期
        price_column = None
        volume_column = None

        # 逐个检查可能的列名
        for col_name in ["收盘", "close"]:
            if col_name in df.columns:
                price_column = col_name
                break

        for col_name in ["成交量", "volume"]:
            if col_name in df.columns:
                volume_column = col_name
                break

        if not price_column:
            logger.error("找不到有效的价格列，当前列名: " + str(df.columns.tolist()))
            return {"error": "找不到有效的价格列"}

        if not volume_column:
            logger.warning("找不到有效的成交量列，将只计算价格相关指标")

        # 提取收盘价和成交量数据
        close_prices = df[price_column].values
        volumes = df[volume_column].values if volume_column else None

        # 计算常用技术指标
        indicators = {}

        # 1. 移动平均线
        ma_windows = [5, 10, 20, 50, 200]
        for window in ma_windows:
            if len(close_prices) >= window:
                ma = pd.Series(close_prices).rolling(window=window).mean().values
                indicators[f"ma_{window}"] = ma.tolist()

        # 2. 相对强弱指数 (RSI)
        if len(close_prices) >= 14:
            delta = pd.Series(close_prices).diff()
            gain = delta.clip(lower=0)
            loss = -delta.clip(upper=0)
            avg_gain = gain.rolling(window=14).mean()
            avg_loss = loss.rolling(window=14).mean()
            rs = avg_gain / avg_loss
            rsi = 100 - (100 / (1 + rs))
            indicators["rsi"] = rsi.values.tolist()

        # 3. MACD (移动平均收敛/发散)
        if len(close_prices) >= 26:
            exp12 = pd.Series(close_prices).ewm(span=12, adjust=False).mean()
            exp26 = pd.Series(close_prices).ewm(span=26, adjust=False).mean()
            macd = exp12 - exp26
            signal = macd.ewm(span=9, adjust=False).mean()
            indicators["macd"] = macd.values.tolist()
            indicators["macd_signal"] = signal.values.tolist()
            indicators["macd_histogram"] = (macd - signal).values.tolist()

        # 4. 布林带
        if len(close_prices) >= 20:
            ma20 = pd.Series(close_prices).rolling(window=20).mean()
            std20 = pd.Series(close_prices).rolling(window=20).std()
            upper_band = ma20 + (std20 * 2)
            lower_band = ma20 - (std20 * 2)
            indicators["bollinger_ma"] = ma20.values.tolist()
            indicators["bollinger_upper"] = upper_band.values.tolist()
            indicators["bollinger_lower"] = lower_band.values.tolist()

        logger.info("成功计算技术指标")
        return indicators

    except Exception as e:
        logger.error(f"计算技术指标时发生错误: {str(e)}")
        return {"error": str(e)}

# Stock Analysis Agent with AI Agents, using OWL 🦉
# This is a stock analysis system based on OWL multi-agent technology
# The system contains two core components:
# 1. Stock Analysis Assistant - Provides comprehensive stock investment advice and analysis
# 2. SEC Financial Data Analysis Assistant - Focuses on analyzing SEC files and financial statements
# The system uses the 🦉(OWL) framework to coordinate multiple AI agents to complete complex stock analysis tasks

from argparse import Namespace
from typing import Any, List, Optional, Dict, Callable
import time

import os
import logging
import json
import argparse

from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.types import ModelPlatformType,ModelType
from colorama import Fore, Style

from camel.toolkits import (
    SearchToolkit,
    FileWriteToolkit
)
from camel.societies import RolePlaying
from camel.logger import set_log_level, get_logger
from agent.sec_agent import get_sec_summary_for_company_tool
from prompts import get_system_prompt
from owl.utils import run_society
from pathlib import Path

# Load environment variables from .env file
load_dotenv()

# Create the log directory for debugging and logging purposes
LOG_DIR = "./log"
os.makedirs(LOG_DIR, exist_ok=True)

# Create the output directory for interview preparation materials
REPORT_DIR = "./output"
os.makedirs(REPORT_DIR, exist_ok=True)

set_log_level(level="INFO")
logger = get_logger(__name__)
file_handler = logging.FileHandler(LOG_DIR+"/stock_analysis.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

root_logger = logging.getLogger()
root_logger.addHandler(file_handler)

def run_society_with_strict_limit(society, round_limit=5, progress_callback=None) -> tuple[str, List[dict[Any, Any]], dict[Any, Any]]:
    """Wrapper around run_society to ensure round limit is strictly enforced

    This implementation hijacks the step method to force termination after a specific number of rounds.
    """
    # Track rounds manually
    round_count = 0

    # Save original step function
    original_step = society.step

    # Override the step method
    def limited_step(*args, **kwargs):
        nonlocal round_count
        round_count += 1

        # Report progress if callback is provided
        if progress_callback and callable(progress_callback):
            progress_callback(round_count, round_limit)

        # Force termination after reaching the round limit
        if round_count >= round_limit:
            logger.info(f"Reached round limit of {round_limit}, forcibly terminating.")
            # Force a TASK_DONE in the user response to trigger termination
            result = original_step(*args, **kwargs)
            if len(result) >= 2 and hasattr(result[1], 'msgs') and result[1].msgs and len(result[1].msgs) > 0:
                result[1].msgs[0].content += "\n\nTASK_DONE"
                result[1].terminated = True
            return result

        return original_step(*args, **kwargs)

    # Replace the step method
    society.step = limited_step

    try:
        # Run the conversation with the standard run_society function
        answer, chat_history, token_count = run_society(society, round_limit=round_limit)

        # Add a note about the conversation being truncated
        if len(chat_history) > 0 and "truncated_note" not in chat_history[-1]:
            chat_history[-1]["truncated_note"] = True
            if "assistant" in chat_history[-1]:
                chat_history[-1]["assistant"] += "\n\n[Note: This conversation was limited to maintain response quality.]"

        return answer, chat_history, token_count

    finally:
        # Restore the original step method
        society.step = original_step

def construct_stock_analysis_society( company_name: str) -> RolePlaying:
    """Construct a society of agents for the stock analysis.

    Args:
        task (str): Analyze relevant stock information and output a report.

    Returns:
        RolePlaying: A configured society of agents for the stock analysis.
    """
    models = {
       "user": ModelFactory.create(
            model_platform=ModelPlatformType.DEEPSEEK,
            model_type=ModelType.DEEPSEEK_CHAT,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.DEEPSEEK,
            model_type=ModelType.DEEPSEEK_CHAT,
            model_config_dict={"temperature": 0},
        ),
    }

    tools = [
        *FileWriteToolkit(output_dir=REPORT_DIR).get_tools(),
        SearchToolkit().search_baidu,
        get_sec_summary_for_company_tool

    ]

    if os.environ.get("GOOGLE_API_KEY") and os.environ.get("SEARCH_ENGINE_ID"):
        tools.append(SearchToolkit().search_google)

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

     # Build enhanced prompt asking for full, detailed output
    base_prompt = get_system_prompt()
    enhanced_prompt = f"""{base_prompt}
Task: Generate a comprehensive investment analysis report for {company_name}.
Requirements:
1. Research and collect company information:
   - Use search tools to gather general company background, industry position, and recent news
   - Utilize SEC tools to obtain financial statements and regulatory filings
   - Analyze key financial metrics and performance indicators

2. Generate a detailed markdown report with the following sections:
   - Company Overview and Business Model
   - Industry Analysis and Market Position
   - Financial Analysis (last 3-5 years)
     * Revenue and Profit Trends
     * Balance Sheet Analysis
     * Cash Flow Analysis
     * Key Financial Ratios
   - Risk Assessment
   - Investment Recommendation

3. Report Format Requirements:
   - Write in professional markdown format
   - Include data tables and key metrics
   - Minimum 2000 words comprehensive analysis
   - You MUST use the write_to_file tool to save the report as '{company_name}_investment_analysis.md'
   - Confirm the file has been written successfully before completing the task
"""

    task_kwargs = {
        "task_prompt": enhanced_prompt,
        "with_task_specify": False,
    }

    society = RolePlaying(
        **task_kwargs,
        user_role_name="financial_analyst",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="investment_advisor",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def save_chat_history(chat_history: List[dict[Any, Any]], company_name: str) -> None:
    """Analyze chat history and extract tool call information."""

    # 保存聊天历史记录到文件
    chat_history_file = os.path.join(LOG_DIR, f"{company_name}_chat_history.json")
    with open(chat_history_file, "w", encoding="utf-8") as f:
        json.dump(chat_history, f, ensure_ascii=False, indent=2)

    print(f"{Fore.GREEN}Records saved to {chat_history_file}{Style.RESET_ALL}")

def parse_arguments() -> Namespace:
    """Parse command line arguments"""
    parser = argparse.ArgumentParser(description='Stock Analysis Agent')
    parser.add_argument('--company', type=str, default="Google", help='Company name to analyze')
    parser.add_argument('--use-agentops', action='store_true', help='Enable AgentOps tracking')
    parser.add_argument('--rounds', type=int, default=5, help='Maximum conversation rounds')
    return parser.parse_args()

def init_agentops(use_agentops) -> bool:
    """Initialize AgentOps tracking (if enabled)"""
    if not use_agentops:
        return False

    import agentops
    agentops_api_key = os.getenv("AGENTOPS_API_KEY")
    if agentops_api_key:
        agentops.init(agentops_api_key, default_tags=["Stock Analysis Agent using owl"])
        print(f"{Fore.GREEN}AgentOps tracking enabled{Style.RESET_ALL}")
        return True
    else:
        print(f"{Fore.YELLOW}Warning: AGENTOPS_API_KEY not set, AgentOps tracking disabled{Style.RESET_ALL}")
        return False

def generate_stock_investment_report(
    company_name: str,
    round_limit: int = 5,
    progress_callback: Optional[Callable] = None
) -> Dict[str, Any]:
    start_time = time.time()
    logging.info(f"Starting stock investment report generation for {company_name})")

    society = construct_stock_analysis_society(company_name=company_name)

    # Use our wrapper function to strictly enforce the round limit
    answer, chat_history, token_count = run_society_with_strict_limit(
        society,
        round_limit=round_limit,
        progress_callback=progress_callback
    )

    duration = time.time() - start_time
    logging.info(f"Completed stock investment report generation for {company_name} in {duration:.2f} seconds")

    # Find any files that were generated
    generated_files = [str(file) for file in Path(REPORT_DIR).glob("*") if file.is_file()]

    return {
        "answer": answer,
        "chat_history": chat_history,
        "token_count": token_count,
        "generated_files": generated_files,
        "duration_seconds": duration
    }

if __name__ == "__main__":
    # Parse command line arguments
    args = parse_arguments()

    # Initialize AgentOps (if needed)
    agentops_enabled = init_agentops(args.use_agentops)

    company_name = args.company
    max_rounds = args.rounds

    print(f"{Fore.CYAN}=> Generating stock investment report for {company_name}...{Style.RESET_ALL}")
    result = generate_stock_investment_report(company_name, round_limit=max_rounds)

    print(f"{Fore.CYAN}=> Stock investment report generation completed!{Style.RESET_ALL}")
    # Record tool usage history
    save_chat_history(chat_history=result['chat_history'], company_name=company_name)

    print(f"{Fore.YELLOW}Answer: {Style.RESET_ALL}{result['answer']}\n")
    print(f"{Fore.BLUE}Generated files: {Style.RESET_ALL}{result['generated_files']}")
    print(f"{Fore.MAGENTA}Token Count: {Style.RESET_ALL}{result['token_count']}")
    print(f"{Fore.GREEN}Execution time: {Style.RESET_ALL}{result['duration_seconds']:.2f} seconds")

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys

sys.path.append("../")

import json
import random
import re
import string
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Union, Tuple

from tqdm import tqdm
from camel.benchmarks import BaseBenchmark
from camel.tasks import Task
from camel.logger import get_logger

from .common import extract_pattern
from .enhanced_role_playing import run_society, OwlGAIARolePlaying

logger = get_logger(__name__)

class GAIABenchmark(BaseBenchmark):
    r"""GAIA Benchmark adapted from `"GAIA: a benchmark for General AI
    Assistants"
    <https://huggingface.co/datasets/gaia-benchmark/GAIA>`_.

    Args:
        data_dir (str): The directory to save the data.
        save_to (str): The file to save the results.
        processes (int, optional): The number of processes to use.
            (default: :obj:`1`)
    """

    def __init__(
        self,
        data_dir: str,
        save_to: str,
        processes: int = 1,
    ):
        r"""Initialize the GAIA benchmark.

        Args:
            data_dir (str): The directory to save the data.
            save_to (str): The file to save the results.
            processes (int, optional): The number of processes to use for
                parallel processing. (default: :obj:`1`)
        """
        super().__init__("gaia", data_dir, save_to, processes)

    def download(self):
        r"""Download the GAIA dataset."""
        from huggingface_hub import snapshot_download

        snapshot_download(
            repo_id="gaia-benchmark/GAIA",
            repo_type="dataset",
            local_dir=self.data_dir,
            local_dir_use_symlinks=True,
        )

    def _check_task_completed(self, task_id: str) -> bool:
        for data in self._results:
            if data["task_id"] == task_id:
                return True
        return False

    def dump_tasks(self, save_path: str, datas):
        constructed_data = []
        for idx, data in enumerate(datas):
            tmp_dict = {
                "idx": idx,
                "task_id": data["task_id"],
                "Question": data["Question"],
                "Level": data["Level"],
                "Final answer": data["Final answer"],
                "Annotation Metadata": data["Annotator Metadata"],
            }

            constructed_data.append(tmp_dict)
        with open(save_path, "w", encoding="utf-8") as f:
            json.dump(constructed_data, f, indent=4)
        f.close()

        print(f"Successfully dumped tasks to {save_path}")

    def load(self, force_download=False):
        r"""Load the GAIA dataset.

        Args:
            force_download (bool, optional): Whether to
                force download the data.
        """
        if force_download:
            logger.info("Force downloading data.")
            self.download()

        # Define validation and test directories
        valid_dir = self.data_dir / "2023/validation"
        test_dir = self.data_dir / "2023/test"

        # Check if directories exist; if not, download the data
        if not valid_dir.is_dir() or not test_dir.is_dir():
            logger.info("Data not found. Downloading data.")
            self.download()

        # Load metadata for both validation and test datasets
        for path, label in zip([valid_dir, test_dir], ["valid", "test"]):
            self._data[label] = []
            with open(path / "metadata.jsonl", "r") as f:
                lines = f.readlines()
                for line in lines:
                    data = json.loads(line)
                    if data["task_id"] == "0-0-0-0-0":
                        continue
                    if data["file_name"]:
                        data["file_name"] = path / data["file_name"]
                    self._data[label].append(data)
        return self

    @property
    def train(self):
        r"""Get the training set."""
        raise NotImplementedError("GAIA does not have a training set.")

    def run(
        self,
        user_role_name: str,
        assistant_role_name: str,
        user_agent_kwargs: dict,
        assistant_agent_kwargs: dict,
        on: Literal["train", "valid", "test"],
        level: Union[int, List[int], Literal["all"]],
        randomize: bool = False,
        subset: Optional[int] = None,
        idx: Optional[List[int]] = None,
        save_result: bool = False,
    ) -> Dict[str, Any]:
        # Validate inputs
        if on not in ["valid", "test"]:
            raise ValueError(
                f"Invalid value for `on`: {on}, expected 'valid' or 'test'."
            )

        levels = (
            [1, 2, 3]
            if level == "all"
            else [level]
            if isinstance(level, int)
            else level
        )
        if not all(isinstance(level, int) and level in [1, 2, 3] for level in levels):
            raise ValueError(
                f"Invalid value for `level`: {level}, expected 1, 2, 3 " "or 'all'."
            )
        logger.info(f"Running benchmark on {on} set at levels {levels}.")
        datas = [data for data in self._data[on] if data["Level"] in levels]
        # Shuffle and subset data if necessary
        if randomize:
            random.shuffle(datas)
        if subset:
            datas = datas[:subset]

        if idx is not None:
            # pick only the tasks with the specified idx
            if len(idx) != 0:
                datas = [datas[i] for i in idx]

        logger.info(f"Number of tasks: {len(datas)}")

        self._results = []

        if save_result:
            try:
                with open(self.save_to, "r", encoding="utf-8") as f:
                    self._results = json.load(f)
                f.close()
            except Exception as e:
                logger.warning(e)
                # raise FileNotFoundError(f"{self.save_to} does not exist.")
        datas = [
            data for data in datas if not self._check_task_completed(data["task_id"])
        ]
        logger.info(f"Number of tasks to be processed: {len(datas)}")
        # Process tasks
        for task in tqdm(datas, desc="Running"):
            if_prepared_task, info = self._prepare_task(task)
            if not if_prepared_task:
                _result_info = {
                    "task_id": task["task_id"],
                    "question": task["Question"],
                    "level": task["Level"],
                    "model_answer": None,
                    "ground_truth": None,
                    "score": 0,
                    "history": None,
                }
                self._results.append(_result_info)
                continue
            try:
                logger.info(f"Task Question: {task['Question']}")
                logger.info(f"Required tools: {task['Annotator Metadata']['Tools']}")

                task_kwargs = {
                    "task_prompt": task["Question"],
                    "with_task_specify": False,
                }

                society = OwlGAIARolePlaying(
                    **task_kwargs,
                    user_role_name=user_role_name,
                    user_agent_kwargs=user_agent_kwargs,
                    assistant_role_name=assistant_role_name,
                    assistant_agent_kwargs=assistant_agent_kwargs,
                )

                raw_answer, chat_history, token_info = run_society(society)
                try:
                    answer = extract_pattern(raw_answer, "final_answer")
                except Exception as e:
                    logger.error(
                        f"Error in extracting final answer from text {raw_answer}: {e}"
                    )
                    answer = None

                logger.info(
                    f"Model answer: {answer}, Ground truth: {task['Final answer']}"
                )

                _result_info = {
                    "task_id": task["task_id"],
                    "question": task["Question"]
                    + "Please decompose the task into several sub-tasks and find the answer step-by-step.",
                    "level": task["Level"],
                    "model_answer": answer,
                    "ground_truth": task["Final answer"],
                    "score": self.question_scorer(answer, task["Final answer"]),
                    "token_info": token_info,
                    "history": chat_history,
                }
                self._results.append(_result_info)

            except Exception as e:
                logger.error(f"Error in processing task: {e}")

            if save_result:
                with open(self.save_to, "w") as f:
                    json.dump(self._results, f, indent=4, ensure_ascii=False)
                f.close()

        return self._generate_summary()

    def _prepare_task(self, task: Dict[str, Any]) -> Tuple[bool, str]:
        r"""Prepare the task by validating and enriching its data."""
        if task["file_name"]:
            if isinstance(task["file_name"], Path):
                task["file_name"] = str(task["file_name"])

            file_path = Path(task["file_name"])
            if not file_path.exists():
                logger.info(f"Skipping task because file not found: {file_path}")
                return False, f"Skipping task because file not found: {file_path}"
            if file_path.suffix in [".pdf", ".docx", ".doc", ".txt"]:
                task["Question"] += (
                    f" Here are the necessary document files: {file_path}"
                )

            elif file_path.suffix in [".jpg", ".jpeg", ".png"]:
                task["Question"] += f" Here are the necessary image files: {file_path}"

            elif file_path.suffix in [".xlsx", "xls", ".csv"]:
                task["Question"] += (
                    f" Here are the necessary table files: {file_path}, for processing excel file, you can write python code and leverage excel toolkit to process the file step-by-step and get the information."
                )

            elif file_path.suffix in [".py"]:
                task["Question"] += f" Here are the necessary python files: {file_path}"

            else:
                task["Question"] += f" Here are the necessary files: {file_path}"

        return True, None

    def _create_task(self, task: Dict[str, Any]) -> Task:
        r"""Create a user message from a task.

        Args:
            task (Dict[str, Any]): The task to create the message from.

        Returns:
            Task: The task created from the input.
        """
        return Task(id=str(task["task_id"]), content=task["Question"])

    def _generate_summary(self) -> Dict[str, Any]:
        r"""Generate and return a summary of the benchmark results."""
        correct = sum(result["score"] for result in self._results)
        return {
            "total": len(self._results),
            "correct": correct,
            "results": self._results,
            "accuracy": correct / len(self._results) if len(self._results) > 0 else 0,
        }

    def question_scorer(self, model_answer: str, ground_truth: str) -> bool:
        r"""Scorer for the GAIA benchmark.
        https://huggingface.co/spaces/gaia-benchmark/leaderboard/blob/main/
        scorer.py

        Args:
            model_answer (str): The model answer.
            ground_truth (str): The ground truth answer.

        Returns:
            bool: The score of the model
        """

        def is_float(element: Any) -> bool:
            try:
                float(element)
                return True
            except ValueError:
                return False

        if is_float(ground_truth):
            logger.info(f"Evaluating {model_answer} as a number.")
            normalized_answer = self.normalize_number_str(model_answer)
            return normalized_answer == float(ground_truth)

        elif any(char in ground_truth for char in [",", ";"]):
            logger.info(f"Evaluating {model_answer} as a comma separated list.")
            gt_elems = self.split_string(ground_truth)
            ma_elems = self.split_string(model_answer)

            if len(gt_elems) != len(ma_elems):
                logger.warning(
                    "Answer lists have different lengths, returning False.",
                    UserWarning,
                )
                return False

            comparisons = []
            for ma_elem, gt_elem in zip(ma_elems, gt_elems):
                if is_float(gt_elem):
                    normalized_ma_elem = self.normalize_number_str(ma_elem)
                    comparisons.append(normalized_ma_elem == float(gt_elem))
                else:
                    ma_elem = self.normalize_str(ma_elem, remove_punct=False)
                    gt_elem = self.normalize_str(gt_elem, remove_punct=False)
                    comparisons.append(ma_elem == gt_elem)
            return all(comparisons)
        else:
            logger.info(f"Evaluating {model_answer} as a string.")
            ma_elem = self.normalize_str(model_answer)
            gt_elem = self.normalize_str(ground_truth)
            return ma_elem == gt_elem

    def normalize_number_str(self, number_str: str) -> float:
        for char in ["$", "%", ","]:
            number_str = number_str.replace(char, "")
        try:
            return float(number_str)
        except ValueError:
            logger.error(f"String {number_str} cannot be normalized to number str.")
            return float("inf")

    def split_string(self, s: str, char_list: Optional[List[str]] = None) -> list[str]:
        r"""Split a string based on a list of characters.

        Args:
            s (str): The string to split.
            char_list (Optional[List[str]], optional): T
                he list of characters to split on.
                (default: :obj:`None`)
        """
        if char_list is None:
            char_list = [",", ";"]
        pattern = f"[{''.join(char_list)}]"
        return re.split(pattern, s)

    def normalize_str(self, input_str, remove_punct=True) -> str:
        r"""Normalize a string.

        Args:
            input_str: The input string to normalize.
            remove_punct: Whether to remove punctuation.

        Returns:
            str: The normalized string.
        """
        no_spaces = re.sub(r"\s", "", input_str)
        if remove_punct:
            translator = str.maketrans("", "", string.punctuation)
            return no_spaces.lower().translate(translator)
        else:
            return no_spaces.lower()

"""
代理基类模块

定义所有代理的共同基类和接口
"""
from typing import Dict, Any, Optional, List
from abc import ABC, abstractmethod
import json
import logging
import re
from camel.agents import ChatAgent
from camel.messages import BaseMessage

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("logs/agents.log"),
        logging.StreamHandler()
    ]
)

class BaseAgent(ABC):
    """代理基类"""

    def __init__(self, role_agent: ChatAgent, show_reasoning: bool = False, model_name: str = "gemini"):
        """初始化代理

        Args:
            role_agent: Camel框架的聊天代理
            show_reasoning: 是否显示推理过程
            model_name: 使用的模型名称 (gemini, openai, qwen)
        """
        self.agent = role_agent
        self.show_reasoning = show_reasoning
        self.model_name = model_name
        self.logger = logging.getLogger(self.__class__.__name__)

    def log_message(self, message: BaseMessage) -> None:
        """记录消息

        Args:
            message: 要记录的消息
        """
        if self.show_reasoning:
            print(f"\n{'='*80}")
            print(f"【{self.__class__.__name__}】推理过程:")
            print(f"{'-'*80}")
            print(message.content)
            print(f"{'='*80}\n")

        # 记录到日志
        self.logger.info(f"推理过程: {message.content[:100]}...")

    def format_data(self, data: Dict[str, Any]) -> str:
        """格式化数据为字符串

        Args:
            data: 要格式化的数据

        Returns:
            str: 格式化后的字符串
        """
        return json.dumps(data, ensure_ascii=False, indent=2)

    def parse_json_response(self, response: str) -> Dict[str, Any]:
        """从响应中解析JSON

        尝试从响应文本中提取JSON数据，处理各种边缘情况

        Args:
            response: 响应文本

        Returns:
            Dict[str, Any]: 解析后的JSON数据
        """
        try:
            # 尝试直接解析
            return json.loads(response)
        except json.JSONDecodeError:
            pass

        # 尝试从Markdown代码块中提取JSON
        json_pattern = r"```(?:json)?\s*([\s\S]*?)\s*```"
        matches = re.findall(json_pattern, response)

        if matches:
            for match in matches:
                try:
                    return json.loads(match)
                except json.JSONDecodeError:
                    continue

        # 尝试找到大括号包裹的内容
        brace_pattern = r"\{[\s\S]*\}"
        matches = re.findall(brace_pattern, response)

        if matches:
            for match in matches:
                try:
                    return json.loads(match)
                except json.JSONDecodeError:
                    continue

        # 如果所有尝试都失败，返回空字典并记录错误
        self.logger.error(f"无法从响应中解析JSON: {response}")
        return {}

    @abstractmethod
    def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """处理输入数据并返回结果

        Args:
            data: 输入数据

        Returns:
            Dict[str, Any]: 处理结果
        """
        pass

    def generate_human_message(self, content: str) -> BaseMessage:
        """生成人类消息

        Args:
            content: 消息内容

        Returns:
            BaseMessage: 用户消息对象
        """
        return BaseMessage.make_user_message(role_name="user", content=content)

    def generate_ai_message(self, content: str) -> BaseMessage:
        """生成AI消息

        Args:
            content: 消息内容

        Returns:
            BaseMessage: 助手消息对象
        """
        return BaseMessage.make_assistant_message(role_name="assistant", content=content)

"""
角色定义模块

使用Camel框架定义系统中的各种AI代理角色
"""
from camel.agents import ChatAgent
from camel.messages import BaseMessage
from camel.models import ModelFactory
from camel.types import ModelType, ModelPlatformType
from camel.configs.qwen_config import QwenConfig
from camel.configs.openai_config import ChatGPTConfig
import os
from typing import Dict, Any, List, Optional
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

# 获取API密钥和模型配置
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-1.5-flash")  # 默认模型

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o")  # 默认模型

QWEN_API_KEY = os.getenv("QWEN_API_KEY")
QWEN_MODEL = os.getenv("QWEN_MODEL", "qwen-max")  # 默认模型
QWEN_API_URL = os.getenv("QWEN_API_URL", "")  # API URL

# 角色系统提示
MARKET_DATA_ANALYST_PROMPT = """
你是一名专业的市场数据分析师，负责收集、处理和分析A股市场数据。
你的主要职责是：
1. 收集股票的历史价格、交易量和其他市场数据
2. 计算各种技术指标（如移动平均线、相对强弱指数等）
3. 整理和预处理数据，确保数据质量和准确性
4. 标记数据异常并进行适当处理

请以专业、精确的方式分析数据，确保你提供的数据是正确的，并为后续分析提供坚实基础。
"""

TECHNICAL_ANALYST_PROMPT = """
你是一名经验丰富的技术分析师，专注于通过技术指标和图表模式分析A股市场。
你的主要职责是：
1. 分析价格走势、交易量和技术指标
2. 识别支撑位和阻力位
3. 寻找图表模式（如头肩顶、三角形等）
4. 分析动量和趋势强度
5. 提供基于技术分析的交易信号

请基于市场数据分析师提供的数据，进行深入的技术分析，并给出明确的交易信号（看涨、看跌或中性）及相应的置信度。
"""

FUNDAMENTALS_ANALYST_PROMPT = """
你是一名资深的基本面分析师，专注于分析A股上市公司的财务状况和业务表现。
你的主要职责是：
1. 分析公司财务报表（资产负债表、利润表、现金流量表）
2. 计算和解释关键财务比率（如市盈率、市净率、ROE等）
3. 评估公司业务模式和竞争优势
4. 分析行业趋势和公司在行业中的地位
5. 提供基于基本面的投资建议

请基于市场数据分析师提供的数据，进行深入的基本面分析，并给出明确的交易信号（看涨、看跌或中性）及相应的置信度。
"""

SENTIMENT_ANALYST_PROMPT = """
你是一名市场情绪分析师，专注于分析A股市场相关的新闻、社交媒体讨论和市场情绪指标。
你的主要职责是：
1. 分析与特定股票相关的新闻报道
2. 评估市场对该股票的整体情绪（积极、中性或消极）
3. 识别可能影响市场情绪的重要事件或新闻
4. 评估市场情绪指标（如恐惧与贪婪指数）
5. 提供基于情绪分析的交易信号

请基于市场数据分析师提供的数据和新闻内容，进行深入的情绪分析，并给出明确的交易信号（看涨、看跌或中性）及相应的置信度。
"""

VALUATION_ANALYST_PROMPT = """
你是一名专业的估值分析师，专注于确定A股上市公司的内在价值。
你的主要职责是：
1. 应用不同的估值模型（如DCF、相对估值法）
2. 计算公司的内在价值
3. 比较当前市场价格与内在价值
4. 评估股票是否被高估或低估
5. 提供基于估值的交易信号

请基于市场数据分析师和基本面分析师提供的数据，进行深入的估值分析，并给出明确的交易信号（看涨、看跌或中性）及相应的置信度。
"""

RESEARCHER_BULL_PROMPT = """
你是一名持有看多观点的研究员，专注于寻找支持买入特定A股的理由。
你的主要职责是：
1. 分析和解释各类分析师（技术、基本面、情绪、估值）提供的积极信号
2. 寻找被市场忽视的积极因素
3. 探索公司未来增长的潜在催化剂
4. 构建支持买入决策的完整论据
5. 为投资决策提供看多的研究报告

请基于各位分析师提供的数据和分析，提出最有力的看多论据，并提供一份全面的研究报告。
"""

RESEARCHER_BEAR_PROMPT = """
你是一名持有看空观点的研究员，专注于寻找支持卖出特定A股的理由。
你的主要职责是：
1. 分析和解释各类分析师（技术、基本面、情绪、估值）提供的消极信号
2. 寻找被市场忽视的风险因素
3. 探索可能导致股价下跌的潜在风险
4. 构建支持卖出决策的完整论据
5. 为投资决策提供看空的研究报告

请基于各位分析师提供的数据和分析，提出最有力的看空论据，并提供一份全面的研究报告。
"""

DEBATE_ROOM_PROMPT = """
你是一个投资辩论室的主持人，负责整合多头和空头研究员的观点，形成一个平衡的投资视角。
你的主要职责是：
1. 公正评估多头和空头研究员的论据
2. 识别双方论据中的优点和弱点
3. 权衡不同因素的重要性
4. 形成综合性的市场观点
5. 提出平衡的投资建议

请基于多头和空头研究员提供的研究报告，进行深入的分析和辩论，并给出一个综合性的市场观点和投资建议。
"""

RISK_MANAGER_PROMPT = """
你是一名资深的风险管理经理，负责评估和管理投资决策的风险。
你的主要职责是：
1. 计算投资组合的风险指标（如波动率、最大回撤等）
2. 设定风险限制（如持仓限制、止损水平）
3. 评估市场和特定股票的风险水平
4. 根据投资者风险偏好调整风险暴露
5. 提供风险管理建议

请基于市场数据、投资组合状况和辩论室的综合观点，进行深入的风险分析，并提供明确的风险管理建议，包括建议的持仓规模和风险控制措施。
"""

PORTFOLIO_MANAGER_PROMPT = """
你是一名投资组合经理，负责做出最终的投资决策并管理整体投资组合。
你的主要职责是：
1. 整合各类分析和建议（技术、基本面、情绪、风险等）
2. 做出最终的投资决策（买入、卖出或持有）
3. 确定具体的交易数量和价格
4. 平衡投资组合的风险与回报
5. 优化资金分配

请基于各位分析师和风险管理经理提供的信息，做出最终的投资决策，包括具体的交易行动、数量和执行策略。
"""

# 创建模型配置
def get_model_config(model_name: str = "gemini") -> tuple:
    """获取模型配置

    Args:
        model_name: 模型名称 (gemini, openai, qwen)

    Returns:
        tuple: (ModelPlatformType, ModelType, config_dict) 模型平台类型、模型类型和配置字典
    """
    model_name = model_name.lower()

    if model_name == "gemini":
        if not GEMINI_API_KEY:
            raise ValueError("缺少Gemini API密钥，请在.env文件中设置GEMINI_API_KEY")
        return ModelPlatformType.GEMINI, ModelType.GEMINI_1_5_FLASH, {"api_key": GEMINI_API_KEY, "model": GEMINI_MODEL}

    elif model_name == "openai":
        if not OPENAI_API_KEY:
            raise ValueError("缺少OpenAI API密钥，请在.env文件中设置OPENAI_API_KEY")

        # 返回GPT-4o配置
        return ModelPlatformType.OPENAI, ModelType.GPT_4O, {"api_key": OPENAI_API_KEY, "model": OPENAI_MODEL}

    elif model_name == "qwen":
        if not QWEN_API_KEY:
            raise ValueError("缺少Qwen API密钥，请在.env文件中设置QWEN_API_KEY")

        # 使用QwenConfig类创建配置，不包含model参数
        qwen_config = QwenConfig(temperature=0.1)
        config = qwen_config.as_dict()

        # 移除可能导致错误的参数
        if "model" in config:
            del config["model"]

        # 使用QWEN_MAX类型
        return ModelPlatformType.QWEN, ModelType.QWEN_MAX, config

    else:
        raise ValueError(f"不支持的模型: {model_name}，支持的模型: gemini, openai, qwen")

# 创建角色代理工厂函数
def create_role_agent(role: str, model_name: str = "gemini") -> ChatAgent:
    """创建特定角色的代理

    Args:
        role: 角色名称
        model_name: 模型名称 (gemini, openai, qwen)

    Returns:
        ChatAgent: 创建的角色代理
    """
    # 获取模型配置
    model_platform, model_type, model_config = get_model_config(model_name)

    # 获取角色的系统提示
    role_prompts = {
        "market_data_analyst": MARKET_DATA_ANALYST_PROMPT,
        "technical_analyst": TECHNICAL_ANALYST_PROMPT,
        "fundamentals_analyst": FUNDAMENTALS_ANALYST_PROMPT,
        "sentiment_analyst": SENTIMENT_ANALYST_PROMPT,
        "valuation_analyst": VALUATION_ANALYST_PROMPT,
        "researcher_bull": RESEARCHER_BULL_PROMPT,
        "researcher_bear": RESEARCHER_BEAR_PROMPT,
        "debate_room": DEBATE_ROOM_PROMPT,
        "risk_manager": RISK_MANAGER_PROMPT,
        "portfolio_manager": PORTFOLIO_MANAGER_PROMPT,
    }

    if role not in role_prompts:
        raise ValueError(f"未知角色: {role}")

    # 格式化角色名称为更友好的显示名称
    display_names = {
        "market_data_analyst": "市场数据分析师",
        "technical_analyst": "技术分析师",
        "fundamentals_analyst": "基本面分析师",
        "sentiment_analyst": "情绪分析师",
        "valuation_analyst": "估值分析师",
        "researcher_bull": "多头研究员",
        "researcher_bear": "空头研究员",
        "debate_room": "辩论室",
        "risk_manager": "风险管理经理",
        "portfolio_manager": "投资组合经理",
    }

    display_name = display_names.get(role, role)

    # 创建模型
    model = ModelFactory.create(
        model_platform=model_platform,
        model_type=model_type,
        model_config_dict=model_config
    )

    # 创建并返回代理
    return ChatAgent(
        model=model,
        system_message=role_prompts[role]
    )

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

from typing import Dict, List, Optional, Tuple
import threading

from camel.agents import ChatAgent
from camel.responses import ChatAgentResponse
from camel.messages.base import BaseMessage
from camel.societies import RolePlaying
from camel.logger import get_logger

from copy import deepcopy

logger = get_logger(__name__)

class OwlRolePlaying(RolePlaying):
    def __init__(self, **kwargs):
        self.user_role_name = kwargs.get("user_role_name", "user")
        self.assistant_role_name = kwargs.get("assistant_role_name", "assistant")

        self.output_language = kwargs.get("output_language", None)

        self.user_agent_kwargs: dict = kwargs.get("user_agent_kwargs", {})
        self.assistant_agent_kwargs: dict = kwargs.get("assistant_agent_kwargs", {})

        self.output_language = kwargs.get("output_language", None)

        super().__init__(**kwargs)

        init_user_sys_msg, init_assistant_sys_msg = self._construct_gaia_sys_msgs()

        self.assistant_agent: ChatAgent
        self.user_agent: ChatAgent
        self.assistant_sys_msg: Optional[BaseMessage]
        self.user_sys_msg: Optional[BaseMessage]

        # self.is_reasoning_task = self._judge_if_reasoning_task(self.task_prompt)

        # if self.is_reasoning_task:
        #     logger.info("The task is judged as a reasoning or coding task. The assistant agent will use the reasoning model O3-MINI.")
        # else:
        #     logger.info("The assistant agent will use the default model.")

        self._init_agents(
            init_assistant_sys_msg,
            init_user_sys_msg,
            assistant_agent_kwargs=self.assistant_agent_kwargs,
            user_agent_kwargs=self.user_agent_kwargs,
            output_language=self.output_language,
            # is_reasoning_task=self.is_reasoning_task
        )

    def _init_agents(
        self,
        init_assistant_sys_msg: BaseMessage,
        init_user_sys_msg: BaseMessage,
        assistant_agent_kwargs: Optional[Dict] = None,
        user_agent_kwargs: Optional[Dict] = None,
        output_language: Optional[str] = None,
        is_reasoning_task: bool = False,
        stop_event: Optional[threading.Event] = None,
    ) -> None:
        r"""Initialize assistant and user agents with their system messages.

        Args:
            init_assistant_sys_msg (BaseMessage): Assistant agent's initial
                system message.
            init_user_sys_msg (BaseMessage): User agent's initial system
                message.
            assistant_agent_kwargs (Dict, optional): Additional arguments to
                pass to the assistant agent. (default: :obj:`None`)
            user_agent_kwargs (Dict, optional): Additional arguments to
                pass to the user agent. (default: :obj:`None`)
            output_language (str, optional): The language to be output by the
                agents. (default: :obj:`None`)
        """
        if self.model is not None:
            if assistant_agent_kwargs is None:
                assistant_agent_kwargs = {"model": self.model}
            elif "model" not in assistant_agent_kwargs:
                assistant_agent_kwargs.update(dict(model=self.model))
            if user_agent_kwargs is None:
                user_agent_kwargs = {"model": self.model}
            elif "model" not in user_agent_kwargs:
                user_agent_kwargs.update(dict(model=self.model))

        # # If the task is a reasoning task, the assistant agent should use the reasoning model O3-MINI
        # if is_reasoning_task:
        #     assistant_agent_kwargs['model'] = ModelFactory.create(
        #         model_platform=ModelPlatformType.OPENAI,
        #         model_type=ModelType.O3_MINI,
        #     )

        self.assistant_agent = ChatAgent(
            init_assistant_sys_msg,
            output_language=output_language,
            **(assistant_agent_kwargs or {}),
        )
        self.assistant_sys_msg = self.assistant_agent.system_message

        self.user_agent = ChatAgent(
            init_user_sys_msg,
            output_language=output_language,
            **(user_agent_kwargs or {}),
        )
        self.user_sys_msg = self.user_agent.system_message

    # def _judge_if_reasoning_task(self, question: str) -> bool:
    #     r"""Judge if the question is a reasoning task."""

    #     LLM = OpenAIModel(model_type=ModelType.O3_MINI)
    #     prompt = f"""
    #     Please judge whether the following question is a reasoning or coding task, which can be solved by reasoning without leveraging external resources, or is suitable for writing code to solve the task.
    #     If it is a reasoning or coding task, please return only "yes".
    #     If it is not a reasoning or coding task, please return only "no".
    #     Note:
    #     - If the question required some world knowledge to answer the question, please carefully judge it, because the model's own knowledge is often unreliable.
    #     - If it is suitable for writing codes (e.g. process excel files, write simulation codes, etc.), in most cases, it can be considered as a coding task.
    #     Question: <question>{question}</question>
    #     """
    #     messages = [{"role": "user", "content": prompt}]
    #     resp = LLM.run(messages)
    #     if 'yes' in resp.choices[0].message.content.lower():
    #         return True
    #     else:
    #         return False

    def _construct_gaia_sys_msgs(self):
        user_system_prompt = f"""
===== RULES OF USER =====
Never forget you are a user and I am a assistant. Never flip roles! You will always instruct me. We share a common interest in collaborating to successfully complete a task.
I must help you to complete a difficult task.
You must instruct me based on my expertise and your needs to solve the task step by step. The format of your instruction is: `Instruction: [YOUR INSTRUCTION]`, where "Instruction" describes a sub-task or question.
You must give me one instruction at a time.
I must write a response that appropriately solves the requested instruction.
You should instruct me not ask me questions.

Please note that the task may be very complicated. Do not attempt to solve the task by single step. You must instruct me to find the answer step by step.
Here are some tips that will help you to give more valuable instructions about our task to me:
<tips>
- I have various tools to use, such as search toolkit, web browser simulation toolkit, document relevant toolkit, code execution toolkit, etc. Thus, You must think how human will solve the task step-by-step, and give me instructions just like that. For example, one may first use google search to get some initial information and the target url, then retrieve the content of the url, or do some web browser interaction to find the answer.
- Although the task is complex, the answer does exist. If you can't find the answer using the current scheme, try to re-plan and use other ways to find the answer, e.g. using other tools or methods that can achieve similar results.
- Always remind me to verify my final answer about the overall task. This work can be done by using multiple tools(e.g., screenshots, webpage analysis, etc.), or something else.
- If I have written code, please remind me to run the code and get the result.
- Search results typically do not provide precise answers. It is not likely to find the answer directly using search toolkit only, the search query should be concise and focuses on finding sources rather than direct answers, as it always need to use other tools to further process the url, e.g. interact with the webpage, extract webpage content, etc.
- If the question mentions youtube video, in most cases you have to process the content of the mentioned video.
- For downloading files, you can either use the web browser simulation toolkit or write codes (for example, the github content can be downloaded via https://raw.githubusercontent.com/...).
- Flexibly write codes to solve some problems, such as excel relevant tasks.
</tips>

Now, here is the overall task: <task>{self.task_prompt}</task>. Never forget our task!

Now you must start to instruct me to solve the task step-by-step. Do not add anything else other than your instruction!
Keep giving me instructions until you think the task is completed.
When the task is completed, you must only reply with a single word <TASK_DONE>.
Never say <TASK_DONE> unless my responses have solved your task.
        """

        assistant_system_prompt = f"""
===== RULES OF ASSISTANT =====
Never forget you are a assistant and I am a user. Never flip roles! Never instruct me! You have to utilize your available tools to solve the task I assigned.
We share a common interest in collaborating to successfully complete a complex task.
You must help me to complete the task.

Here is our overall task: {self.task_prompt}. Never forget our task!

I must instruct you based on your expertise and my needs to complete the task. An instruction is typically a sub-task or question.

You must leverage your available tools, try your best to solve the problem, and explain your solutions.
Unless I say the task is completed, you should always start with:
Solution: [YOUR_SOLUTION]
[YOUR_SOLUTION] should be specific, including detailed explanations and provide preferable detailed implementations and examples and lists for task-solving.

Please note that our overall task may be very complicated. Here are some tips that may help you solve the task:
<tips>
- If one way fails to provide an answer, try other ways or methods. The answer does exists.
- If the search snippet is unhelpful but the URL comes from an authoritative source, try visit the website for more details.
- When looking for specific numerical values (e.g., dollar amounts), prioritize reliable sources and avoid relying only on search snippets.
- When solving tasks that require web searches, check Wikipedia first before exploring other websites.
- When trying to solve math problems, you can try to write python code and use sympy library to solve the problem.
- Always verify the accuracy of your final answers! Try cross-checking the answers by other ways. (e.g., screenshots, webpage analysis, etc.).
- Do not be overly confident in your own knowledge. Searching can provide a broader perspective and help validate existing knowledge.
- After writing codes, do not forget to run the code and get the result. If it encounters an error, try to debug it. Also, bear in mind that the code execution environment does not support interactive input.
- When a tool fails to run, or the code does not run correctly, never assume that it returns the correct result and continue to reason based on the assumption, because the assumed result cannot lead you to the correct answer. The right way is to think about the reason for the error and try again.
- Search results typically do not provide precise answers. It is not likely to find the answer directly using search toolkit only, the search query should be concise and focuses on finding sources rather than direct answers, as it always need to use other tools to further process the url, e.g. interact with the webpage, extract webpage content, etc.
- For downloading files, you can either use the web browser simulation toolkit or write codes.
</tips>

        """

        user_sys_msg = BaseMessage.make_user_message(
            role_name=self.user_role_name, content=user_system_prompt
        )

        assistant_sys_msg = BaseMessage.make_assistant_message(
            role_name=self.assistant_role_name, content=assistant_system_prompt
        )

        return user_sys_msg, assistant_sys_msg

    def step(
        self, assistant_msg: BaseMessage
    ) -> Tuple[ChatAgentResponse, ChatAgentResponse]:
        user_response = self.user_agent.step(assistant_msg)
        if user_response.terminated or user_response.msgs is None:
            return (
                ChatAgentResponse(msgs=[], terminated=False, info={}),
                ChatAgentResponse(
                    msgs=[],
                    terminated=user_response.terminated,
                    info=user_response.info,
                ),
            )
        user_msg = self._reduce_message_options(user_response.msgs)

        modified_user_msg = deepcopy(user_msg)

        if "TASK_DONE" not in user_msg.content:
            modified_user_msg.content += f"""\n
            Here are auxiliary information about the overall task, which may help you understand the intent of the current task:
            <auxiliary_information>
            {self.task_prompt}
            </auxiliary_information>
            If there are available tools and you want to call them, never say 'I will ...', but first call the tool and reply based on tool call's result, and tell me which tool you have called.
            """

        else:
            # The task is done, and the assistant agent need to give the final answer about the original task
            modified_user_msg.content += f"""\n
            Now please make a final answer of the original task based on our conversation : <task>{self.task_prompt}</task>
            """

        # process assistant's response
        assistant_response = self.assistant_agent.step(modified_user_msg)
        if assistant_response.terminated or assistant_response.msgs is None:
            return (
                ChatAgentResponse(
                    msgs=[],
                    terminated=assistant_response.terminated,
                    info=assistant_response.info,
                ),
                ChatAgentResponse(
                    msgs=[user_msg], terminated=False, info=user_response.info
                ),
            )
        assistant_msg = self._reduce_message_options(assistant_response.msgs)

        modified_assistant_msg = deepcopy(assistant_msg)
        if "TASK_DONE" not in user_msg.content:
            modified_assistant_msg.content += f"""\n
                Provide me with the next instruction and input (if needed) based on my response and our current task: <task>{self.task_prompt}</task>
                Before producing the final answer, please check whether I have rechecked the final answer using different toolkit as much as possible. If not, please remind me to do that.
                If I have written codes, remind me to run the codes.
                If you think our task is done, reply with `TASK_DONE` to end our conversation.
            """

        # return the modified messages
        return (
            ChatAgentResponse(
                msgs=[modified_assistant_msg],
                terminated=assistant_response.terminated,
                info=assistant_response.info,
            ),
            ChatAgentResponse(
                msgs=[modified_user_msg],
                terminated=user_response.terminated,
                info=user_response.info,
            ),
        )

    async def astep(
        self, assistant_msg: BaseMessage
    ) -> Tuple[ChatAgentResponse, ChatAgentResponse]:
        user_response = await self.user_agent.astep(assistant_msg)
        if user_response.terminated or user_response.msgs is None:
            return (
                ChatAgentResponse(msgs=[], terminated=False, info={}),
                ChatAgentResponse(
                    msgs=[],
                    terminated=user_response.terminated,
                    info=user_response.info,
                ),
            )
        user_msg = self._reduce_message_options(user_response.msgs)

        modified_user_msg = deepcopy(user_msg)

        if "TASK_DONE" not in user_msg.content:
            modified_user_msg.content += f"""\n
            Here are auxiliary information about the overall task, which may help you understand the intent of the current task:
            <auxiliary_information>
            {self.task_prompt}
            </auxiliary_information>
            If there are available tools and you want to call them, never say 'I will ...', but first call the tool and reply based on tool call's result, and tell me which tool you have called.
            """

        else:
            # The task is done, and the assistant agent need to give the final answer about the original task
            modified_user_msg.content += f"""\n
            Now please make a final answer of the original task based on our conversation : <task>{self.task_prompt}</task>
            """

        assistant_response = await self.assistant_agent.astep(modified_user_msg)
        if assistant_response.terminated or assistant_response.msgs is None:
            return (
                ChatAgentResponse(
                    msgs=[],
                    terminated=assistant_response.terminated,
                    info=assistant_response.info,
                ),
                ChatAgentResponse(
                    msgs=[user_msg], terminated=False, info=user_response.info
                ),
            )
        assistant_msg = self._reduce_message_options(assistant_response.msgs)

        modified_assistant_msg = deepcopy(assistant_msg)
        if "TASK_DONE" not in user_msg.content:
            modified_assistant_msg.content += f"""\n
                Provide me with the next instruction and input (if needed) based on my response and our current task: <task>{self.task_prompt}</task>
                Before producing the final answer, please check whether I have rechecked the final answer using different toolkit as much as possible. If not, please remind me to do that.
                If I have written codes, remind me to run the codes.
                If you think our task is done, reply with `TASK_DONE` to end our conversation.
            """

        return (
            ChatAgentResponse(
                msgs=[assistant_msg],
                terminated=assistant_response.terminated,
                info=assistant_response.info,
            ),
            ChatAgentResponse(
                msgs=[user_msg],
                terminated=user_response.terminated,
                info=user_response.info,
            ),
        )

class OwlGAIARolePlaying(OwlRolePlaying):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def step(
        self, assistant_msg: BaseMessage
    ) -> Tuple[ChatAgentResponse, ChatAgentResponse]:
        user_response = self.user_agent.step(assistant_msg)
        if user_response.terminated or user_response.msgs is None:
            return (
                ChatAgentResponse(msgs=[], terminated=False, info={}),
                ChatAgentResponse(
                    msgs=[],
                    terminated=user_response.terminated,
                    info=user_response.info,
                ),
            )
        user_msg = self._reduce_message_options(user_response.msgs)

        modified_user_msg = deepcopy(user_msg)

        if "TASK_DONE" not in user_msg.content:
            modified_user_msg.content += f"""\n
            Here are auxiliary information about the overall task, which may help you understand the intent of the current task:
            <auxiliary_information>
            {self.task_prompt}
            </auxiliary_information>
            If there are available tools and you want to call them, never say 'I will ...', but first call the tool and reply based on tool call's result, and tell me which tool you have called.
            """

        else:
            # The task is done, and the assistant agent need to give the final answer about the original task
            modified_user_msg.content += f"""\n
            Now please make a final answer of the original task based on our conversation : <task>{self.task_prompt}</task>
            Please pay special attention to the format in which the answer is presented.
            You should first analyze the answer format required by the question and then output the final answer that meets the format requirements.
            Your response should include the following content:
            - `analysis`: enclosed by <analysis> </analysis>, a detailed analysis of the reasoning result.
            - `final_answer`: enclosed by <final_answer> </final_answer>, the final answer to the question.
            Here are some hint about the final answer:
            <hint>
            Your final answer must be output exactly in the format specified by the question. It should be a number OR as few words as possible OR a comma separated list of numbers and/or strings:
            - If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
            - If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
            - If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.
            </hint>
            """

        # process assistant's response
        assistant_response = self.assistant_agent.step(modified_user_msg)
        if assistant_response.terminated or assistant_response.msgs is None:
            return (
                ChatAgentResponse(
                    msgs=[],
                    terminated=assistant_response.terminated,
                    info=assistant_response.info,
                ),
                ChatAgentResponse(
                    msgs=[user_msg], terminated=False, info=user_response.info
                ),
            )
        assistant_msg = self._reduce_message_options(assistant_response.msgs)

        modified_assistant_msg = deepcopy(assistant_msg)
        if "TASK_DONE" not in user_msg.content:
            modified_assistant_msg.content += f"""\n
                Provide me with the next instruction and input (if needed) based on my response and our current task: <task>{self.task_prompt}</task>
                Before producing the final answer, please check whether I have rechecked the final answer using different toolkit as much as possible. If not, please remind me to do that.
                If I have written codes, remind me to run the codes.
                If you think our task is done, reply with `TASK_DONE` to end our conversation.
            """

        # return the modified messages
        return (
            ChatAgentResponse(
                msgs=[modified_assistant_msg],
                terminated=assistant_response.terminated,
                info=assistant_response.info,
            ),
            ChatAgentResponse(
                msgs=[modified_user_msg],
                terminated=user_response.terminated,
                info=user_response.info,
            ),
        )

def run_society(
    society: OwlRolePlaying,
    round_limit: int = 15,
) -> Tuple[str, List[dict], dict]:
    overall_completion_token_count = 0
    overall_prompt_token_count = 0

    chat_history = []
    init_prompt = """
    Now please give me instructions to solve over overall task step by step. If the task requires some specific knowledge, please instruct me to use tools to complete the task.
        """
    input_msg = society.init_chat(init_prompt)
    for _round in range(round_limit):
        assistant_response, user_response = society.step(input_msg)
        # Check if usage info is available before accessing it
        if assistant_response.info.get("usage") and user_response.info.get("usage"):
            overall_completion_token_count += assistant_response.info["usage"].get(
                "completion_tokens", 0
            ) + user_response.info["usage"].get("completion_tokens", 0)
            overall_prompt_token_count += assistant_response.info["usage"].get(
                "prompt_tokens", 0
            ) + user_response.info["usage"].get("prompt_tokens", 0)

        # convert tool call to dict
        tool_call_records: List[dict] = []
        if assistant_response.info.get("tool_calls"):
            for tool_call in assistant_response.info["tool_calls"]:
                tool_call_records.append(tool_call.as_dict())

        _data = {
            "user": user_response.msg.content
            if hasattr(user_response, "msg") and user_response.msg
            else "",
            "assistant": assistant_response.msg.content
            if hasattr(assistant_response, "msg") and assistant_response.msg
            else "",
            "tool_calls": tool_call_records,
        }

        chat_history.append(_data)
        logger.info(
            f"Round #{_round} user_response:\n {user_response.msgs[0].content if user_response.msgs and len(user_response.msgs) > 0 else ''}"
        )
        logger.info(
            f"Round #{_round} assistant_response:\n {assistant_response.msgs[0].content if assistant_response.msgs and len(assistant_response.msgs) > 0 else ''}"
        )

        if (
            assistant_response.terminated
            or user_response.terminated
            or "TASK_DONE" in user_response.msg.content
        ):
            break

        input_msg = assistant_response.msg

    answer = chat_history[-1]["assistant"]
    token_info = {
        "completion_token_count": overall_completion_token_count,
        "prompt_token_count": overall_prompt_token_count,
    }

    return answer, chat_history, token_info

async def arun_society(
    society: OwlRolePlaying,
    round_limit: int = 15,
) -> Tuple[str, List[dict], dict]:
    overall_completion_token_count = 0
    overall_prompt_token_count = 0

    chat_history = []
    init_prompt = """
    Now please give me instructions to solve over overall task step by step. If the task requires some specific knowledge, please instruct me to use tools to complete the task.
        """
    input_msg = society.init_chat(init_prompt)
    for _round in range(round_limit):
        assistant_response, user_response = await society.astep(input_msg)
        # Check if usage info is available before accessing it
        if assistant_response.info.get("usage") and user_response.info.get("usage"):
            overall_prompt_token_count += assistant_response.info["usage"].get(
                "completion_tokens", 0
            )
            overall_prompt_token_count += assistant_response.info["usage"].get(
                "prompt_tokens", 0
            ) + user_response.info["usage"].get("prompt_tokens", 0)

        # convert tool call to dict
        tool_call_records: List[dict] = []
        if assistant_response.info.get("tool_calls"):
            for tool_call in assistant_response.info["tool_calls"]:
                tool_call_records.append(tool_call.as_dict())

        _data = {
            "user": user_response.msg.content
            if hasattr(user_response, "msg") and user_response.msg
            else "",
            "assistant": assistant_response.msg.content
            if hasattr(assistant_response, "msg") and assistant_response.msg
            else "",
            "tool_calls": tool_call_records,
        }

        chat_history.append(_data)
        logger.info(
            f"Round #{_round} user_response:\n {user_response.msgs[0].content if user_response.msgs and len(user_response.msgs) > 0 else ''}"
        )
        logger.info(
            f"Round #{_round} assistant_response:\n {assistant_response.msgs[0].content if assistant_response.msgs and len(assistant_response.msgs) > 0 else ''}"
        )

        # Check other termination conditions
        if (
            assistant_response.terminated
            or user_response.terminated
            or "TASK_DONE" in user_response.msg.content
            or "任务已完成" in user_response.msg.content
        ):
            break

        input_msg = assistant_response.msg

    answer = chat_history[-1]["assistant"]
    token_info = {
        "completion_token_count": overall_completion_token_count,
        "prompt_token_count": overall_prompt_token_count,
    }

    return answer, chat_history, token_info

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Import from the correct module path
from utils import run_society
import os
import gradio as gr
import time
import json
import logging
import datetime
from typing import Tuple
import importlib
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
import threading
import queue
import re  # For regular expression operations

os.environ["PYTHONIOENCODING"] = "utf-8"

# 配置日志系统
def setup_logging():
    """配置日志系统，将日志输出到文件和内存队列以及控制台"""
    # 创建logs目录（如果不存在）
    logs_dir = os.path.join(os.path.dirname(__file__), "logs")
    os.makedirs(logs_dir, exist_ok=True)

    # 生成日志文件名（使用当前日期）
    current_date = datetime.datetime.now().strftime("%Y-%m-%d")
    log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")

    # 配置根日志记录器（捕获所有日志）
    root_logger = logging.getLogger()

    # 清除现有的处理器，避免重复日志
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)

    root_logger.setLevel(logging.INFO)

    # 创建文件处理器
    file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
    file_handler.setLevel(logging.INFO)

    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)

    # 创建格式化器
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    # 添加处理器到根日志记录器
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)

    logging.info("日志系统已初始化，日志文件: %s", log_file)
    return log_file

# 全局变量
LOG_FILE = None
LOG_QUEUE: queue.Queue = queue.Queue()  # 日志队列
STOP_LOG_THREAD = threading.Event()
CURRENT_PROCESS = None  # 用于跟踪当前运行的进程
STOP_REQUESTED = threading.Event()  # 用于标记是否请求停止

# 日志读取和更新函数
def log_reader_thread(log_file):
    """后台线程，持续读取日志文件并将新行添加到队列中"""
    try:
        with open(log_file, "r", encoding="utf-8") as f:
            # 移动到文件末尾
            f.seek(0, 2)

            while not STOP_LOG_THREAD.is_set():
                line = f.readline()
                if line:
                    LOG_QUEUE.put(line)  # 添加到对话记录队列
                else:
                    # 没有新行，等待一小段时间
                    time.sleep(0.1)
    except Exception as e:
        logging.error(f"日志读取线程出错: {str(e)}")

def get_latest_logs(max_lines=100, queue_source=None):
    """从队列中获取最新的日志行，如果队列为空则直接从文件读取

    Args:
        max_lines: 最大返回行数
        queue_source: 指定使用哪个队列，默认为LOG_QUEUE

    Returns:
        str: 日志内容
    """
    logs = []
    log_queue = queue_source if queue_source else LOG_QUEUE

    # 创建一个临时队列来存储日志，以便我们可以处理它们而不会从原始队列中删除它们
    temp_queue = queue.Queue()
    temp_logs = []

    try:
        # 尝试从队列中获取所有可用的日志行
        while not log_queue.empty() and len(temp_logs) < max_lines:
            log = log_queue.get_nowait()
            temp_logs.append(log)
            temp_queue.put(log)  # 将日志放回临时队列
    except queue.Empty:
        pass

    # 处理对话记录
    logs = temp_logs

    # 如果没有新日志或日志不足，尝试直接从文件读取最后几行
    if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "r", encoding="utf-8") as f:
                all_lines = f.readlines()
                # 如果队列中已有一些日志，只读取剩余需要的行数
                remaining_lines = max_lines - len(logs)
                file_logs = (
                    all_lines[-remaining_lines:]
                    if len(all_lines) > remaining_lines
                    else all_lines
                )

                # 将文件日志添加到队列日志之前
                logs = file_logs + logs
        except Exception as e:
            error_msg = f"读取日志文件出错: {str(e)}"
            logging.error(error_msg)
            if not logs:  # 只有在没有任何日志的情况下才添加错误消息
                logs = [error_msg]

    # 如果仍然没有日志，返回提示信息
    if not logs:
        return "初始化运行中..."

    # 过滤日志，只保留 camel.agents.chat_agent - INFO 的日志
    filtered_logs = []
    for log in logs:
        if "camel.agents.chat_agent - INFO" in log:
            filtered_logs.append(log)

    # 如果过滤后没有日志，返回提示信息
    if not filtered_logs:
        return "暂无对话记录。"

    # 处理日志内容，提取最新的用户和助手消息
    simplified_logs = []

    # 使用集合来跟踪已经处理过的消息，避免重复
    processed_messages = set()

    def process_message(role, content):
        # 创建一个唯一标识符来跟踪消息
        msg_id = f"{role}:{content}"
        if msg_id in processed_messages:
            return None

        processed_messages.add(msg_id)
        content = content.replace("\\n", "\n")
        lines = [line.strip() for line in content.split("\n")]
        content = "\n".join(lines)

        role_emoji = "🙋" if role.lower() == "user" else "🤖"
        return f"""### {role_emoji} {role.title()} Agent

{content}"""

    for log in filtered_logs:
        formatted_messages = []
        # 尝试提取消息数组
        messages_match = re.search(
            r"Model (.*?), index (\d+), processed these messages: (\[.*\])", log
        )

        if messages_match:
            try:
                messages = json.loads(messages_match.group(3))
                for msg in messages:
                    if msg.get("role") in ["user", "assistant"]:
                        formatted_msg = process_message(
                            msg.get("role"), msg.get("content", "")
                        )
                        if formatted_msg:
                            formatted_messages.append(formatted_msg)
            except json.JSONDecodeError:
                pass

        # 如果JSON解析失败或没有找到消息数组，尝试直接提取对话内容
        if not formatted_messages:
            user_pattern = re.compile(r"\{'role': 'user', 'content': '(.*?)'\}")
            assistant_pattern = re.compile(
                r"\{'role': 'assistant', 'content': '(.*?)'\}"
            )

            for content in user_pattern.findall(log):
                formatted_msg = process_message("user", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

            for content in assistant_pattern.findall(log):
                formatted_msg = process_message("assistant", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

        if formatted_messages:
            simplified_logs.append("\n\n".join(formatted_messages))

    # 格式化日志输出，确保每个对话记录之间有适当的分隔
    formatted_logs = []
    for i, log in enumerate(simplified_logs):
        # 移除开头和结尾的多余空白字符
        log = log.strip()

        formatted_logs.append(log)

        # 确保每个对话记录以换行符结束
        if not log.endswith("\n"):
            formatted_logs.append("\n")

    return "\n".join(formatted_logs)

# Dictionary containing module descriptions
MODULE_DESCRIPTIONS = {
    "run": "默认模式：使用OpenAI模型的默认的智能体协作模式，适合大多数任务。",
    "run_mini": "使用使用OpenAI模型最小化配置处理任务",
    "run_gemini": "使用 Gemini模型处理任务",
    "run_claude": "使用 Claude模型处理任务",
    "run_deepseek_zh": "使用eepseek模型处理中文任务",
    "run_openai_compatible_model": "使用openai兼容模型处理任务",
    "run_ollama": "使用本地ollama模型处理任务",
    "run_qwen_mini_zh": "使用qwen模型最小化配置处理任务",
    "run_qwen_zh": "使用qwen模型处理任务",
    "run_azure_openai": "使用azure openai模型处理任务",
    "run_groq": "使用groq模型处理任务",
    "run_ppio": "使用ppio模型处理任务",
    "run_together_ai": "使用together ai模型处理任务",
    "run_novita_ai": "使用novita ai模型处理任务",
}

# 默认环境变量模板
DEFAULT_ENV_TEMPLATE = """#===========================================
# MODEL & API
# (See https://docs.camel-ai.org/key_modules/models.html#)
#===========================================

# OPENAI API (https://platform.openai.com/api-keys)
OPENAI_API_KEY='Your_Key'
# OPENAI_API_BASE_URL=""

# Azure OpenAI API
# AZURE_OPENAI_BASE_URL=""
# AZURE_API_VERSION=""
# AZURE_OPENAI_API_KEY=""
# AZURE_DEPLOYMENT_NAME=""

# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
QWEN_API_KEY='Your_Key'

# DeepSeek API (https://platform.deepseek.com/api_keys)
DEEPSEEK_API_KEY='Your_Key'

#===========================================
# Tools & Services API
#===========================================

# Google Search API (https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3)
GOOGLE_API_KEY='Your_Key'
SEARCH_ENGINE_ID='Your_ID'

# Chunkr API (https://chunkr.ai/)
CHUNKR_API_KEY='Your_Key'

# Firecrawl API (https://www.firecrawl.dev/)
FIRECRAWL_API_KEY='Your_Key'
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
"""

def validate_input(question: str) -> bool:
    """验证用户输入是否有效

    Args:
        question: 用户问题

    Returns:
        bool: 输入是否有效
    """
    # 检查输入是否为空或只包含空格
    if not question or question.strip() == "":
        return False
    return True

def run_owl(question: str, example_module: str) -> Tuple[str, str, str]:
    """运行OWL系统并返回结果

    Args:
        question: 用户问题
        example_module: 要导入的示例模块名（如 "run_terminal_zh" 或 "run_deep"）

    Returns:
        Tuple[...]: 回答、令牌计数、状态
    """
    global CURRENT_PROCESS

    # 验证输入
    if not validate_input(question):
        logging.warning("用户提交了无效的输入")
        return ("请输入有效的问题", "0", "❌ 错误: 输入问题无效")

    try:
        # 确保环境变量已加载
        load_dotenv(find_dotenv(), override=True)
        logging.info(f"处理问题: '{question}', 使用模块: {example_module}")

        # 检查模块是否在MODULE_DESCRIPTIONS中
        if example_module not in MODULE_DESCRIPTIONS:
            logging.error(f"用户选择了不支持的模块: {example_module}")
            return (
                f"所选模块 '{example_module}' 不受支持",
                "0",
                "❌ 错误: 不支持的模块",
            )

        # 动态导入目标模块
        module_path = f"examples.{example_module}"
        try:
            logging.info(f"正在导入模块: {module_path}")
            module = importlib.import_module(module_path)
        except ImportError as ie:
            logging.error(f"无法导入模块 {module_path}: {str(ie)}")
            return (
                f"无法导入模块: {module_path}",
                "0",
                f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}",
            )
        except Exception as e:
            logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
            return (f"导入模块时发生错误: {module_path}", "0", f"❌ 错误: {str(e)}")

        # 检查是否包含construct_society函数
        if not hasattr(module, "construct_society"):
            logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
            return (
                f"模块 {module_path} 中未找到 construct_society 函数",
                "0",
                "❌ 错误: 模块接口不兼容",
            )

        # 构建社会模拟
        try:
            logging.info("正在构建社会模拟...")
            society = module.construct_society(question)

        except Exception as e:
            logging.error(f"构建社会模拟时发生错误: {str(e)}")
            return (
                f"构建社会模拟时发生错误: {str(e)}",
                "0",
                f"❌ 错误: 构建失败 - {str(e)}",
            )

        # 运行社会模拟
        try:
            logging.info("正在运行社会模拟...")
            answer, chat_history, token_info = run_society(society)
            logging.info("社会模拟运行完成")
        except Exception as e:
            logging.error(f"运行社会模拟时发生错误: {str(e)}")
            return (
                f"运行社会模拟时发生错误: {str(e)}",
                "0",
                f"❌ 错误: 运行失败 - {str(e)}",
            )

        # 安全地获取令牌计数
        if not isinstance(token_info, dict):
            token_info = {}

        completion_tokens = token_info.get("completion_token_count", 0)
        prompt_tokens = token_info.get("prompt_token_count", 0)
        total_tokens = completion_tokens + prompt_tokens

        logging.info(
            f"处理完成，令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}"
        )

        return (
            answer,
            f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
            "✅ 成功完成",
        )

    except Exception as e:
        logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
        return (f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}")

def update_module_description(module_name: str) -> str:
    """返回所选模块的描述"""
    return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")

# 存储前端配置的环境变量
WEB_FRONTEND_ENV_VARS: dict[str, str] = {}

def init_env_file():
    """初始化.env文件如果不存在"""
    dotenv_path = find_dotenv()
    if not dotenv_path:
        with open(".env", "w") as f:
            f.write(DEFAULT_ENV_TEMPLATE)
        dotenv_path = find_dotenv()
    return dotenv_path

def load_env_vars():
    """加载环境变量并返回字典格式

    Returns:
        dict: 环境变量字典，每个值为一个包含值和来源的元组 (value, source)
    """
    dotenv_path = init_env_file()
    load_dotenv(dotenv_path, override=True)

    # 从.env文件读取环境变量
    env_file_vars = {}
    with open(dotenv_path, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                if "=" in line:
                    key, value = line.split("=", 1)
                    env_file_vars[key.strip()] = value.strip().strip("\"'")

    # 从系统环境变量中获取
    system_env_vars = {
        k: v
        for k, v in os.environ.items()
        if k not in env_file_vars and k not in WEB_FRONTEND_ENV_VARS
    }

    # 合并环境变量，并标记来源
    env_vars = {}

    # 添加系统环境变量（最低优先级）
    for key, value in system_env_vars.items():
        env_vars[key] = (value, "系统")

    # 添加.env文件环境变量（中等优先级）
    for key, value in env_file_vars.items():
        env_vars[key] = (value, ".env文件")

    # 添加前端配置的环境变量（最高优先级）
    for key, value in WEB_FRONTEND_ENV_VARS.items():
        env_vars[key] = (value, "前端配置")
        # 确保操作系统环境变量也被更新
        os.environ[key] = value

    return env_vars

def save_env_vars(env_vars):
    """保存环境变量到.env文件

    Args:
        env_vars: 字典，键为环境变量名，值可以是字符串或(值,来源)元组
    """
    try:
        dotenv_path = init_env_file()

        # 保存每个环境变量
        for key, value_data in env_vars.items():
            if key and key.strip():  # 确保键不为空
                # 处理值可能是元组的情况
                if isinstance(value_data, tuple):
                    value = value_data[0]
                else:
                    value = value_data

                set_key(dotenv_path, key.strip(), value.strip())

        # 重新加载环境变量以确保生效
        load_dotenv(dotenv_path, override=True)

        return True, "环境变量已成功保存！"
    except Exception as e:
        return False, f"保存环境变量时出错: {str(e)}"

def add_env_var(key, value, from_frontend=True):
    """添加或更新单个环境变量

    Args:
        key: 环境变量名
        value: 环境变量值
        from_frontend: 是否来自前端配置，默认为True
    """
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        key = key.strip()
        value = value.strip()

        # 如果来自前端，则添加到前端环境变量字典
        if from_frontend:
            WEB_FRONTEND_ENV_VARS[key] = value
            # 直接更新系统环境变量
            os.environ[key] = value

        # 同时更新.env文件
        dotenv_path = init_env_file()
        set_key(dotenv_path, key, value)
        load_dotenv(dotenv_path, override=True)

        return True, f"环境变量 {key} 已成功添加/更新！"
    except Exception as e:
        return False, f"添加环境变量时出错: {str(e)}"

def delete_env_var(key):
    """删除环境变量"""
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        key = key.strip()

        # 从.env文件中删除
        dotenv_path = init_env_file()
        unset_key(dotenv_path, key)

        # 从前端环境变量字典中删除
        if key in WEB_FRONTEND_ENV_VARS:
            del WEB_FRONTEND_ENV_VARS[key]

        # 从当前进程环境中也删除
        if key in os.environ:
            del os.environ[key]

        return True, f"环境变量 {key} 已成功删除！"
    except Exception as e:
        return False, f"删除环境变量时出错: {str(e)}"

def is_api_related(key: str) -> bool:
    """判断环境变量是否与API相关

    Args:
        key: 环境变量名

    Returns:
        bool: 是否与API相关
    """
    # API相关的关键词
    api_keywords = [
        "api",
        "key",
        "token",
        "secret",
        "password",
        "openai",
        "qwen",
        "deepseek",
        "google",
        "search",
        "hf",
        "hugging",
        "chunkr",
        "firecrawl",
    ]

    # 检查是否包含API相关关键词（不区分大小写）
    return any(keyword in key.lower() for keyword in api_keywords)

def get_api_guide(key: str) -> str:
    """根据环境变量名返回对应的API获取指南

    Args:
        key: 环境变量名

    Returns:
        str: API获取指南链接或说明
    """
    key_lower = key.lower()
    if "openai" in key_lower:
        return "https://platform.openai.com/api-keys"
    elif "qwen" in key_lower or "dashscope" in key_lower:
        return "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
    elif "deepseek" in key_lower:
        return "https://platform.deepseek.com/api_keys"
    elif "ppio" in key_lower:
        return "https://ppinfra.com/settings/key-management?utm_source=github_owl"
    elif "google" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "search_engine_id" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "chunkr" in key_lower:
        return "https://chunkr.ai/"
    elif "firecrawl" in key_lower:
        return "https://www.firecrawl.dev/"
    elif "novita" in key_lower:
        return "https://novita.ai/settings/key-management?utm_source=github_owl&utm_medium=github_readme&utm_campaign=github_link"
    else:
        return ""

def update_env_table():
    """更新环境变量表格显示，只显示API相关的环境变量"""
    env_vars = load_env_vars()
    # 过滤出API相关的环境变量
    api_env_vars = {k: v for k, v in env_vars.items() if is_api_related(k)}
    # 转换为列表格式，以符合Gradio Dataframe的要求
    # 格式: [变量名, 变量值, 获取指南链接]
    result = []
    for k, v in api_env_vars.items():
        guide = get_api_guide(k)
        # 如果有指南链接，创建一个可点击的链接
        guide_link = (
            f"🔗 获取"
            if guide
            else ""
        )
        result.append([k, v[0], guide_link])
    return result

def save_env_table_changes(data):
    """保存环境变量表格的更改

    Args:
        data: Dataframe数据，可能是pandas DataFrame对象

    Returns:
        str: 操作状态信息，包含HTML格式的状态消息
    """
    try:
        logging.info(f"开始处理环境变量表格数据，类型: {type(data)}")

        # 获取当前所有环境变量
        current_env_vars = load_env_vars()
        processed_keys = set()  # 记录已处理的键，用于检测删除的变量

        # 处理pandas DataFrame对象
        import pandas as pd

        if isinstance(data, pd.DataFrame):
            # 获取列名信息
            columns = data.columns.tolist()
            logging.info(f"DataFrame列名: {columns}")

            # 遍历DataFrame的每一行
            for index, row in data.iterrows():
                # 使用列名访问数据
                if len(columns) >= 3:
                    # 获取变量名和值 (第0列是变量名，第1列是值)
                    key = row[0] if isinstance(row, pd.Series) else row.iloc[0]
                    value = row[1] if isinstance(row, pd.Series) else row.iloc[1]

                    # 检查是否为空行或已删除的变量
                    if key and str(key).strip():  # 如果键名不为空，则添加或更新
                        logging.info(f"处理环境变量: {key} = {value}")
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        # 处理其他格式
        elif isinstance(data, dict):
            logging.info(f"字典格式数据的键: {list(data.keys())}")
            # 如果是字典格式，尝试不同的键
            if "data" in data:
                rows = data["data"]
            elif "values" in data:
                rows = data["values"]
            elif "value" in data:
                rows = data["value"]
            else:
                # 尝试直接使用字典作为行数据
                rows = []
                for key, value in data.items():
                    if key not in ["headers", "types", "columns"]:
                        rows.append([key, value])

            if isinstance(rows, list):
                for row in rows:
                    if isinstance(row, list) and len(row) >= 2:
                        key, value = row[0], row[1]
                        if key and str(key).strip():
                            add_env_var(key, str(value))
                            processed_keys.add(key)
        elif isinstance(data, list):
            # 列表格式
            for row in data:
                if isinstance(row, list) and len(row) >= 2:
                    key, value = row[0], row[1]
                    if key and str(key).strip():
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        else:
            logging.error(f"未知的数据格式: {type(data)}")
            return f"❌ 保存失败: 未知的数据格式 {type(data)}"

        # 处理删除的变量 - 检查当前环境变量中是否有未在表格中出现的变量
        api_related_keys = {k for k in current_env_vars.keys() if is_api_related(k)}
        keys_to_delete = api_related_keys - processed_keys

        # 删除不再表格中的变量
        for key in keys_to_delete:
            logging.info(f"删除环境变量: {key}")
            delete_env_var(key)

        return "✅ 环境变量已成功保存"
    except Exception as e:
        import traceback

        error_details = traceback.format_exc()
        logging.error(f"保存环境变量时出错: {str(e)}\n{error_details}")
        return f"❌ 保存失败: {str(e)}"

def get_env_var_value(key):
    """获取环境变量的实际值

    优先级：前端配置 > .env文件 > 系统环境变量
    """
    # 检查前端配置的环境变量
    if key in WEB_FRONTEND_ENV_VARS:
        return WEB_FRONTEND_ENV_VARS[key]

    # 检查系统环境变量（包括从.env加载的）
    return os.environ.get(key, "")

def create_ui():
    """创建增强版Gradio界面"""

    def clear_log_file():
        """清空日志文件内容"""
        try:
            if LOG_FILE and os.path.exists(LOG_FILE):
                # 清空日志文件内容而不是删除文件
                open(LOG_FILE, "w").close()
                logging.info("日志文件已清空")
                # 清空日志队列
                while not LOG_QUEUE.empty():
                    try:
                        LOG_QUEUE.get_nowait()
                    except queue.Empty:
                        break
                return ""
            else:
                return ""
        except Exception as e:
            logging.error(f"清空日志文件时出错: {str(e)}")
            return ""

    # 创建一个实时日志更新函数
    def process_with_live_logs(question, module_name):
        """处理问题并实时更新日志"""
        global CURRENT_PROCESS

        # 清空日志文件
        clear_log_file()

        # 创建一个后台线程来处理问题
        result_queue = queue.Queue()

        def process_in_background():
            try:
                result = run_owl(question, module_name)
                result_queue.put(result)
            except Exception as e:
                result_queue.put((f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}"))

        # 启动后台处理线程
        bg_thread = threading.Thread(target=process_in_background)
        CURRENT_PROCESS = bg_thread  # 记录当前进程
        bg_thread.start()

        # 在等待处理完成的同时，每秒更新一次日志
        while bg_thread.is_alive():
            # 更新对话记录显示
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # 始终更新状态
            yield (
                "0",
                " 处理中...",
                logs2,
            )

            time.sleep(1)

        # 处理完成，获取结果
        if not result_queue.empty():
            result = result_queue.get()
            answer, token_count, status = result

            # 最后一次更新对话记录
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # 根据状态设置不同的指示器
            if "错误" in status:
                status_with_indicator = (
                    f" {status}"
                )
            else:
                status_with_indicator = (
                    f" {status}"
                )

            yield token_count, status_with_indicator, logs2
        else:
            logs2 = get_latest_logs(100, LOG_QUEUE)
            yield (
                "0",
                " 已终止",
                logs2,
            )

    with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
        gr.Markdown(
            """
                # 🦉 OWL 多智能体协作系统

                基于CAMEL框架开发的先进多智能体协作系统，旨在通过智能体协作解决复杂问题。

                可以通过修改本地脚本自定义模型和工具。

                本网页应用目前处于测试阶段，仅供演示和测试使用，尚未推荐用于生产环境。
                """
        )

        # 添加自定义CSS
        gr.HTML("""

            """)

        with gr.Row():
            with gr.Column(scale=0.5):
                question_input = gr.Textbox(
                    lines=5,
                    placeholder="请输入您的问题...",
                    label="问题",
                    elem_id="question_input",
                    show_copy_button=True,
                    value="打开百度搜索，总结一下camel-ai的camel框架的github star、fork数目等，并把数字用plot包写成python文件保存到本地，并运行生成的python文件。",
                )

                # 增强版模块选择下拉菜单
                # 只包含MODULE_DESCRIPTIONS中定义的模块
                module_dropdown = gr.Dropdown(
                    choices=list(MODULE_DESCRIPTIONS.keys()),
                    value="run_qwen_zh",
                    label="选择功能模块",
                    interactive=True,
                )

                # 模块描述文本框
                module_description = gr.Textbox(
                    value=MODULE_DESCRIPTIONS["run_qwen_zh"],
                    label="模块描述",
                    interactive=False,
                    elem_classes="module-info",
                )

                with gr.Row():
                    run_button = gr.Button(
                        "运行", variant="primary", elem_classes="primary"
                    )

                status_output = gr.HTML(
                    value=" 已就绪",
                    label="状态",
                )
                token_count_output = gr.Textbox(
                    label="令牌计数", interactive=False, elem_classes="token-count"
                )

                # 示例问题
                examples = [
                    "打开百度搜索，总结一下camel-ai的camel框架的github star、fork数目等，并把数字用plot包写成python文件保存到本地，并运行生成的python文件。",
                    "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
                    "写一个hello world的python文件，保存到本地",
                ]

                gr.Examples(examples=examples, inputs=question_input)

                gr.HTML("""

关于 OWL 多智能体协作系统

OWL 是一个基于CAMEL框架开发的先进多智能体协作系统，旨在通过智能体协作解决复杂问题。

© 2025 CAMEL-AI.org. 基于Apache License 2.0开源协议

GitHub

                    """)

            with gr.Tabs():  # 设置对话记录为默认选中的标签页
                with gr.TabItem("对话记录"):
                    # 添加对话记录显示区域
                    with gr.Group():
                        log_display2 = gr.Markdown(
                            value="暂无对话记录。",
                            elem_classes="log-display",
                        )

                    with gr.Row():
                        refresh_logs_button2 = gr.Button("刷新记录")
                        auto_refresh_checkbox2 = gr.Checkbox(
                            label="自动刷新", value=True, interactive=True
                        )
                        clear_logs_button2 = gr.Button("清空记录", variant="secondary")

                with gr.TabItem("环境变量管理", id="env-settings"):
                    with gr.Group(elem_classes="env-manager-container"):
                        gr.Markdown("""
                            ## 环境变量管理

                            在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中，确保您的API密钥安全存储且不会上传到网络。正确设置API密钥对于OWL系统的功能至关重要, 可以按找工具需求灵活配置环境变量。
                            """)

                        # 主要内容分为两列布局
                        with gr.Row():
                            # 左侧列：环境变量管理控件
                            with gr.Column(scale=3):
                                with gr.Group(elem_classes="env-controls"):
                                    # 环境变量表格 - 设置为可交互以直接编辑
                                    gr.Markdown("""

                                      提示： 请确保运行cp .env_template .env创建本地.env文件，根据运行模块灵活配置所需环境变量

                                    """)

                                    # 增强版环境变量表格，支持添加和删除行
                                    env_table = gr.Dataframe(
                                        headers=["变量名", "值", "获取指南"],
                                        datatype=[
                                            "str",
                                            "str",
                                            "html",
                                        ],  # 将最后一列设置为html类型以支持链接
                                        row_count=10,  # 增加行数，以便添加新变量
                                        col_count=(3, "fixed"),
                                        value=update_env_table,
                                        label="API密钥和环境变量",
                                        interactive=True,  # 设置为可交互，允许直接编辑
                                        elem_classes="env-table",
                                    )

                                    # 操作说明
                                    gr.Markdown(
                                        """

                                    操作指南:

编辑变量: 直接点击表格中的"值"单元格进行编辑

添加变量: 在空白行中输入新的变量名和值

删除变量: 清空变量名即可删除该行

获取API密钥: 点击"获取指南"列中的链接获取相应API密钥

                                    """,
                                        elem_classes="env-instructions",
                                    )

                                    # 环境变量操作按钮
                                    with gr.Row(elem_classes="env-buttons"):
                                        save_env_button = gr.Button(
                                            "💾 保存更改",
                                            variant="primary",
                                            elem_classes="env-button",
                                        )
                                        refresh_button = gr.Button(
                                            "🔄 刷新列表", elem_classes="env-button"
                                        )

                                    # 状态显示
                                    env_status = gr.HTML(
                                        label="操作状态",
                                        value="",
                                        elem_classes="env-status",
                                    )

                    # 连接事件处理函数
                    save_env_button.click(
                        fn=save_env_table_changes,
                        inputs=[env_table],
                        outputs=[env_status],
                    ).then(fn=update_env_table, outputs=[env_table])

                    refresh_button.click(fn=update_env_table, outputs=[env_table])

        # 设置事件处理
        run_button.click(
            fn=process_with_live_logs,
            inputs=[question_input, module_dropdown],
            outputs=[token_count_output, status_output, log_display2],
        )

        # 模块选择更新描述
        module_dropdown.change(
            fn=update_module_description,
            inputs=module_dropdown,
            outputs=module_description,
        )

        # 对话记录相关事件处理
        refresh_logs_button2.click(
            fn=lambda: get_latest_logs(100, LOG_QUEUE), outputs=[log_display2]
        )

        clear_logs_button2.click(fn=clear_log_file, outputs=[log_display2])

        # 自动刷新控制
        def toggle_auto_refresh(enabled):
            if enabled:
                return gr.update(every=3)
            else:
                return gr.update(every=0)

        auto_refresh_checkbox2.change(
            fn=toggle_auto_refresh,
            inputs=[auto_refresh_checkbox2],
            outputs=[log_display2],
        )

        # 不再默认自动刷新日志

    return app

# 主函数
def main():
    try:
        # 初始化日志系统
        global LOG_FILE
        LOG_FILE = setup_logging()
        logging.info("OWL Web应用程序启动")

        # 启动日志读取线程
        log_thread = threading.Thread(
            target=log_reader_thread, args=(LOG_FILE,), daemon=True
        )
        log_thread.start()
        logging.info("日志读取线程已启动")

        # 初始化.env文件（如果不存在）
        init_env_file()
        app = create_ui()

        app.queue()
        app.launch(share=False)
    except Exception as e:
        logging.error(f"启动应用程序时发生错误: {str(e)}")
        print(f"启动应用程序时发生错误: {str(e)}")
        import traceback

        traceback.print_exc()

    finally:
        # 确保日志线程停止
        STOP_LOG_THREAD.set()
        STOP_REQUESTED.set()
        logging.info("应用程序关闭")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import logging
import json

from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.types import ModelPlatformType

from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
)
from camel.societies import RolePlaying
from camel.logger import set_log_level, get_logger

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")
logger = get_logger(__name__)
file_handler = logging.FileHandler("learning_journey.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

root_logger = logging.getLogger()
root_logger.addHandler(file_handler)

def construct_learning_society(task: str) -> RolePlaying:
    """Construct a society of agents for the learning journey companion.

    Args:
        task (str): The learning task description including what the user wants to learn and what they already know.

    Returns:
        RolePlaying: A configured society of agents for the learning companion.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "content_researcher": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.2},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.3},
        ),
    }

    browser_toolkit = BrowserToolkit(
        headless=False,
        web_agent_model=models["content_researcher"],
        planning_agent_model=models["planning"],
    )

    tools = [
        *browser_toolkit.get_tools(),
        SearchToolkit().search_duckduckgo,
    ]

    user_agent_kwargs = {
        "model": models["user"],
    }

    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": task,
        "with_task_specify": False,
    }

    society = RolePlaying(
        **task_kwargs,
        user_role_name="learner",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="learning_companion",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def analyze_chat_history(chat_history):
    """Analyze chat history and extract tool call information."""
    print("\n============ Tool Call Analysis ============")
    logger.info("========== Starting tool call analysis ==========")

    tool_calls = []
    for i, message in enumerate(chat_history):
        if message.get("role") == "assistant" and "tool_calls" in message:
            for tool_call in message.get("tool_calls", []):
                if tool_call.get("type") == "function":
                    function = tool_call.get("function", {})
                    tool_info = {
                        "call_id": tool_call.get("id"),
                        "name": function.get("name"),
                        "arguments": function.get("arguments"),
                        "message_index": i,
                    }
                    tool_calls.append(tool_info)
                    print(
                        f"Tool Call: {function.get('name')} Args: {function.get('arguments')}"
                    )
                    logger.info(
                        f"Tool Call: {function.get('name')} Args: {function.get('arguments')}"
                    )

        elif message.get("role") == "tool" and "tool_call_id" in message:
            for tool_call in tool_calls:
                if tool_call.get("call_id") == message.get("tool_call_id"):
                    result = message.get("content", "")
                    result_summary = (
                        result[:100] + "..." if len(result) > 100 else result
                    )
                    print(
                        f"Tool Result: {tool_call.get('name')} Return: {result_summary}"
                    )
                    logger.info(
                        f"Tool Result: {tool_call.get('name')} Return: {result_summary}"
                    )

    print(f"Total tool calls found: {len(tool_calls)}")
    logger.info(f"Total tool calls found: {len(tool_calls)}")
    logger.info("========== Finished tool call analysis ==========")

    with open("learning_journey_history.json", "w", encoding="utf-8") as f:
        json.dump(chat_history, f, ensure_ascii=False, indent=2)

    print("Records saved to learning_journey_history.json")
    print("============ Analysis Complete ============\n")

def run_learning_companion(task: str = None):
    """Run the learning companion with the given task.

    Args:
        task (str, optional): The learning task description. Defaults to an example task.
    """
    task = """
    I want to learn about the transformers architecture in an llm.
    I've also taken a basic statistics course.
    I have about 10 hours per week to dedicate to learning. Devise a roadmap for me .
    """

    society = construct_learning_society(task)

    from owl.utils import run_society

    answer, chat_history, token_count = run_society(society, round_limit=5)

    # Record tool usage history
    analyze_chat_history(chat_history)
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    run_learning_companion()

/owl/community_usecase/OWL Interview Preparation Assistant/main.py

#main.py
import os
import logging
import time
from typing import Dict, Any, Callable, Optional
from pathlib import Path
import sys

# Add parent directory to path for OWL imports
sys.path.append('../')
from dotenv import load_dotenv
import numpy as np  # Explicitly import numpy to avoid 'numpy' errors
from camel.models import ModelFactory
from camel.types import ModelPlatformType, ModelType
from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
    CodeExecutionToolkit
)
from camel.societies import RolePlaying
from camel.configs import ChatGPTConfig
from owl.utils import run_society  # Official run_society with round_limit support

# Import prompt templates
from config.prompts import (
    get_system_prompt,
    get_company_research_prompt,
    get_question_generator_prompt,
    get_preparation_plan_prompt
)

# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()

# Create the output directory for interview preparation materials
INTERVIEW_PREP_DIR = "./interview_prep"
os.makedirs(INTERVIEW_PREP_DIR, exist_ok=True)

def run_society_with_strict_limit(society, round_limit=5, progress_callback=None):
    """Wrapper around run_society to ensure round limit is strictly enforced

    This implementation hijacks the step method to force termination after a specific number of rounds.
    """
    # Track rounds manually
    round_count = 0

    # Save original step function
    original_step = society.step

    # Override the step method
    def limited_step(*args, **kwargs):
        nonlocal round_count
        round_count += 1

        # Report progress if callback is provided
        if progress_callback and callable(progress_callback):
            progress_callback(round_count, round_limit)

        # Force termination after reaching the round limit
        if round_count >= round_limit:
            logger.info(f"Reached round limit of {round_limit}, forcibly terminating.")
            # Force a TASK_DONE in the user response to trigger termination
            result = original_step(*args, **kwargs)
            if len(result) >= 2 and hasattr(result[1], 'msgs') and result[1].msgs and len(result[1].msgs) > 0:
                result[1].msgs[0].content += "\n\nTASK_DONE"
                result[1].terminated = True
            return result

        return original_step(*args, **kwargs)

    # Replace the step method
    society.step = limited_step

    try:
        # Run the conversation with the standard run_society function
        answer, chat_history, token_count = run_society(society, round_limit=round_limit)

        # Add a note about the conversation being truncated
        if len(chat_history) > 0 and "truncated_note" not in chat_history[-1]:
            chat_history[-1]["truncated_note"] = True
            if "assistant" in chat_history[-1]:
                chat_history[-1]["assistant"] += "\n\n[Note: This conversation was limited to maintain response quality.]"

        return answer, chat_history, token_count

    finally:
        # Restore the original step method
        society.step = original_step

def construct_interview_assistant(
    job_description: str,
    company_name: str,
    detailed: bool = True,
    limited_searches: bool = True
) -> RolePlaying:
    """
    Construct a specialized interview preparation assistant using OWL.
    """
    # Select model based on environment variables
    if os.environ.get("OPENROUTER_API_KEY"):
        logger.info("Using OpenRouter with Gemini model")
        model = ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            api_key=os.environ.get("OPENROUTER_API_KEY"),
            model_type="google/gemini-2.0-flash-001",
            url="https://openrouter.ai/api/v1",
            model_config_dict={
                "temperature": 0.6,
                "max_tokens": 4000,  # Reduced from 10000 to avoid exceeding limits
                # Do NOT use context_length - it's not a valid API parameter
            }
        )
    elif os.environ.get("OPENAI_API_KEY"):
        logger.info("Using OpenAI model (GPT-4)")
        config = ChatGPTConfig(
            temperature=0.3,
            max_tokens=4000
        )
        model = ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=config.as_dict()
        )
    else:
        raise ValueError("Either OPENAI_API_KEY or OPENROUTER_API_KEY must be set")

    # Configure toolkits - Remove FileWriteToolkit as requested
    essential_tools = [
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        # Removed the FileWriteToolkit as requested
    ]

    if os.environ.get("GOOGLE_API_KEY") and os.environ.get("SEARCH_ENGINE_ID"):
        essential_tools.append(SearchToolkit().search_google)

    if detailed:
        tools = [
            *essential_tools,
            *BrowserToolkit(
                headless=True,
                web_agent_model=model,
                planning_agent_model=model,
            ).get_tools(),
            *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        ]
        logger.info("Using full toolset for comprehensive results (detailed=True)")
    else:
        tools = essential_tools
        logger.info("Using essential toolset for faster results (detailed=False)")

    user_agent_kwargs = {"model": model}
    assistant_agent_kwargs = {"model": model, "tools": tools}

    # Build enhanced prompt asking for full, detailed output
    base_prompt = get_system_prompt()
    enhanced_prompt = f"""{base_prompt}
Task: Help me prepare for an interview at {company_name} for the position of {job_description}.
Requirements:
1. Provide a highly detailed, extremely comprehensive response (aim for at least 2000+ words).
2. Structure the output with clear sections, actionable insights, examples, and code where relevant.
3. Tailor the content specifically to {company_name} and the {job_description} role.
4. Do NOT truncate or summarize—provide the full explanation directly.
"""

    task_kwargs = {
        "task_prompt": enhanced_prompt,
        "with_task_specify": False,
    }

    society = RolePlaying(
        **task_kwargs,
        user_role_name="job_seeker",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="interview_coach",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    # Try to set memory parameters to reduce context size
    try:
        # Try to access the context creator if it exists
        if hasattr(society, '_context_creator') and hasattr(society._context_creator, 'max_tokens'):
            society._context_creator.max_tokens = 4000
        # Alternative approach through kwargs if available
        elif hasattr(society, '_context_creator_kwargs'):
            society._context_creator_kwargs = {"max_tokens": 4000}
    except AttributeError:
        logger.warning("Could not directly set memory parameters. Using default values.")

    return society

def research_company(
    company_name: str,
    detailed: bool = True,
    limited_searches: bool = True,
    progress_callback: Optional[Callable] = None
) -> Dict[str, Any]:
    start_time = time.time()
    logging.info(f"Beginning company research for {company_name}")
    base_prompt = get_company_research_prompt(company_name)
    enhanced_prompt = f"""{base_prompt}

Please provide the most detailed, in-depth report possible, with no summarization or truncation.
Your response must include extensive coverage, code samples (if relevant), and be at least 2000 words long.
"""
    society = construct_interview_assistant("", company_name, detailed=detailed, limited_searches=limited_searches)
    society.task_prompt = enhanced_prompt

    # Use our strict wrapper function to enforce limit at exactly 5 rounds
    answer, chat_history, token_count = run_society_with_strict_limit(
        society,
        round_limit=5,
        progress_callback=progress_callback
    )

    duration = time.time() - start_time
    logging.info(f"Completed company research for {company_name} in {duration:.2f} seconds")

    # Find any files that may have been generated
    generated_files = [str(file) for file in Path(INTERVIEW_PREP_DIR).glob("*") if file.is_file()]

    return {
        "answer": answer,
        "chat_history": chat_history,
        "token_count": token_count,
        "generated_files": generated_files,
        "duration_seconds": duration
    }

def generate_interview_questions(
    job_role: str,
    company_name: str,
    detailed: bool = True,
    limited_searches: bool = True,
    progress_callback: Optional[Callable] = None
) -> Dict[str, Any]:
    start_time = time.time()
    logging.info(f"Starting question generation for {job_role} at {company_name} (detailed={detailed})")

    try:
        # Ensure numpy is available to prevent 'numpy' errors
        import numpy as np

        base_prompt = get_question_generator_prompt(job_role, company_name)
        enhanced_prompt = f"""{base_prompt}

Please provide at least 50 highly specific questions with code examples, multiple solution approaches,
and extremely thorough explanations. Aim for 3000+ words, with no truncation or summarization.
"""
        society = construct_interview_assistant(job_role, company_name, detailed=detailed, limited_searches=limited_searches)
        society.task_prompt = enhanced_prompt

        # Use our wrapper function to strictly enforce a limit of 5 rounds
        answer, chat_history, token_count = run_society_with_strict_limit(
            society,
            round_limit=5,
            progress_callback=progress_callback
        )

        duration = time.time() - start_time
        logging.info(f"Completed question generation for {job_role} at {company_name} in {duration:.2f} seconds")

        # Find any files that were generated
        generated_files = [str(file) for file in Path(INTERVIEW_PREP_DIR).glob("*") if file.is_file()]

        return {
            "answer": answer,
            "chat_history": chat_history,
            "token_count": token_count,
            "generated_files": generated_files,
            "duration_seconds": duration
        }

    except Exception as e:
        logging.error(f"Error in question generation: {str(e)}", exc_info=True)
        raise

def create_interview_prep_plan(
    job_role: str,
    company_name: str,
    detailed: bool = True,
    limited_searches: bool = True,
    progress_callback: Optional[Callable] = None
) -> Dict[str, Any]:
    start_time = time.time()
    logging.info(f"Starting preparation plan creation for {job_role} at {company_name} (detailed={detailed})")

    try:
        base_prompt = get_preparation_plan_prompt(job_role, company_name)
        enhanced_prompt = f"""{base_prompt}

Please provide a highly thorough, step-by-step preparation plan with multiple days of tasks,
detailed technical reviews, code examples where applicable, and at least 2000 words total.
No truncation or summaries—include the full content.
"""
        society = construct_interview_assistant(job_role, company_name, detailed=detailed, limited_searches=limited_searches)
        society.task_prompt = enhanced_prompt

        # Use our wrapper function with strict limit of 5 rounds
        answer, chat_history, token_count = run_society_with_strict_limit(
            society,
            round_limit=5,
            progress_callback=progress_callback
        )

        duration = time.time() - start_time
        logging.info(f"Completed preparation plan creation in {duration:.2f} seconds")

        # Find any files that were generated
        generated_files = [str(file) for file in Path(INTERVIEW_PREP_DIR).glob("*") if file.is_file()]

        return {
            "answer": answer,
            "chat_history": chat_history,
            "token_count": token_count,
            "generated_files": generated_files,
            "duration_seconds": duration
        }

    except Exception as e:
        logging.error(f"Error in preparation plan creation: {str(e)}", exc_info=True)
        raise

if __name__ == "__main__":
    job_role = "Machine Learning Engineer"
    company_name = "Google"
    result = create_interview_prep_plan(job_role, company_name, detailed=True)
    print(f"Answer: {result['answer']}")
    print(f"Generated files: {result['generated_files']}")
    print(f"Execution time: {result['duration_seconds']:.2f} seconds")
    print(f"Conversation rounds: {len(result['chat_history'])}")

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import logging
import json

from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.types import ModelPlatformType

from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
)
from camel.societies import RolePlaying
from camel.logger import set_log_level, get_logger

from owl.utils import run_society
import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")
logger = get_logger(__name__)
file_handler = logging.FileHandler("cooking_companion.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

root_logger = logging.getLogger()
root_logger.addHandler(file_handler)

def construct_cooking_society(task: str) -> RolePlaying:
    """Construct a society of agents for the cooking companion.

    Args:
        task (str): The cooking-related task to be addressed.

    Returns:
        RolePlaying: A configured society of agents for the cooking companion.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "recipe_analyst": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.2},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.3},
        ),
    }

    browser_toolkit = BrowserToolkit(
        headless=False,
        web_agent_model=models["recipe_analyst"],
        planning_agent_model=models["planning"],
    )

    tools = [
        *browser_toolkit.get_tools(),
        SearchToolkit().search_duckduckgo,
    ]

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    task_kwargs = {
        "task_prompt": task,
        "with_task_specify": False,
    }

    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="cooking_assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def analyze_chat_history(chat_history):
    """Analyze chat history and extract tool call information."""
    print("\n============ Tool Call Analysis ============")
    logger.info("========== Starting tool call analysis ==========")

    tool_calls = []
    for i, message in enumerate(chat_history):
        if message.get("role") == "assistant" and "tool_calls" in message:
            for tool_call in message.get("tool_calls", []):
                if tool_call.get("type") == "function":
                    function = tool_call.get("function", {})
                    tool_info = {
                        "call_id": tool_call.get("id"),
                        "name": function.get("name"),
                        "arguments": function.get("arguments"),
                        "message_index": i,
                    }
                    tool_calls.append(tool_info)
                    print(
                        f"Tool Call: {function.get('name')} Args: {function.get('arguments')}"
                    )
                    logger.info(
                        f"Tool Call: {function.get('name')} Args: {function.get('arguments')}"
                    )

        elif message.get("role") == "tool" and "tool_call_id" in message:
            for tool_call in tool_calls:
                if tool_call.get("call_id") == message.get("tool_call_id"):
                    result = message.get("content", "")
                    result_summary = (
                        result[:100] + "..." if len(result) > 100 else result
                    )
                    print(
                        f"Tool Result: {tool_call.get('name')} Return: {result_summary}"
                    )
                    logger.info(
                        f"Tool Result: {tool_call.get('name')} Return: {result_summary}"
                    )

    print(f"Total tool calls found: {len(tool_calls)}")
    logger.info(f"Total tool calls found: {len(tool_calls)}")
    logger.info("========== Finished tool call analysis ==========")

    with open("cooking_chat_history.json", "w", encoding="utf-8") as f:
        json.dump(chat_history, f, ensure_ascii=False, indent=2)

    print("Records saved to cooking_chat_history.json")
    print("============ Analysis Complete ============\n")

def run_cooking_companion():
    task = "I have chicken breast, broccoli, garlic, and pasta. I'm looking for a quick dinner recipe that's healthy. I'm also trying to reduce my sodium intake. Search the internet for a recipe, modify it for low sodium, and create a shopping list for any additional ingredients I need?"
    society = construct_cooking_society(task)
    answer, chat_history, token_count = run_society(society)

    # Record tool usage history
    analyze_chat_history(chat_history)
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    run_cooking_companion()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Import from the correct module path
from utils import run_society
import os
import gradio as gr
import time
import json
import logging
import datetime
from typing import Tuple
import importlib
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
import threading
import queue
import re

os.environ["PYTHONIOENCODING"] = "utf-8"

# Configure logging system
def setup_logging():
    """Configure logging system to output logs to file, memory queue, and console"""
    # Create logs directory (if it doesn't exist)
    logs_dir = os.path.join(os.path.dirname(__file__), "logs")
    os.makedirs(logs_dir, exist_ok=True)

    # Generate log filename (using current date)
    current_date = datetime.datetime.now().strftime("%Y-%m-%d")
    log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")

    # Configure root logger (captures all logs)
    root_logger = logging.getLogger()

    # Clear existing handlers to avoid duplicate logs
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)

    root_logger.setLevel(logging.INFO)

    # Create file handler
    file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
    file_handler.setLevel(logging.INFO)

    # Create console handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)

    # Create formatter
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    # Add handlers to root logger
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)

    logging.info("Logging system initialized, log file: %s", log_file)
    return log_file

# Global variables
LOG_FILE = None
LOG_QUEUE: queue.Queue = queue.Queue()  # Log queue
STOP_LOG_THREAD = threading.Event()
CURRENT_PROCESS = None  # Used to track the currently running process
STOP_REQUESTED = threading.Event()  # Used to mark if stop was requested

# Log reading and updating functions
def log_reader_thread(log_file):
    """Background thread that continuously reads the log file and adds new lines to the queue"""
    try:
        with open(log_file, "r", encoding="utf-8") as f:
            # Move to the end of file
            f.seek(0, 2)

            while not STOP_LOG_THREAD.is_set():
                line = f.readline()
                if line:
                    LOG_QUEUE.put(line)  # Add to conversation record queue
                else:
                    # No new lines, wait for a short time
                    time.sleep(0.1)
    except Exception as e:
        logging.error(f"Log reader thread error: {str(e)}")

def get_latest_logs(max_lines=100, queue_source=None):
    """Get the latest log lines from the queue, or read directly from the file if the queue is empty

    Args:
        max_lines: Maximum number of lines to return
        queue_source: Specify which queue to use, default is LOG_QUEUE

    Returns:
        str: Log content
    """
    logs = []
    log_queue = queue_source if queue_source else LOG_QUEUE

    # Create a temporary queue to store logs so we can process them without removing them from the original queue
    temp_queue = queue.Queue()
    temp_logs = []

    try:
        # Try to get all available log lines from the queue
        while not log_queue.empty() and len(temp_logs) < max_lines:
            log = log_queue.get_nowait()
            temp_logs.append(log)
            temp_queue.put(log)  # Put the log back into the temporary queue
    except queue.Empty:
        pass

    # Process conversation records
    logs = temp_logs

    # If there are no new logs or not enough logs, try to read the last few lines directly from the file
    if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "r", encoding="utf-8") as f:
                all_lines = f.readlines()
                # If there are already some logs in the queue, only read the remaining needed lines
                remaining_lines = max_lines - len(logs)
                file_logs = (
                    all_lines[-remaining_lines:]
                    if len(all_lines) > remaining_lines
                    else all_lines
                )

                # Add file logs before queue logs
                logs = file_logs + logs
        except Exception as e:
            error_msg = f"Error reading log file: {str(e)}"
            logging.error(error_msg)
            if not logs:  # Only add error message if there are no logs
                logs = [error_msg]

    # If there are still no logs, return a prompt message
    if not logs:
        return "Initialization in progress..."

    # Filter logs, only keep logs with 'camel.agents.chat_agent - INFO'
    filtered_logs = []
    for log in logs:
        if "camel.agents.chat_agent - INFO" in log:
            filtered_logs.append(log)

    # If there are no logs after filtering, return a prompt message
    if not filtered_logs:
        return "No conversation records yet."

    # Process log content, extract the latest user and assistant messages
    simplified_logs = []

    # Use a set to track messages that have already been processed, to avoid duplicates
    processed_messages = set()

    def process_message(role, content):
        # Create a unique identifier to track messages
        msg_id = f"{role}:{content}"
        if msg_id in processed_messages:
            return None

        processed_messages.add(msg_id)
        content = content.replace("\\n", "\n")
        lines = [line.strip() for line in content.split("\n")]
        content = "\n".join(lines)

        role_emoji = "🙋" if role.lower() == "user" else "🤖"
        return f"""### {role_emoji} {role.title()} Agent

{content}"""

    for log in filtered_logs:
        formatted_messages = []
        # Try to extract message array
        messages_match = re.search(
            r"Model (.*?), index (\d+), processed these messages: (\[.*\])", log
        )

        if messages_match:
            try:
                messages = json.loads(messages_match.group(3))
                for msg in messages:
                    if msg.get("role") in ["user", "assistant"]:
                        formatted_msg = process_message(
                            msg.get("role"), msg.get("content", "")
                        )
                        if formatted_msg:
                            formatted_messages.append(formatted_msg)
            except json.JSONDecodeError:
                pass

        # If JSON parsing fails or no message array is found, try to extract conversation content directly
        if not formatted_messages:
            user_pattern = re.compile(r"\{'role': 'user', 'content': '(.*?)'\}")
            assistant_pattern = re.compile(
                r"\{'role': 'assistant', 'content': '(.*?)'\}"
            )

            for content in user_pattern.findall(log):
                formatted_msg = process_message("user", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

            for content in assistant_pattern.findall(log):
                formatted_msg = process_message("assistant", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

        if formatted_messages:
            simplified_logs.append("\n\n".join(formatted_messages))

    # Format log output, ensure appropriate separation between each conversation record
    formatted_logs = []
    for i, log in enumerate(simplified_logs):
        # Remove excess whitespace characters from beginning and end
        log = log.strip()

        formatted_logs.append(log)

        # Ensure each conversation record ends with a newline
        if not log.endswith("\n"):
            formatted_logs.append("\n")

    return "\n".join(formatted_logs)

# Dictionary containing module descriptions
MODULE_DESCRIPTIONS = {
    "run": "Default mode: Using OpenAI model's default agent collaboration mode, suitable for most tasks.",
    "run_mini": "Using OpenAI model with minimal configuration to process tasks",
    "run_gemini": "Using Gemini model to process tasks",
    "run_claude": "Using Claude model to process tasks",
    "run_deepseek_zh": "Using deepseek model to process Chinese tasks",
    "run_mistral": "Using Mistral models to process tasks",
    "run_openai_compatible_model": "Using openai compatible model to process tasks",
    "run_ollama": "Using local ollama model to process tasks",
    "run_qwen_mini_zh": "Using qwen model with minimal configuration to process tasks",
    "run_qwen_zh": "Using qwen model to process tasks",
    "run_azure_openai": "Using azure openai model to process tasks",
    "run_groq": "Using groq model to process tasks",
    "run_ppio": "Using ppio model to process tasks",
    "run_together_ai": "Using together ai model to process tasks",
    "run_novita_ai": "Using novita ai model to process tasks",
}

# Default environment variable template
DEFAULT_ENV_TEMPLATE = """#===========================================
# MODEL & API
# (See https://docs.camel-ai.org/key_modules/models.html#)
#===========================================

# OPENAI API (https://platform.openai.com/api-keys)
OPENAI_API_KEY='Your_Key'
# OPENAI_API_BASE_URL=""

# Azure OpenAI API
# AZURE_OPENAI_BASE_URL=""
# AZURE_API_VERSION=""
# AZURE_OPENAI_API_KEY=""
# AZURE_DEPLOYMENT_NAME=""

# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
QWEN_API_KEY='Your_Key'

# DeepSeek API (https://platform.deepseek.com/api_keys)
DEEPSEEK_API_KEY='Your_Key'

#===========================================
# Tools & Services API
#===========================================

# Google Search API (https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3)
GOOGLE_API_KEY='Your_Key'
SEARCH_ENGINE_ID='Your_ID'

# Chunkr API (https://chunkr.ai/)
CHUNKR_API_KEY='Your_Key'

# Firecrawl API (https://www.firecrawl.dev/)
FIRECRAWL_API_KEY='Your_Key'
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
"""

def validate_input(question: str) -> bool:
    """Validate if user input is valid

    Args:
        question: User question

    Returns:
        bool: Whether the input is valid
    """
    # Check if input is empty or contains only spaces
    if not question or question.strip() == "":
        return False
    return True

def run_owl(question: str, example_module: str) -> Tuple[str, str, str]:
    """Run the OWL system and return results

    Args:
        question: User question
        example_module: Example module name to import (e.g., "run_terminal_zh" or "run_deep")

    Returns:
        Tuple[...]: Answer, token count, status
    """
    global CURRENT_PROCESS

    # Validate input
    if not validate_input(question):
        logging.warning("User submitted invalid input")
        return (
            "Please enter a valid question",
            "0",
            "❌ Error: Invalid input question",
        )

    try:
        # Ensure environment variables are loaded
        load_dotenv(find_dotenv(), override=True)
        logging.info(
            f"Processing question: '{question}', using module: {example_module}"
        )

        # Check if the module is in MODULE_DESCRIPTIONS
        if example_module not in MODULE_DESCRIPTIONS:
            logging.error(f"User selected an unsupported module: {example_module}")
            return (
                f"Selected module '{example_module}' is not supported",
                "0",
                "❌ Error: Unsupported module",
            )

        # Dynamically import target module
        module_path = f"examples.{example_module}"
        try:
            logging.info(f"Importing module: {module_path}")
            module = importlib.import_module(module_path)
        except ImportError as ie:
            logging.error(f"Unable to import module {module_path}: {str(ie)}")
            return (
                f"Unable to import module: {module_path}",
                "0",
                f"❌ Error: Module {example_module} does not exist or cannot be loaded - {str(ie)}",
            )
        except Exception as e:
            logging.error(
                f"Error occurred while importing module {module_path}: {str(e)}"
            )
            return (
                f"Error occurred while importing module: {module_path}",
                "0",
                f"❌ Error: {str(e)}",
            )

        # Check if it contains the construct_society function
        if not hasattr(module, "construct_society"):
            logging.error(
                f"construct_society function not found in module {module_path}"
            )
            return (
                f"construct_society function not found in module {module_path}",
                "0",
                "❌ Error: Module interface incompatible",
            )

        # Build society simulation
        try:
            logging.info("Building society simulation...")
            society = module.construct_society(question)

        except Exception as e:
            logging.error(f"Error occurred while building society simulation: {str(e)}")
            return (
                f"Error occurred while building society simulation: {str(e)}",
                "0",
                f"❌ Error: Build failed - {str(e)}",
            )

        # Run society simulation
        try:
            logging.info("Running society simulation...")
            answer, chat_history, token_info = run_society(society)
            logging.info("Society simulation completed")
        except Exception as e:
            logging.error(f"Error occurred while running society simulation: {str(e)}")
            return (
                f"Error occurred while running society simulation: {str(e)}",
                "0",
                f"❌ Error: Run failed - {str(e)}",
            )

        # Safely get token count
        if not isinstance(token_info, dict):
            token_info = {}

        completion_tokens = token_info.get("completion_token_count", 0)
        prompt_tokens = token_info.get("prompt_token_count", 0)
        total_tokens = completion_tokens + prompt_tokens

        logging.info(
            f"Processing completed, token usage: completion={completion_tokens}, prompt={prompt_tokens}, total={total_tokens}"
        )

        return (
            answer,
            f"Completion tokens: {completion_tokens:,} | Prompt tokens: {prompt_tokens:,} | Total: {total_tokens:,}",
            "✅ Successfully completed",
        )

    except Exception as e:
        logging.error(
            f"Uncaught error occurred while processing the question: {str(e)}"
        )
        return (f"Error occurred: {str(e)}", "0", f"❌ Error: {str(e)}")

def update_module_description(module_name: str) -> str:
    """Return the description of the selected module"""
    return MODULE_DESCRIPTIONS.get(module_name, "No description available")

# Store environment variables configured from the frontend
WEB_FRONTEND_ENV_VARS: dict[str, str] = {}

def init_env_file():
    """Initialize .env file if it doesn't exist"""
    dotenv_path = find_dotenv()
    if not dotenv_path:
        with open(".env", "w") as f:
            f.write(DEFAULT_ENV_TEMPLATE)
        dotenv_path = find_dotenv()
    return dotenv_path

def load_env_vars():
    """Load environment variables and return as dictionary format

    Returns:
        dict: Environment variable dictionary, each value is a tuple containing value and source (value, source)
    """
    dotenv_path = init_env_file()
    load_dotenv(dotenv_path, override=True)

    # Read environment variables from .env file
    env_file_vars = {}
    with open(dotenv_path, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                if "=" in line:
                    key, value = line.split("=", 1)
                    env_file_vars[key.strip()] = value.strip().strip("\"'")

    # Get from system environment variables
    system_env_vars = {
        k: v
        for k, v in os.environ.items()
        if k not in env_file_vars and k not in WEB_FRONTEND_ENV_VARS
    }

    # Merge environment variables and mark sources
    env_vars = {}

    # Add system environment variables (lowest priority)
    for key, value in system_env_vars.items():
        env_vars[key] = (value, "System")

    # Add .env file environment variables (medium priority)
    for key, value in env_file_vars.items():
        env_vars[key] = (value, ".env file")

    # Add frontend configured environment variables (highest priority)
    for key, value in WEB_FRONTEND_ENV_VARS.items():
        env_vars[key] = (value, "Frontend configuration")
        # Ensure operating system environment variables are also updated
        os.environ[key] = value

    return env_vars

def save_env_vars(env_vars):
    """Save environment variables to .env file

    Args:
        env_vars: Dictionary, keys are environment variable names, values can be strings or (value, source) tuples
    """
    try:
        dotenv_path = init_env_file()

        # Save each environment variable
        for key, value_data in env_vars.items():
            if key and key.strip():  # Ensure key is not empty
                # Handle case where value might be a tuple
                if isinstance(value_data, tuple):
                    value = value_data[0]
                else:
                    value = value_data

                set_key(dotenv_path, key.strip(), value.strip())

        # Reload environment variables to ensure they take effect
        load_dotenv(dotenv_path, override=True)

        return True, "Environment variables have been successfully saved!"
    except Exception as e:
        return False, f"Error saving environment variables: {str(e)}"

def add_env_var(key, value, from_frontend=True):
    """Add or update a single environment variable

    Args:
        key: Environment variable name
        value: Environment variable value
        from_frontend: Whether it's from frontend configuration, default is True
    """
    try:
        if not key or not key.strip():
            return False, "Variable name cannot be empty"

        key = key.strip()
        value = value.strip()

        # If from frontend, add to frontend environment variable dictionary
        if from_frontend:
            WEB_FRONTEND_ENV_VARS[key] = value
            # Directly update system environment variables
            os.environ[key] = value

        # Also update .env file
        dotenv_path = init_env_file()
        set_key(dotenv_path, key, value)
        load_dotenv(dotenv_path, override=True)

        return True, f"Environment variable {key} has been successfully added/updated!"
    except Exception as e:
        return False, f"Error adding environment variable: {str(e)}"

def delete_env_var(key):
    """Delete environment variable"""
    try:
        if not key or not key.strip():
            return False, "Variable name cannot be empty"

        key = key.strip()

        # Delete from .env file
        dotenv_path = init_env_file()
        unset_key(dotenv_path, key)

        # Delete from frontend environment variable dictionary
        if key in WEB_FRONTEND_ENV_VARS:
            del WEB_FRONTEND_ENV_VARS[key]

        # Also delete from current process environment
        if key in os.environ:
            del os.environ[key]

        return True, f"Environment variable {key} has been successfully deleted!"
    except Exception as e:
        return False, f"Error deleting environment variable: {str(e)}"

def is_api_related(key: str) -> bool:
    """Determine if an environment variable is API-related

    Args:
        key: Environment variable name

    Returns:
        bool: Whether it's API-related
    """
    # API-related keywords
    api_keywords = [
        "api",
        "key",
        "token",
        "secret",
        "password",
        "openai",
        "qwen",
        "deepseek",
        "google",
        "search",
        "hf",
        "hugging",
        "chunkr",
        "firecrawl",
    ]

    # Check if it contains API-related keywords (case insensitive)
    return any(keyword in key.lower() for keyword in api_keywords)

def get_api_guide(key: str) -> str:
    """Return the corresponding API guide based on the environment variable name

    Args:
        key: Environment variable name

    Returns:
        str: API guide link or description
    """
    key_lower = key.lower()
    if "openai" in key_lower:
        return "https://platform.openai.com/api-keys"
    elif "qwen" in key_lower or "dashscope" in key_lower:
        return "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
    elif "deepseek" in key_lower:
        return "https://platform.deepseek.com/api_keys"
    elif "ppio" in key_lower:
        return "https://ppinfra.com/settings/key-management?utm_source=github_owl"
    elif "google" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "search_engine_id" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "chunkr" in key_lower:
        return "https://chunkr.ai/"
    elif "firecrawl" in key_lower:
        return "https://www.firecrawl.dev/"
    elif "novita" in key_lower:
        return "https://novita.ai/settings/key-management?utm_source=github_owl&utm_medium=github_readme&utm_campaign=github_link"
    else:
        return ""

def update_env_table():
    """Update environment variable table display, only showing API-related environment variables"""
    env_vars = load_env_vars()
    # Filter out API-related environment variables
    api_env_vars = {k: v for k, v in env_vars.items() if is_api_related(k)}
    # Convert to list format to meet Gradio Dataframe requirements
    # Format: [Variable name, Variable value, Guide link]
    result = []
    for k, v in api_env_vars.items():
        guide = get_api_guide(k)
        # If there's a guide link, create a clickable link
        guide_link = (
            f"<a href='{guide}' target='_blank' class='guide-link'>🔗 Get</a>"
            if guide
            else ""
        )
        result.append([k, v[0], guide_link])
    return result

def save_env_table_changes(data):
    """Save changes to the environment variable table

    Args:
        data: Dataframe data, possibly a pandas DataFrame object

    Returns:
        str: Operation status information, containing HTML-formatted status message
    """
    try:
        logging.info(
            f"Starting to process environment variable table data, type: {type(data)}"
        )

        # Get all current environment variables
        current_env_vars = load_env_vars()
        processed_keys = set()  # Record processed keys to detect deleted variables

        # Process pandas DataFrame object
        import pandas as pd

        if isinstance(data, pd.DataFrame):
            # Get column name information
            columns = data.columns.tolist()
            logging.info(f"DataFrame column names: {columns}")

            # Iterate through each row of the DataFrame
            for index, row in data.iterrows():
                # Use column names to access data
                if len(columns) >= 3:
                    # Get variable name and value (column 0 is name, column 1 is value)
                    key = row[0] if isinstance(row, pd.Series) else row.iloc[0]
                    value = row[1] if isinstance(row, pd.Series) else row.iloc[1]

                    # Check if it's an empty row or deleted variable
                    if (
                        key and str(key).strip()
                    ):  # If key name is not empty, add or update
                        logging.info(
                            f"Processing environment variable: {key} = {value}"
                        )
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        # Process other formats
        elif isinstance(data, dict):
            logging.info(f"Dictionary format data keys: {list(data.keys())}")
            # If dictionary format, try different keys
            if "data" in data:
                rows = data["data"]
            elif "values" in data:
                rows = data["values"]
            elif "value" in data:
                rows = data["value"]
            else:
                # Try using dictionary directly as row data
                rows = []
                for key, value in data.items():
                    if key not in ["headers", "types", "columns"]:
                        rows.append([key, value])

            if isinstance(rows, list):
                for row in rows:
                    if isinstance(row, list) and len(row) >= 2:
                        key, value = row[0], row[1]
                        if key and str(key).strip():
                            add_env_var(key, str(value))
                            processed_keys.add(key)
        elif isinstance(data, list):
            # 列表格式
            for row in data:
                if isinstance(row, list) and len(row) >= 2:
                    key, value = row[0], row[1]
                    if key and str(key).strip():
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        else:
            logging.error(f"Unknown data format: {type(data)}")
            return f"❌ Save failed: Unknown data format {type(data)}"

        # Process deleted variables - check if there are variables in current environment not appearing in the table
        api_related_keys = {k for k in current_env_vars.keys() if is_api_related(k)}
        keys_to_delete = api_related_keys - processed_keys

        # Delete variables no longer in the table
        for key in keys_to_delete:
            logging.info(f"Deleting environment variable: {key}")
            delete_env_var(key)

        return "✅ Environment variables have been successfully saved"
    except Exception as e:
        import traceback

        error_details = traceback.format_exc()
        logging.error(f"Error saving environment variables: {str(e)}\n{error_details}")
        return f"❌ Save failed: {str(e)}"

def get_env_var_value(key):
    """Get the actual value of an environment variable

    Priority: Frontend configuration > .env file > System environment variables
    """
    # Check frontend configured environment variables
    if key in WEB_FRONTEND_ENV_VARS:
        return WEB_FRONTEND_ENV_VARS[key]

    # Check system environment variables (including those loaded from .env)
    return os.environ.get(key, "")

def create_ui():
    """Create enhanced Gradio interface"""

    def clear_log_file():
        """Clear log file content"""
        try:
            if LOG_FILE and os.path.exists(LOG_FILE):
                # Clear log file content instead of deleting the file
                open(LOG_FILE, "w").close()
                logging.info("Log file has been cleared")
                # Clear log queue
                while not LOG_QUEUE.empty():
                    try:
                        LOG_QUEUE.get_nowait()
                    except queue.Empty:
                        break
                return ""
            else:
                return ""
        except Exception as e:
            logging.error(f"Error clearing log file: {str(e)}")
            return ""

    # Create a real-time log update function
    def process_with_live_logs(question, module_name):
        """Process questions and update logs in real-time"""
        global CURRENT_PROCESS

        # Clear log file
        clear_log_file()

        # Create a background thread to process the question
        result_queue = queue.Queue()

        def process_in_background():
            try:
                result = run_owl(question, module_name)
                result_queue.put(result)
            except Exception as e:
                result_queue.put(
                    (f"Error occurred: {str(e)}", "0", f"❌ Error: {str(e)}")
                )

        # Start background processing thread
        bg_thread = threading.Thread(target=process_in_background)
        CURRENT_PROCESS = bg_thread  # Record current process
        bg_thread.start()

        # While waiting for processing to complete, update logs once per second
        while bg_thread.is_alive():
            # Update conversation record display
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # Always update status
            yield (
                "0",
                "<span class='status-indicator status-running'></span> Processing...",
                logs2,
            )

            time.sleep(1)

        # Processing complete, get results
        if not result_queue.empty():
            result = result_queue.get()
            answer, token_count, status = result

            # Final update of conversation record
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # Set different indicators based on status
            if "Error" in status:
                status_with_indicator = (
                    f"<span class='status-indicator status-error'></span> {status}"
                )
            else:
                status_with_indicator = (
                    f"<span class='status-indicator status-success'></span> {status}"
                )

            yield token_count, status_with_indicator, logs2
        else:
            logs2 = get_latest_logs(100, LOG_QUEUE)
            yield (
                "0",
                "<span class='status-indicator status-error'></span> Terminated",
                logs2,
            )

    with gr.Blocks(title="OWL", theme=gr.themes.Soft(primary_hue="blue")) as app:
        gr.Markdown(
            """
                # 🦉 OWL Multi-Agent Collaboration System

                Advanced multi-agent collaboration system developed based on the CAMEL framework, designed to solve complex problems through agent collaboration.

                Models and tools can be customized by modifying local scripts.

                This web app is currently in beta development. It is provided for demonstration and testing purposes only and is not yet recommended for production use.
                """
        )

        # Add custom CSS
        gr.HTML("""
            <style>
            /* Chat container style */
            .chat-container .chatbot {
                height: 500px;
                overflow-y: auto;
                border-radius: 10px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            }

            /* Improved tab style */
            .tabs .tab-nav {
                background-color: #f5f5f5;
                border-radius: 8px 8px 0 0;
                padding: 5px;
            }

            .tabs .tab-nav button {
                border-radius: 5px;
                margin: 0 3px;
                padding: 8px 15px;
                font-weight: 500;
            }

            .tabs .tab-nav button.selected {
                background-color: #2c7be5;
                color: white;
            }

            /* Status indicator style */
            .status-indicator {
                display: inline-block;
                width: 10px;
                height: 10px;
                border-radius: 50%;
                margin-right: 5px;
            }

            .status-running {
                background-color: #ffc107;
                animation: pulse 1.5s infinite;
            }

            .status-success {
                background-color: #28a745;
            }

            .status-error {
                background-color: #dc3545;
            }

            /* Log display area style */
            .log-display textarea {
                height: 400px !important;
                max-height: 400px !important;
                overflow-y: auto !important;
                font-family: monospace;
                font-size: 0.9em;
                white-space: pre-wrap;
                line-height: 1.4;
            }

            .log-display {
                border-radius: 10px;
                padding: 15px;
                margin-bottom: 20px;
                min-height: 50vh;
                max-height: 75vh;
            }

            /* Environment variable management style */
            .env-manager-container {
                border-radius: 10px;
                padding: 15px;
                background-color: #f9f9f9;
                margin-bottom: 20px;
            }

            .env-controls, .api-help-container {
                border-radius: 8px;
                padding: 15px;
                background-color: white;
                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
                height: 100%;
            }

            .env-add-group, .env-delete-group {
                margin-top: 20px;
                padding: 15px;
                border-radius: 8px;
                background-color: #f5f8ff;
                border: 1px solid #e0e8ff;
            }

            .env-delete-group {
                background-color: #fff5f5;
                border: 1px solid #ffe0e0;
            }

            .env-buttons {
                justify-content: flex-start;
                gap: 10px;
                margin-top: 10px;
            }

            .env-button {
                min-width: 100px;
            }

            .delete-button {
                background-color: #dc3545;
                color: white;
            }

            .env-table {
                margin-bottom: 15px;
            }

            /* Improved environment variable table style */
            .env-table table {
                border-collapse: separate;
                border-spacing: 0;
                width: 100%;
                border-radius: 8px;
                overflow: hidden;
                box-shadow: 0 2px 8px rgba(0,0,0,0.05);
            }

            .env-table th {
                background-color: #f0f7ff;
                padding: 12px 15px;
                text-align: left;
                font-weight: 600;
                color: #2c7be5;
                border-bottom: 2px solid #e0e8ff;
            }

            .env-table td {
                padding: 10px 15px;
                border-bottom: 1px solid #f0f0f0;
            }

            .env-table tr:hover td {
                background-color: #f9fbff;
            }

            .env-table tr:last-child td {
                border-bottom: none;
            }

            /* Status icon style */
            .status-icon-cell {
                text-align: center;
                font-size: 1.2em;
            }

            /* Link style */
            .guide-link {
                color: #2c7be5;
                text-decoration: none;
                cursor: pointer;
                font-weight: 500;
            }

            .guide-link:hover {
                text-decoration: underline;
            }

            .env-status {
                margin-top: 15px;
                font-weight: 500;
                padding: 10px;
                border-radius: 6px;
                transition: all 0.3s ease;
            }

            .env-status-success {
                background-color: #d4edda;
                color: #155724;
                border: 1px solid #c3e6cb;
            }

            .env-status-error {
                background-color: #f8d7da;
                color: #721c24;
                border: 1px solid #f5c6cb;
            }

            .api-help-accordion {
                margin-bottom: 8px;
                border-radius: 6px;
                overflow: hidden;
            }

            @keyframes pulse {
                0% { opacity: 1; }
                50% { opacity: 0.5; }
                100% { opacity: 1; }
            }
            </style>
            """)

        with gr.Row():
            with gr.Column(scale=0.5):
                question_input = gr.Textbox(
                    lines=5,
                    placeholder="Please enter your question...",
                    label="Question",
                    elem_id="question_input",
                    show_copy_button=True,
                    value="Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task.",
                )

                # Enhanced module selection dropdown
                # Only includes modules defined in MODULE_DESCRIPTIONS
                module_dropdown = gr.Dropdown(
                    choices=list(MODULE_DESCRIPTIONS.keys()),
                    value="run",
                    label="Select Function Module",
                    interactive=True,
                )

                # Module description text box
                module_description = gr.Textbox(
                    value=MODULE_DESCRIPTIONS["run"],
                    label="Module Description",
                    interactive=False,
                    elem_classes="module-info",
                )

                with gr.Row():
                    run_button = gr.Button(
                        "Run", variant="primary", elem_classes="primary"
                    )

                status_output = gr.HTML(
                    value="<span class='status-indicator status-success'></span> Ready",
                    label="Status",
                )
                token_count_output = gr.Textbox(
                    label="Token Count", interactive=False, elem_classes="token-count"
                )

                # Example questions
                examples = [
                    "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task.",
                    "Browse Amazon and find a product that is attractive to programmers. Please provide the product name and price",
                    "Write a hello world python file and save it locally",
                ]

                gr.Examples(examples=examples, inputs=question_input)

                gr.HTML("""
                        <div class="footer" id="about">
                            <h3>About OWL Multi-Agent Collaboration System</h3>
                            <p>OWL is an advanced multi-agent collaboration system developed based on the CAMEL framework, designed to solve complex problems through agent collaboration.</p>
                            <p>© 2025 CAMEL-AI.org. Based on Apache License 2.0 open source license</p>
                            <p><a href="https://github.com/camel-ai/owl" target="_blank">GitHub</a></p>
                        </div>
                    """)

            with gr.Tabs():  # Set conversation record as the default selected tab
                with gr.TabItem("Conversation Record"):
                    # Add conversation record display area
                    with gr.Group():
                        log_display2 = gr.Markdown(
                            value="No conversation records yet.",
                            elem_classes="log-display",
                        )

                    with gr.Row():
                        refresh_logs_button2 = gr.Button("Refresh Record")
                        auto_refresh_checkbox2 = gr.Checkbox(
                            label="Auto Refresh", value=True, interactive=True
                        )
                        clear_logs_button2 = gr.Button(
                            "Clear Record", variant="secondary"
                        )

                with gr.TabItem("Environment Variable Management", id="env-settings"):
                    with gr.Group(elem_classes="env-manager-container"):
                        gr.Markdown("""
                            ## Environment Variable Management

                            Set model API keys and other service credentials here. This information will be saved in a local `.env` file, ensuring your API keys are securely stored and not uploaded to the network. Correctly setting API keys is crucial for the functionality of the OWL system. Environment variables can be flexibly configured according to tool requirements.
                            """)

                        # Main content divided into two-column layout
                        with gr.Row():
                            # Left column: Environment variable management controls
                            with gr.Column(scale=3):
                                with gr.Group(elem_classes="env-controls"):
                                    # Environment variable table - set to interactive for direct editing
                                    gr.Markdown("""
                                    <div style="background-color: #e7f3fe; border-left: 6px solid #2196F3; padding: 10px; margin: 15px 0; border-radius: 4px;">
                                      <strong>Tip:</strong> Please make sure to run cp .env_template .env to create a local .env file, and flexibly configure the required environment variables according to the running module
                                    </div>
                                    """)

                                    # Enhanced environment variable table, supporting adding and deleting rows
                                    env_table = gr.Dataframe(
                                        headers=[
                                            "Variable Name",
                                            "Value",
                                            "Retrieval Guide",
                                        ],
                                        datatype=[
                                            "str",
                                            "str",
                                            "html",
                                        ],  # Set the last column as HTML type to support links
                                        row_count=10,  # Increase row count to allow adding new variables
                                        col_count=(3, "fixed"),
                                        value=update_env_table,
                                        label="API Keys and Environment Variables",
                                        interactive=True,  # Set as interactive, allowing direct editing
                                        elem_classes="env-table",
                                    )

                                    # Operation instructions
                                    gr.Markdown(
                                        """
                                    <div style="background-color: #fff3cd; border-left: 6px solid #ffc107; padding: 10px; margin: 15px 0; border-radius: 4px;">
                                    <strong>Operation Guide</strong>:
                                    <ul style="margin-top: 8px; margin-bottom: 8px;">
                                      <li><strong>Edit Variable</strong>: Click directly on the "Value" cell in the table to edit</li>
                                      <li><strong>Add Variable</strong>: Enter a new variable name and value in a blank row</li>
                                      <li><strong>Delete Variable</strong>: Clear the variable name to delete that row</li>
                                      <li><strong>Get API Key</strong>: Click on the link in the "Retrieval Guide" column to get the corresponding API key</li>
                                    </ul>
                                    </div>
                                    """,
                                        elem_classes="env-instructions",
                                    )

                                    # Environment variable operation buttons
                                    with gr.Row(elem_classes="env-buttons"):
                                        save_env_button = gr.Button(
                                            "💾 Save Changes",
                                            variant="primary",
                                            elem_classes="env-button",
                                        )
                                        refresh_button = gr.Button(
                                            "🔄 Refresh List", elem_classes="env-button"
                                        )

                                    # Status display
                                    env_status = gr.HTML(
                                        label="Operation Status",
                                        value="",
                                        elem_classes="env-status",
                                    )

                    # 连接事件处理函数
                    save_env_button.click(
                        fn=save_env_table_changes,
                        inputs=[env_table],
                        outputs=[env_status],
                    ).then(fn=update_env_table, outputs=[env_table])

                    refresh_button.click(fn=update_env_table, outputs=[env_table])

        # Set up event handling
        run_button.click(
            fn=process_with_live_logs,
            inputs=[question_input, module_dropdown],
            outputs=[token_count_output, status_output, log_display2],
        )

        # Module selection updates description
        module_dropdown.change(
            fn=update_module_description,
            inputs=module_dropdown,
            outputs=module_description,
        )

        # Conversation record related event handling
        refresh_logs_button2.click(
            fn=lambda: get_latest_logs(100, LOG_QUEUE), outputs=[log_display2]
        )

        clear_logs_button2.click(fn=clear_log_file, outputs=[log_display2])

        # Auto refresh control
        def toggle_auto_refresh(enabled):
            if enabled:
                return gr.update(every=3)
            else:
                return gr.update(every=0)

        auto_refresh_checkbox2.change(
            fn=toggle_auto_refresh,
            inputs=[auto_refresh_checkbox2],
            outputs=[log_display2],
        )

        # No longer automatically refresh logs by default

    return app

# Main function
def main():
    try:
        # Initialize logging system
        global LOG_FILE
        LOG_FILE = setup_logging()
        logging.info("OWL Web application started")

        # Start log reading thread
        log_thread = threading.Thread(
            target=log_reader_thread, args=(LOG_FILE,), daemon=True
        )
        log_thread.start()
        logging.info("Log reading thread started")

        # Initialize .env file (if it doesn't exist)
        init_env_file()
        app = create_ui()

        app.queue()
        app.launch(
            share=False,
            favicon_path=os.path.join(
                os.path.dirname(__file__), "assets", "owl-favicon.ico"
            ),
        )
    except Exception as e:
        logging.error(f"Error occurred while starting the application: {str(e)}")
        print(f"Error occurred while starting the application: {str(e)}")
        import traceback

        traceback.print_exc()

    finally:
        # Ensure log thread stops
        STOP_LOG_THREAD.set()
        STOP_REQUESTED.set()
        logging.info("Application closed")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

from camel.loaders import UnstructuredIO
from camel.toolkits.base import BaseToolkit
from camel.toolkits.function_tool import FunctionTool
from camel.toolkits import ImageAnalysisToolkit, ExcelToolkit
from camel.utils import retry_on_error
from camel.logger import get_logger
from camel.models import BaseModelBackend
from chunkr_ai import Chunkr
import requests
import mimetypes
import json
from typing import List, Optional, Tuple, Literal
from urllib.parse import urlparse
import os
import subprocess
import xmltodict
import nest_asyncio
import traceback

nest_asyncio.apply()

logger = get_logger(__name__)

class DocumentProcessingToolkit(BaseToolkit):
    r"""A class representing a toolkit for processing document and return the content of the document.

    This class provides method for processing docx, pdf, pptx, etc. It cannot process excel files.
    """

    def __init__(
        self, cache_dir: Optional[str] = None, model: Optional[BaseModelBackend] = None
    ):
        self.image_tool = ImageAnalysisToolkit(model=model)
        # self.audio_tool = AudioAnalysisToolkit()
        self.excel_tool = ExcelToolkit()

        self.cache_dir = "tmp/"
        if cache_dir:
            self.cache_dir = cache_dir

        self.uio = UnstructuredIO()

    @retry_on_error()
    def extract_document_content(self, document_path: str) -> Tuple[bool, str]:
        r"""Extract the content of a given document (or url) and return the processed text.
        It may filter out some information, resulting in inaccurate content.

        Args:
            document_path (str): The path of the document to be processed, either a local path or a URL. It can process image, audio files, zip files and webpages, etc.

        Returns:
            Tuple[bool, str]: A tuple containing a boolean indicating whether the document was processed successfully, and the content of the document (if success).
        """

        logger.debug(
            f"Calling extract_document_content function with document_path=`{document_path}`"
        )

        if any(document_path.endswith(ext) for ext in [".jpg", ".jpeg", ".png"]):
            res = self.image_tool.ask_question_about_image(
                document_path, "Please make a detailed caption about the image."
            )
            return True, res

        # if any(document_path.endswith(ext) for ext in ['.mp3', '.wav']):
        #     res = self.audio_tool.ask_question_about_audio(document_path, "Please transcribe the audio content to text.")
        #     return True, res

        if any(document_path.endswith(ext) for ext in ["xls", "xlsx"]):
            res = self.excel_tool.extract_excel_content(document_path)
            return True, res

        if any(document_path.endswith(ext) for ext in ["zip"]):
            extracted_files = self._unzip_file(document_path)
            return True, f"The extracted files are: {extracted_files}"

        if any(document_path.endswith(ext) for ext in ["json", "jsonl", "jsonld"]):
            with open(document_path, "r", encoding="utf-8") as f:
                content = json.load(f)
            f.close()
            return True, content

        if any(document_path.endswith(ext) for ext in ["py"]):
            with open(document_path, "r", encoding="utf-8") as f:
                content = f.read()
            f.close()
            return True, content

        if any(document_path.endswith(ext) for ext in ["xml"]):
            data = None
            with open(document_path, "r", encoding="utf-8") as f:
                content = f.read()
            f.close()

            try:
                data = xmltodict.parse(content)
                logger.debug(f"The extracted xml data is: {data}")
                return True, data

            except Exception:
                logger.debug(f"The raw xml data is: {content}")
                return True, content

        if self._is_webpage(document_path):
            try:
                extracted_text = self._extract_webpage_content(document_path)
                return True, extracted_text
            except Exception:
                try:
                    elements = self.uio.parse_file_or_url(document_path)
                    if elements is None:
                        logger.error(f"Failed to parse the document: {document_path}.")
                        return False, f"Failed to parse the document: {document_path}."
                    else:
                        # Convert elements list to string
                        elements_str = "\n".join(str(element) for element in elements)
                        return True, elements_str
                except Exception:
                    return False, "Failed to extract content from the webpage."

        else:
            try:
                elements = self.uio.parse_file_or_url(document_path)
                if elements is None:
                    logger.error(f"Failed to parse the document: {document_path}.")
                    return False, f"Failed to parse the document: {document_path}."
                else:
                    # Convert elements list to string
                    elements_str = "\n".join(str(element) for element in elements)
                    return True, elements_str

            except Exception as e:
                logger.error(traceback.format_exc())
                return False, f"Error occurred while processing document: {e}"

    def _is_webpage(self, url: str) -> bool:
        r"""Judge whether the given URL is a webpage."""
        try:
            parsed_url = urlparse(url)
            is_url = all([parsed_url.scheme, parsed_url.netloc])
            if not is_url:
                return False

            path = parsed_url.path
            file_type, _ = mimetypes.guess_type(path)
            if file_type is not None and "text/html" in file_type:
                return True

            response = requests.head(url, allow_redirects=True, timeout=10)
            content_type = response.headers.get("Content-Type", "").lower()

            if "text/html" in content_type:
                return True
            else:
                return False

        except requests.exceptions.RequestException as e:
            # raise RuntimeError(f"Error while checking the URL: {e}")
            logger.warning(f"Error while checking the URL: {e}")
            return False

        except TypeError:
            return True

    @retry_on_error()
    async def _extract_content_with_chunkr(
        self,
        document_path: str,
        output_format: Literal["json", "markdown"] = "markdown",
    ) -> str:
        chunkr = Chunkr(api_key=os.getenv("CHUNKR_API_KEY"))

        result = await chunkr.upload(document_path)

        # result = chunkr.upload(document_path)

        if result.status == "Failed":
            logger.error(
                f"Error while processing document {document_path}: {result.message} using Chunkr."
            )
            return f"Error while processing document: {result.message}"

        # extract document name
        document_name = os.path.basename(document_path)
        output_file_path: str

        if output_format == "json":
            output_file_path = f"{document_name}.json"
            result.json(output_file_path)

        elif output_format == "markdown":
            output_file_path = f"{document_name}.md"
            result.markdown(output_file_path)

        else:
            return "Invalid output format."

        with open(output_file_path, "r") as f:
            extracted_text = f.read()
        f.close()
        return extracted_text

    @retry_on_error()
    def _extract_webpage_content(self, url: str) -> str:
        api_key = os.getenv("FIRECRAWL_API_KEY")
        from firecrawl import FirecrawlApp

        # Initialize the FirecrawlApp with your API key
        app = FirecrawlApp(api_key=api_key)

        data = app.crawl_url(
            url, params={"limit": 1, "scrapeOptions": {"formats": ["markdown"]}}
        )
        logger.debug(f"Extractred data from {url}: {data}")
        if len(data["data"]) == 0:
            if data["success"]:
                return "No content found on the webpage."
            else:
                return "Error while crawling the webpage."

        return str(data["data"][0]["markdown"])

    def _download_file(self, url: str):
        r"""Download a file from a URL and save it to the cache directory."""
        try:
            response = requests.get(url, stream=True)
            response.raise_for_status()
            file_name = url.split("/")[-1]

            file_path = os.path.join(self.cache_dir, file_name)

            with open(file_path, "wb") as file:
                for chunk in response.iter_content(chunk_size=8192):
                    file.write(chunk)

            return file_path

        except requests.exceptions.RequestException as e:
            print(f"Error downloading the file: {e}")

    def _get_formatted_time(self) -> str:
        import time

        return time.strftime("%m%d%H%M")

    def _unzip_file(self, zip_path: str) -> List[str]:
        if not zip_path.endswith(".zip"):
            raise ValueError("Only .zip files are supported")

        zip_name = os.path.splitext(os.path.basename(zip_path))[0]
        extract_path = os.path.join(self.cache_dir, zip_name)
        os.makedirs(extract_path, exist_ok=True)

        try:
            subprocess.run(["unzip", "-o", zip_path, "-d", extract_path], check=True)
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"Failed to unzip file: {e}")

        extracted_files = []
        for root, _, files in os.walk(extract_path):
            for file in files:
                extracted_files.append(os.path.join(root, file))

        return extracted_files

    def get_tools(self) -> List[FunctionTool]:
        r"""Returns a list of FunctionTool objects representing the functions in the toolkit.

        Returns:
            List[FunctionTool]: A list of FunctionTool objects representing the functions in the toolkit.
        """
        return [
            FunctionTool(self.extract_document_content),
        ]  # Added closing triple quotes here

from typing import List
from typing import Optional
from camel.toolkits.base import BaseToolkit
from camel.toolkits.function_tool import FunctionTool
from camel.utils import api_keys_required, dependencies_required
import requests
import html2text
import re

class SECToolkit(BaseToolkit):
    r"""A class representing a toolkit for SEC filings analysis.

    This toolkit provides functionality to:
    - Fetch and process 10-K (annual) reports
    - Fetch and process 10-Q (quarterly) reports
    - Clean and format filing content for analysis
    - Support semantic search capabilities on filing content

    The toolkit requires SEC API credentials and handles HTTP requests
    to SEC's EDGAR database to retrieve filing documents.
    """

    @dependencies_required("sec_api")
    @api_keys_required(
        [
            (None, "SEC_API_API_KEY"),
        ]
    )
    def fetch_10k_filing(self, stock_name: str) -> Optional[str]:
        r"""Fetches and processes the latest 10-K form content for a given stock symbol.

        This function retrieves the most recent 10-K filing from SEC's database using
        the provided stock ticker symbol. It downloads the filing content, converts
        it from HTML to text format, and performs text cleaning.

        Args:
            stock_name (str): The stock ticker symbol (e.g., 'AAPL' for Apple Inc.).

        Returns:
            Optional[str]: A cleaned text version of the 10-K filing content.
                Returns None in the following cases:
                - No filings found for the given stock symbol
                - HTTP errors during content retrieval
                - Other exceptions during processing

                The returned text is preprocessed to:
                - Remove HTML formatting
                - Remove special characters
                - Retain only alphanumeric characters, dollar signs, spaces and newlines
        """

        from sec_api import QueryApi
        import os

        try:
            queryApi = QueryApi(api_key=os.environ['SEC_API_API_KEY'])
            query = {
                "query": {
                    "query_string": {
                        "query": f"ticker:{stock_name} AND formType:\"10-K\""
                    }
                },
                "from": "0",
                "size": "1",
                "sort": [{ "filedAt": { "order": "desc" }}]
            }
            response = queryApi.get_filings(query)
            if response and 'filings' in response:
                filings = response['filings']
            else:
                filings = []
            if len(filings) == 0:
                print("No filings found for this stock.")
                return None

            url = filings[0]['linkToFilingDetails']

            headers = {
                "User-Agent": "crewai.com bisan@crewai.com",
                "Accept-Encoding": "gzip, deflate",
                "Host": "www.sec.gov"
            }
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            h = html2text.HTML2Text()
            h.ignore_links = False
            text = h.handle(response.content.decode("utf-8"))

            text = re.sub(r"[^a-zA-Z$0-9\s\n]", "", text)
            return text
        except requests.exceptions.HTTPError as e:
            print(f"HTTP error occurred: {e}")
            return None
        except Exception as e:
            print(f"Error fetching 10-K URL: {e}")
            return None

    @dependencies_required("sec_api")
    @api_keys_required(
        [
            (None, "SEC_API_API_KEY"),
        ]
    )
    def fetch_10q_filing(self, stock_name: str) -> Optional[str]:
        r"""Fetches and processes the latest 10-Q form content for a given stock symbol.

        This function retrieves the most recent 10-Q filing from SEC's database using
        the provided stock ticker symbol. It downloads the filing content, converts
        it from HTML to text format, and performs text cleaning.

        Args:
            stock_name (str): The stock ticker symbol (e.g., 'AAPL' for Apple Inc.).

        Returns:
            Optional[str]: A cleaned text version of the 10-Q filing content.
                Returns None in the following cases:
                - No filings found for the given stock symbol
                - HTTP errors during content retrieval
                - Other exceptions during processing

                The returned text is preprocessed to:
                - Remove HTML formatting
                - Remove special characters
                - Retain only alphanumeric characters, dollar signs, spaces and newlines
        """

        from sec_api import QueryApi
        import os

        try:
            queryApi = QueryApi(api_key=os.environ['SEC_API_API_KEY'])
            query = {
                "query": {
                    "query_string": {
                        "query": f"ticker:{stock_name} AND formType:\"10-Q\""
                    }
                },
                "from": "0",
                "size": "1",
                "sort": [{ "filedAt": { "order": "desc" }}]
            }
            response = queryApi.get_filings(query)
            if response and 'filings' in response:
                filings = response['filings']
            else:
                filings = []
            if len(filings) == 0:
                print("No filings found for this stock.")
                return None

            url = filings[0]['linkToFilingDetails']

            headers = {
                "User-Agent": "crewai.com bisan@crewai.com",
                "Accept-Encoding": "gzip, deflate",
                "Host": "www.sec.gov"
            }
            response = requests.get(url, headers=headers)
            response.raise_for_status()  # Raise an exception for HTTP errors
            h = html2text.HTML2Text()
            h.ignore_links = False
            text = h.handle(response.content.decode("utf-8"))

            # Removing all non-English words, dollar signs, numbers, and newlines from text
            text = re.sub(r"[^a-zA-Z$0-9\s\n]", "", text)
            return text
        except requests.exceptions.HTTPError as e:
            print(f"HTTP error occurred: {e}")
            return None
        except Exception as e:
            print(f"Error fetching 10-Q URL: {e}")
            return None

    def get_tools(self) -> List[FunctionTool]:
        r"""Returns a list of FunctionTool objects representing the
        functions in the toolkit.

        Returns:
            List[FunctionTool]: A list of FunctionTool objects
                representing the functions in the toolkit.
        """

        return [
            FunctionTool(self.fetch_10k_filing),
            FunctionTool(self.fetch_10q_filing)
        ]

if __name__ == "__main__":
    toolkit = SECToolkit()
    data_10k = toolkit.fetch_10k_filing("GOOG")
    data_10q = toolkit.fetch_10q_filing("GOOG")
    # 检查 data_10k 是否为 None，如果不是则计算长度
    print(f"fetch_10k_filing AAPL = {len(data_10k) if data_10k is not None else 0}")
    print(f"fetch_10q_filing AAPL = {len(data_10q) if data_10q is not None else 0}")

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# 正しいモジュールパスからインポート
from utils import run_society
import os
import gradio as gr
import time
import json
import logging
import datetime
from typing import Tuple
import importlib
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
import threading
import queue
import re

os.environ["PYTHONIOENCODING"] = "utf-8"

# ロギングシステムを設定
def setup_logging():
    """ログをファイル、メモリキュー、およびコンソールに出力するようにロギングシステムを設定"""
    # logsディレクトリを作成（存在しない場合）
    logs_dir = os.path.join(os.path.dirname(__file__), "logs")
    os.makedirs(logs_dir, exist_ok=True)

    # ログファイル名を生成（現在の日付を使用）
    current_date = datetime.datetime.now().strftime("%Y-%m-%d")
    log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")

    # ルートロガーを設定（すべてのログをキャプチャ）
    root_logger = logging.getLogger()

    # 重複ログを避けるために既存のハンドラをクリア
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)

    root_logger.setLevel(logging.INFO)

    # ファイルハンドラを作成
    file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
    file_handler.setLevel(logging.INFO)

    # コンソールハンドラを作成
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)

    # フォーマッタを作成
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    # Add handlers to root logger
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)

    logging.info("ログシステムが初期化されました、ログファイル: %s", log_file)
    return log_file

# グローバル変数
LOG_FILE = None
LOG_QUEUE: queue.Queue = queue.Queue()  # ログキュー
STOP_LOG_THREAD = threading.Event()
CURRENT_PROCESS = None  # 現在実行中のプロセスを追跡するために使用
STOP_REQUESTED = threading.Event()  # 停止が要求されたかどうかをマークするために使用

# ログの読み取りと更新の関数
def log_reader_thread(log_file):
    """継続的にログファイルを読み取り、新しい行をキューに追加するバックグラウンドスレッド"""
    try:
        with open(log_file, "r", encoding="utf-8") as f:
            # ファイルの末尾に移動
            f.seek(0, 2)

            while not STOP_LOG_THREAD.is_set():
                line = f.readline()
                if line:
                    LOG_QUEUE.put(line)  # 会話記録キューに追加
                else:
                    # 新しい行がない場合は短時間待機
                    time.sleep(0.1)
    except Exception as e:
        logging.error(f"ログリーダースレッドエラー: {str(e)}")

def get_latest_logs(max_lines=100, queue_source=None):
    """キューから最新のログ行を取得するか、キューが空の場合はファイルから直接読み取る

    引数:
        max_lines: 返す最大行数
        queue_source: 使用するキューを指定、デフォルトはLOG_QUEUE

    戻り値:
        str: ログ内容
    """
    logs = []
    log_queue = queue_source if queue_source else LOG_QUEUE

    # 元のキューから削除せずに処理できるように、ログを保存する一時キューを作成
    temp_queue = queue.Queue()
    temp_logs = []

    try:
        # キューから利用可能なすべてのログ行を取得
        while not log_queue.empty() and len(temp_logs) < max_lines:
            log = log_queue.get_nowait()
            temp_logs.append(log)
            temp_queue.put(log)  # ログを一時キューに戻す
    except queue.Empty:
        pass

    # 会話記録を処理
    logs = temp_logs

    # 新しいログがないか、十分なログがない場合は、ファイルから直接最後の数行を読み取る
    if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "r", encoding="utf-8") as f:
                all_lines = f.readlines()
                # キューにすでにいくつかのログがある場合は、必要な残りの行だけを読み取る
                remaining_lines = max_lines - len(logs)
                file_logs = (
                    all_lines[-remaining_lines:]
                    if len(all_lines) > remaining_lines
                    else all_lines
                )

                # ファイルログをキューログの前に追加
                logs = file_logs + logs
        except Exception as e:
            error_msg = f"ログファイルの読み取りエラー: {str(e)}"
            logging.error(error_msg)
            if not logs:  # ログがない場合のみエラーメッセージを追加
                logs = [error_msg]

    # まだログがない場合は、プロンプトメッセージを返す
    if not logs:
        return "初期化中..."

    # ログをフィルタリングし、'camel.agents.chat_agent - INFO'を含むログのみを保持
    filtered_logs = []
    for log in logs:
        if "camel.agents.chat_agent - INFO" in log:
            filtered_logs.append(log)

    # フィルタリング後にログがない場合は、プロンプトメッセージを返す
    if not filtered_logs:
        return "まだ会話記録はありません。"

    # Process log content, extract the latest user and assistant messages
    simplified_logs = []

    # Use a set to track messages that have already been processed, to avoid duplicates
    processed_messages = set()

    def process_message(role, content):
        # Create a unique identifier to track messages
        msg_id = f"{role}:{content}"
        if msg_id in processed_messages:
            return None

        processed_messages.add(msg_id)
        content = content.replace("\\n", "\n")
        lines = [line.strip() for line in content.split("\n")]
        content = "\n".join(lines)

        role_emoji = "🙋" if role.lower() == "user" else "🤖"
        role_ja = "ユーザー" if role.lower() == "user" else "アシスタント"
        return f"""### {role_emoji} {role_ja}エージェント

{content}"""

    for log in filtered_logs:
        formatted_messages = []
        # Try to extract message array
        messages_match = re.search(
            r"Model (.*?), index (\d+), processed these messages: (\[.*\])", log
        )

        if messages_match:
            try:
                messages = json.loads(messages_match.group(3))
                for msg in messages:
                    if msg.get("role") in ["user", "assistant"]:
                        formatted_msg = process_message(
                            msg.get("role"), msg.get("content", "")
                        )
                        if formatted_msg:
                            formatted_messages.append(formatted_msg)
            except json.JSONDecodeError:
                pass

        # If JSON parsing fails or no message array is found, try to extract conversation content directly
        if not formatted_messages:
            user_pattern = re.compile(r"\{'role': 'user', 'content': '(.*?)'\}")
            assistant_pattern = re.compile(
                r"\{'role': 'assistant', 'content': '(.*?)'\}"
            )

            for content in user_pattern.findall(log):
                formatted_msg = process_message("user", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

            for content in assistant_pattern.findall(log):
                formatted_msg = process_message("assistant", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

        if formatted_messages:
            simplified_logs.append("\n\n".join(formatted_messages))

    # Format log output, ensure appropriate separation between each conversation record
    formatted_logs = []
    for i, log in enumerate(simplified_logs):
        # Remove excess whitespace characters from beginning and end
        log = log.strip()

        formatted_logs.append(log)

        # Ensure each conversation record ends with a newline
        if not log.endswith("\n"):
            formatted_logs.append("\n")

    return "\n".join(formatted_logs)

# モジュールの説明を含む辞書
MODULE_DESCRIPTIONS = {
    "run": "デフォルトモード: OpenAIモデルのデフォルトエージェント協力モードを使用し、ほとんどのタスクに適しています。",
    "run_mini": "最小限の設定でOpenAIモデルを使用してタスクを処理します",
    "run_deepseek_zh": "中国語タスクを処理するためにdeepseekモデルを使用します",
    "run_openai_compatible_model": "OpenAI互換モデルを使用してタスクを処理します",
    "run_ollama": "ローカルのollamaモデルを使用してタスクを処理します",
    "run_qwen_mini_zh": "最小限の設定でqwenモデルを使用してタスクを処理します",
    "run_qwen_zh": "qwenモデルを使用して中国語タスクを処理します",
    "run_azure_openai": "Azure OpenAIモデルを使用してタスクを処理します",
    "run_groq": "groqモデルを使用してタスクを処理します",
    "run_together_ai": "together aiモデルを使用してタスクを処理します",
}

# デフォルトの環境変数テンプレート
DEFAULT_ENV_TEMPLATE = """#===========================================
# モデル & API
# (参照: https://docs.camel-ai.org/key_modules/models.html#)
#===========================================

# OPENAI API (https://platform.openai.com/api-keys)
OPENAI_API_KEY='あなたのキー'
# OPENAI_API_BASE_URL=""

# Azure OpenAI API
# AZURE_OPENAI_BASE_URL=""
# AZURE_API_VERSION=""
# AZURE_OPENAI_API_KEY=""
# AZURE_DEPLOYMENT_NAME=""

# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
QWEN_API_KEY='あなたのキー'

# DeepSeek API (https://platform.deepseek.com/api_keys)
DEEPSEEK_API_KEY='あなたのキー'

#===========================================
# ツール & サービス API
#===========================================

# Google Search API (https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3)
GOOGLE_API_KEY='あなたのキー'
SEARCH_ENGINE_ID='あなたのID'

# Chunkr API (https://chunkr.ai/)
CHUNKR_API_KEY='あなたのキー'

# Firecrawl API (https://www.firecrawl.dev/)
FIRECRAWL_API_KEY='あなたのキー'
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
"""

def validate_input(question: str) -> bool:
    """ユーザー入力が有効かどうかを検証

    引数:
        question: ユーザーの質問

    戻り値:
        bool: 入力が有効かどうか
    """
    # 入力が空またはスペースのみかどうかをチェック
    if not question or question.strip() == "":
        return False
    return True

def run_owl(question: str, example_module: str) -> Tuple[str, str, str]:
    """OWLシステムを実行して結果を返す

    引数:
        question: ユーザーの質問
        example_module: インポートする例モジュール名（例："run_terminal_zh"や"run_deep"）

    戻り値:
        Tuple[...]: 回答、トークン数、ステータス
    """
    global CURRENT_PROCESS

    # Validate input
    if not validate_input(question):
        logging.warning("ユーザーが無効な入力を送信しました")
        return (
            "有効な質問を入力してください",
            "0",
            "❌ エラー: 無効な入力質問",
        )

    try:
        # Ensure environment variables are loaded
        load_dotenv(find_dotenv(), override=True)
        logging.info(f"質問を処理中: '{question}', モジュール使用: {example_module}")

        # Check if the module is in MODULE_DESCRIPTIONS
        if example_module not in MODULE_DESCRIPTIONS:
            logging.error(
                f"ユーザーがサポートされていないモジュールを選択しました: {example_module}"
            )
            return (
                f"選択されたモジュール '{example_module}' はサポートされていません",
                "0",
                "❌ エラー: サポートされていないモジュール",
            )

        # Dynamically import target module
        module_path = f"examples.{example_module}"
        try:
            logging.info(f"モジュールをインポート中: {module_path}")
            module = importlib.import_module(module_path)
        except ImportError as ie:
            logging.error(f"モジュール {module_path} をインポートできません: {str(ie)}")
            return (
                f"モジュールをインポートできません: {module_path}",
                "0",
                f"❌ エラー: モジュール {example_module} が存在しないか、読み込めません - {str(ie)}",
            )
        except Exception as e:
            logging.error(
                f"モジュール {module_path} のインポート中にエラーが発生しました: {str(e)}"
            )
            return (
                f"モジュールのインポート中にエラーが発生しました: {module_path}",
                "0",
                f"❌ エラー: {str(e)}",
            )

        # Check if it contains the construct_society function
        if not hasattr(module, "construct_society"):
            logging.error(
                f"construct_society 関数がモジュール {module_path} に見つかりません"
            )
            return (
                f"construct_society 関数がモジュール {module_path} に見つかりません",
                "0",
                "❌ エラー: モジュールインターフェースが互換性がありません",
            )

        # Build society simulation
        try:
            logging.info("社会シミュレーションを構築中...")
            society = module.construct_society(question)

        except Exception as e:
            logging.error(
                f"社会シミュレーションの構築中にエラーが発生しました: {str(e)}"
            )
            return (
                f"社会シミュレーションの構築中にエラーが発生しました: {str(e)}",
                "0",
                f"❌ エラー: 構築に失敗しました - {str(e)}",
            )

        # Run society simulation
        try:
            logging.info("社会シミュレーションを実行中...")
            answer, chat_history, token_info = run_society(society)
            logging.info("社会シミュレーションが完了しました")
        except Exception as e:
            logging.error(
                f"社会シミュレーションの実行中にエラーが発生しました: {str(e)}"
            )
            return (
                f"社会シミュレーションの実行中にエラーが発生しました: {str(e)}",
                "0",
                f"❌ エラー: 実行に失敗しました - {str(e)}",
            )

        # Safely get token count
        if not isinstance(token_info, dict):
            token_info = {}

        completion_tokens = token_info.get("completion_token_count", 0)
        prompt_tokens = token_info.get("prompt_token_count", 0)
        total_tokens = completion_tokens + prompt_tokens

        logging.info(
            f"処理が完了しました、トークン使用量: 完了={completion_tokens}, プロンプト={prompt_tokens}, 合計={total_tokens}"
        )

        return (
            answer,
            f"完了トークン: {completion_tokens:,} | プロンプトトークン: {prompt_tokens:,} | 合計: {total_tokens:,}",
            "✅ 正常に完了しました",
        )

    except Exception as e:
        logging.error(f"質問の処理中に予期しないエラーが発生しました: {str(e)}")
        return (f"エラーが発生しました: {str(e)}", "0", f"❌ エラー: {str(e)}")

def update_module_description(module_name: str) -> str:
    """選択されたモジュールの説明を返す"""
    return MODULE_DESCRIPTIONS.get(module_name, "説明はありません")

# フロントエンドから設定された環境変数を保存
WEB_FRONTEND_ENV_VARS: dict[str, str] = {}

def init_env_file():
    """.envファイルが存在しない場合に初期化する"""
    dotenv_path = find_dotenv()
    if not dotenv_path:
        with open(".env", "w") as f:
            f.write(DEFAULT_ENV_TEMPLATE)
        dotenv_path = find_dotenv()
    return dotenv_path

def load_env_vars():
    """環境変数を読み込み、辞書形式で返す

    戻り値:
        dict: 環境変数辞書、各値は値とソースを含むタプル（value, source）
    """
    dotenv_path = init_env_file()
    load_dotenv(dotenv_path, override=True)

    # .envファイルから環境変数を読み込む
    env_file_vars = {}
    with open(dotenv_path, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                if "=" in line:
                    key, value = line.split("=", 1)
                    env_file_vars[key.strip()] = value.strip().strip("\"'")

    # システム環境変数から取得
    system_env_vars = {
        k: v
        for k, v in os.environ.items()
        if k not in env_file_vars and k not in WEB_FRONTEND_ENV_VARS
    }

    # 環境変数をマージしてソースをマーク
    env_vars = {}

    # システム環境変数を追加（最低優先度）
    for key, value in system_env_vars.items():
        env_vars[key] = (value, "システム")

    # .envファイル環境変数を追加（中程度の優先度）
    for key, value in env_file_vars.items():
        env_vars[key] = (value, ".envファイル")

    # フロントエンドで設定された環境変数を追加（最高優先度）
    for key, value in WEB_FRONTEND_ENV_VARS.items():
        env_vars[key] = (value, "フロントエンド設定")
        # オペレーティングシステムの環境変数も更新されていることを確認
        os.environ[key] = value

    return env_vars

def save_env_vars(env_vars):
    """環境変数を.envファイルに保存

    引数:
        env_vars: 辞書、キーは環境変数名、値は文字列または（value, source）タプル
    """
    try:
        dotenv_path = init_env_file()

        # Save each environment variable
        for key, value_data in env_vars.items():
            if key and key.strip():  # Ensure key is not empty
                # Handle case where value might be a tuple
                if isinstance(value_data, tuple):
                    value = value_data[0]
                else:
                    value = value_data

                set_key(dotenv_path, key.strip(), value.strip())

        # Reload environment variables to ensure they take effect
        load_dotenv(dotenv_path, override=True)

        return True, "環境変数が正常に保存されました！"
    except Exception as e:
        return False, f"環境変数の保存中にエラーが発生しました: {str(e)}"

def add_env_var(key, value, from_frontend=True):
    """単一の環境変数を追加または更新

    引数:
        key: 環境変数名
        value: 環境変数値
        from_frontend: フロントエンド設定からかどうか、デフォルトはTrue
    """
    try:
        if not key or not key.strip():
            return False, "変数名は空にできません"

        key = key.strip()
        value = value.strip()

        # If from frontend, add to frontend environment variable dictionary
        if from_frontend:
            WEB_FRONTEND_ENV_VARS[key] = value
            # Directly update system environment variables
            os.environ[key] = value

        # Also update .env file
        dotenv_path = init_env_file()
        set_key(dotenv_path, key, value)
        load_dotenv(dotenv_path, override=True)

        return True, f"環境変数 {key} が正常に追加/更新されました！"
    except Exception as e:
        return False, f"環境変数の追加中にエラーが発生しました: {str(e)}"

def delete_env_var(key):
    """環境変数を削除"""
    try:
        if not key or not key.strip():
            return False, "変数名は空にできません"

        key = key.strip()

        # Delete from .env file
        dotenv_path = init_env_file()
        unset_key(dotenv_path, key)

        # Delete from frontend environment variable dictionary
        if key in WEB_FRONTEND_ENV_VARS:
            del WEB_FRONTEND_ENV_VARS[key]

        # Also delete from current process environment
        if key in os.environ:
            del os.environ[key]

        return True, f"環境変数 {key} が正常に削除されました！"
    except Exception as e:
        return False, f"環境変数の削除中にエラーが発生しました: {str(e)}"

def is_api_related(key: str) -> bool:
    """環境変数がAPI関連かどうかを判断

    引数:
        key: 環境変数名

    戻り値:
        bool: API関連かどうか
    """
    # API関連キーワード
    api_keywords = [
        "api",
        "key",
        "token",
        "secret",
        "password",
        "openai",
        "qwen",
        "deepseek",
        "google",
        "search",
        "hf",
        "hugging",
        "chunkr",
        "firecrawl",
    ]

    # API関連キーワードが含まれているか確認（大文字小文字を区別しない）
    return any(keyword in key.lower() for keyword in api_keywords)

def get_api_guide(key: str) -> str:
    """環境変数名に基づいて対応するAPIガイドを返す

    引数:
        key: 環境変数名

    戻り値:
        str: APIガイドリンクまたは説明
    """
    key_lower = key.lower()
    if "openai" in key_lower:
        return "https://platform.openai.com/api-keys"
    elif "qwen" in key_lower or "dashscope" in key_lower:
        return "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
    elif "deepseek" in key_lower:
        return "https://platform.deepseek.com/api_keys"
    elif "google" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "search_engine_id" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "chunkr" in key_lower:
        return "https://chunkr.ai/"
    elif "firecrawl" in key_lower:
        return "https://www.firecrawl.dev/"
    else:
        return ""

def update_env_table():
    """環境変数テーブル表示を更新し、API関連の環境変数のみを表示"""
    env_vars = load_env_vars()
    # Filter out API-related environment variables
    api_env_vars = {k: v for k, v in env_vars.items() if is_api_related(k)}
    # Convert to list format to meet Gradio Dataframe requirements
    # Format: [Variable name, Variable value, Guide link]
    result = []
    for k, v in api_env_vars.items():
        guide = get_api_guide(k)
        # If there's a guide link, create a clickable link
        guide_link = (
            f"🔗 取得"
            if guide
            else ""
        )
        result.append([k, v[0], guide_link])
    return result

def save_env_table_changes(data):
    """環境変数テーブルへの変更を保存

    引数:
        data: データフレームデータ、おそらくpandas DataFrameオブジェクト

    戻り値:
        str: 操作ステータス情報、HTML形式のステータスメッセージを含む
    """
    try:
        logging.info(f"環境変数テーブルデータの処理を開始します、タイプ: {type(data)}")

        # Get all current environment variables
        current_env_vars = load_env_vars()
        processed_keys = set()  # Record processed keys to detect deleted variables

        # Process pandas DataFrame object
        import pandas as pd

        if isinstance(data, pd.DataFrame):
            # Get column name information
            columns = data.columns.tolist()
            logging.info(f"DataFrameの列名: {columns}")

            # Iterate through each row of the DataFrame
            for index, row in data.iterrows():
                # Use column names to access data
                if len(columns) >= 3:
                    # Get variable name and value (column 0 is name, column 1 is value)
                    key = row[0] if isinstance(row, pd.Series) else row.iloc[0]
                    value = row[1] if isinstance(row, pd.Series) else row.iloc[1]

                    # Check if it's an empty row or deleted variable
                    if (
                        key and str(key).strip()
                    ):  # If key name is not empty, add or update
                        logging.info(f"環境変数の処理: {key} = {value}")
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        # Process other formats
        elif isinstance(data, dict):
            logging.info(f"辞書形式データのキー: {list(data.keys())}")
            # If dictionary format, try different keys
            if "data" in data:
                rows = data["data"]
            elif "values" in data:
                rows = data["values"]
            elif "value" in data:
                rows = data["value"]
            else:
                # Try using dictionary directly as row data
                rows = []
                for key, value in data.items():
                    if key not in ["headers", "types", "columns"]:
                        rows.append([key, value])

            if isinstance(rows, list):
                for row in rows:
                    if isinstance(row, list) and len(row) >= 2:
                        key, value = row[0], row[1]
                        if key and str(key).strip():
                            add_env_var(key, str(value))
                            processed_keys.add(key)
        elif isinstance(data, list):
            # 列表格式
            for row in data:
                if isinstance(row, list) and len(row) >= 2:
                    key, value = row[0], row[1]
                    if key and str(key).strip():
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        else:
            logging.error(f"不明なデータ形式: {type(data)}")
            return f"❌ 保存に失敗しました: 不明なデータ形式 {type(data)}"

        # Process deleted variables - check if there are variables in current environment not appearing in the table
        api_related_keys = {k for k in current_env_vars.keys() if is_api_related(k)}
        keys_to_delete = api_related_keys - processed_keys

        # Delete variables no longer in the table
        for key in keys_to_delete:
            logging.info(f"環境変数の削除: {key}")
            delete_env_var(key)

        return "✅ 環境変数が正常に保存されました"
    except Exception as e:
        import traceback

        error_details = traceback.format_exc()
        logging.error(
            f"環境変数の保存中にエラーが発生しました: {str(e)}\n{error_details}"
        )
        return f"❌ 保存に失敗しました: {str(e)}"

def get_env_var_value(key):
    """環境変数の実際の値を取得

    優先順位: フロントエンド設定 > .envファイル > システム環境変数
    """
    # Check frontend configured environment variables
    if key in WEB_FRONTEND_ENV_VARS:
        return WEB_FRONTEND_ENV_VARS[key]

    # Check system environment variables (including those loaded from .env)
    return os.environ.get(key, "")

def create_ui():
    """拡張されたGradioインターフェースを作成"""

    def clear_log_file():
        """ログファイルの内容をクリア"""
        try:
            if LOG_FILE and os.path.exists(LOG_FILE):
                # Clear log file content instead of deleting the file
                open(LOG_FILE, "w").close()
                logging.info("ログファイルがクリアされました")
                # Clear log queue
                while not LOG_QUEUE.empty():
                    try:
                        LOG_QUEUE.get_nowait()
                    except queue.Empty:
                        break
                return ""
            else:
                return ""
        except Exception as e:
            logging.error(f"ログファイルのクリア中にエラーが発生しました: {str(e)}")
            return ""

    # リアルタイムログ更新関数を作成
    def process_with_live_logs(question, module_name):
        """質問を処理し、リアルタイムでログを更新"""
        global CURRENT_PROCESS

        # Clear log file
        clear_log_file()

        # 質問を処理するバックグラウンドスレッドを作成
        result_queue = queue.Queue()

        def process_in_background():
            try:
                result = run_owl(question, module_name)
                result_queue.put(result)
            except Exception as e:
                result_queue.put(
                    (f"エラーが発生しました: {str(e)}", "0", f"❌ エラー: {str(e)}")
                )

        # バックグラウンド処理スレッドを開始
        bg_thread = threading.Thread(target=process_in_background)
        CURRENT_PROCESS = bg_thread  # 現在のプロセスを記録
        bg_thread.start()

        # 処理が完了するのを待つ間、1秒ごとにログを更新
        while bg_thread.is_alive():
            # 会話記録表示を更新
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # Always update status
            yield (
                "0",
                " 処理中...",
                logs2,
            )

            time.sleep(1)

        # Processing complete, get results
        if not result_queue.empty():
            result = result_queue.get()
            answer, token_count, status = result

            # Final update of conversation record
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # Set different indicators based on status
            if "エラー" in status:
                status_with_indicator = (
                    f" {status}"
                )
            else:
                status_with_indicator = (
                    f" {status}"
                )

            yield token_count, status_with_indicator, logs2
        else:
            logs2 = get_latest_logs(100, LOG_QUEUE)
            yield (
                "0",
                " 終了しました",
                logs2,
            )

    with gr.Blocks(title="OWL", theme=gr.themes.Soft(primary_hue="blue")) as app:
        gr.Markdown(
            """
                # 🦉 OWL マルチエージェント協力システム

                CAMELフレームワークをベースに開発された高度なマルチエージェント協力システムで、エージェント協力を通じて複雑な問題を解決するように設計されています。

                モデルやツールはローカルスクリプトを変更することでカスタマイズできます。

                このウェブアプリは現在ベータ開発中です。デモンストレーションとテスト目的のみで提供されており、本番環境での使用はまだ推奨されていません。
                """
        )

        # Add custom CSS
        gr.HTML("""

            """)

        with gr.Row():
            with gr.Column(scale=0.5):
                question_input = gr.Textbox(
                    lines=5,
                    placeholder="質問を入力してください...",
                    label="質問",
                    elem_id="question_input",
                    show_copy_button=True,
                    value="Googleで検索して、camel-aiのcamelフレームワークのGitHubスター数、フォーク数などを要約し、その数値をplotパッケージを使ってPythonファイルに書き込み、ローカルに保存して、生成したPythonファイルを実行してください。",
                )

                # Enhanced module selection dropdown
                # Only includes modules defined in MODULE_DESCRIPTIONS
                module_dropdown = gr.Dropdown(
                    choices=list(MODULE_DESCRIPTIONS.keys()),
                    value="run",
                    label="機能モジュールを選択",
                    interactive=True,
                )

                # Module description text box
                module_description = gr.Textbox(
                    value=MODULE_DESCRIPTIONS["run"],
                    label="モジュールの説明",
                    interactive=False,
                    elem_classes="module-info",
                )

                with gr.Row():
                    run_button = gr.Button(
                        "実行", variant="primary", elem_classes="primary"
                    )

                status_output = gr.HTML(
                    value=" 準備完了",
                    label="ステータス",
                )
                token_count_output = gr.Textbox(
                    label="トークン数", interactive=False, elem_classes="token-count"
                )

                # Example questions
                examples = [
                    "Googleで検索して、camel-aiのcamelフレームワークのGitHubスター数、フォーク数などを要約し、その数値をplotパッケージを使ってPythonファイルに書き込み、ローカルに保存して、生成したPythonファイルを実行してください。",
                    "Amazonを閲覧して、プログラマーに魅力的な商品を見つけてください。商品名と価格を提供してください",
                    "Hello worldを表示するPythonファイルを作成し、ローカルに保存してください",
                ]

                gr.Examples(examples=examples, inputs=question_input)

                gr.HTML("""

OWLマルチエージェント協力システムについて

OWLはCAMELフレームワークをベースに開発された高度なマルチエージェント協力システムで、エージェント協力を通じて複雑な問題を解決するように設計されています。

© 2025 CAMEL-AI.org. Apache License 2.0オープンソースライセンスに基づいています

GitHub

                    """)

            with gr.Tabs():  # Set conversation record as the default selected tab
                with gr.TabItem("会話記録"):
                    # Add conversation record display area
                    with gr.Group():
                        log_display2 = gr.Markdown(
                            value="まだ会話記録はありません。",
                            elem_classes="log-display",
                        )

                    with gr.Row():
                        refresh_logs_button2 = gr.Button("記録を更新")
                        auto_refresh_checkbox2 = gr.Checkbox(
                            label="自動更新", value=True, interactive=True
                        )
                        clear_logs_button2 = gr.Button(
                            "記録をクリア", variant="secondary"
                        )

                with gr.TabItem("環境変数管理", id="env-settings"):
                    with gr.Group(elem_classes="env-manager-container"):
                        gr.Markdown("""
                            ## 環境変数管理

                            ここでモデルAPIキーやその他のサービス認証情報を設定します。この情報はローカルの`.env`ファイルに保存され、APIキーが安全に保存され、ネットワークにアップロードされないことを保証します。APIキーを正しく設定することは、OWLシステムの機能にとって非常に重要です。環境変数はツールの要件に応じて柔軟に設定できます。
                            """)

                        # Main content divided into two-column layout
                        with gr.Row():
                            # Left column: Environment variable management controls
                            with gr.Column(scale=3):
                                with gr.Group(elem_classes="env-controls"):
                                    # Environment variable table - set to interactive for direct editing
                                    gr.Markdown("""

                                      ヒント: cp .env_template .env を実行してローカルの.envファイルを作成し、実行モジュールに応じて必要な環境変数を柔軟に設定してください

                                    """)

                                    # Enhanced environment variable table, supporting adding and deleting rows
                                    env_table = gr.Dataframe(
                                        headers=[
                                            "変数名",
                                            "値",
                                            "取得ガイド",
                                        ],
                                        datatype=[
                                            "str",
                                            "str",
                                            "html",
                                        ],  # Set the last column as HTML type to support links
                                        row_count=10,  # Increase row count to allow adding new variables
                                        col_count=(3, "fixed"),
                                        value=update_env_table,
                                        label="APIキーと環境変数",
                                        interactive=True,  # Set as interactive, allowing direct editing
                                        elem_classes="env-table",
                                    )

                                    # Operation instructions
                                    gr.Markdown(
                                        """

                                    操作ガイド:

変数の編集: テーブルの「値」セルを直接クリックして編集

変数の追加: 空白行に新しい変数名と値を入力

変数の削除: 変数名をクリアしてその行を削除

APIキーの取得: 「取得ガイド」列のリンクをクリックして対応するAPIキーを取得

                                    """,
                                        elem_classes="env-instructions",
                                    )

                                    # Environment variable operation buttons
                                    with gr.Row(elem_classes="env-buttons"):
                                        save_env_button = gr.Button(
                                            "💾 変更を保存",
                                            variant="primary",
                                            elem_classes="env-button",
                                        )
                                        refresh_button = gr.Button(
                                            "🔄 リストを更新", elem_classes="env-button"
                                        )

                                    # Status display
                                    env_status = gr.HTML(
                                        label="操作ステータス",
                                        value="",
                                        elem_classes="env-status",
                                    )

                    # 連接事件処理函数
                    save_env_button.click(
                        fn=save_env_table_changes,
                        inputs=[env_table],
                        outputs=[env_status],
                    ).then(fn=update_env_table, outputs=[env_table])

                    refresh_button.click(fn=update_env_table, outputs=[env_table])

        # Set up event handling
        run_button.click(
            fn=process_with_live_logs,
            inputs=[question_input, module_dropdown],
            outputs=[token_count_output, status_output, log_display2],
        )

        # Module selection updates description
        module_dropdown.change(
            fn=update_module_description,
            inputs=module_dropdown,
            outputs=module_description,
        )

        # Conversation record related event handling
        refresh_logs_button2.click(
            fn=lambda: get_latest_logs(100, LOG_QUEUE), outputs=[log_display2]
        )

        clear_logs_button2.click(fn=clear_log_file, outputs=[log_display2])

        # Auto refresh control
        def toggle_auto_refresh(enabled):
            if enabled:
                return gr.update(every=3)
            else:
                return gr.update(every=0)

        auto_refresh_checkbox2.change(
            fn=toggle_auto_refresh,
            inputs=[auto_refresh_checkbox2],
            outputs=[log_display2],
        )

        # No longer automatically refresh logs by default

    return app

# メイン関数
def main():
    try:
        # ロギングシステムを初期化
        global LOG_FILE
        LOG_FILE = setup_logging()
        logging.info("OWL Webアプリケーションが開始されました")

        # ログ読み取りスレッドを開始
        log_thread = threading.Thread(
            target=log_reader_thread, args=(LOG_FILE,), daemon=True
        )
        log_thread.start()
        logging.info("ログ読み取りスレッドが開始されました")

        # .envファイルを初期化（存在しない場合）
        init_env_file()
        app = create_ui()

        app.queue()
        app.launch(
            share=False,
            favicon_path=os.path.join(
                os.path.dirname(__file__), "assets", "owl-favicon.ico"
            ),
        )
    except Exception as e:
        logging.error(f"アプリケーションの起動中にエラーが発生しました: {str(e)}")
        print(f"アプリケーションの起動中にエラーが発生しました: {str(e)}")
        import traceback

        traceback.print_exc()

    finally:
        # ログスレッドが停止することを確認
        STOP_LOG_THREAD.set()
        STOP_REQUESTED.set()
        logging.info("アプリケーションが終了しました")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import sys

from dotenv import load_dotenv
from camel.configs import ChatGPTConfig
from camel.models import ModelFactory
from camel.messages.base import BaseMessage

from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType

from owl.utils import OwlRolePlaying
from typing import Dict, List, Optional, Tuple
from camel.logger import set_log_level, set_log_file, get_logger

import pathlib

logger = get_logger(__name__)

base_dir = pathlib.Path(__file__).parent.parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

class ExcelRolePalying(OwlRolePlaying):
    def _construct_gaia_sys_msgs(self):
        user_system_prompt = f"""
### ===== 用户规则 =====
永远不要忘记，你是用户，而我是助手。绝对不能互换角色！ 你必须始终指导我，我们的共同目标是合作完成任务。
我的职责是帮助你完成一个复杂的任务。

你必须根据我的专业能力和你的需求逐步指导我解决任务。
你的指令格式必须为：
`Instruction: [你的指令]`
其中，"Instruction" 代表一个子任务或问题。

- 你每次只能给出一个指令。
- 我必须依据你的指令提供适当的解决方案。
- 你只能指导我，而不能向我提问。

---

### 请注意
任务可能会非常复杂，不要试图一次性解决整个任务！
你必须让我一步一步地寻找答案。

以下是一些能帮助你给出更有价值指令的提示：

#### <tips>
- 我可以使用各种工具，比如：excel Toolkit 和 code Execution Toolkit 等。

- 尽管任务复杂，但答案是存在的。
  如果你发现当前方案无法找到答案，请重新规划任务，使用其他方法或工具来达到相同的目标。

- 务必提醒我验证最终答案是否正确！
  这可以通过多种方式完成，例如截图、网页分析等。

- 如果我编写了代码，请提醒我运行代码并获取结果。

- 请灵活使用代码解决问题，尤其是涉及 Excel 相关任务时。

</tips>

---

### 任务描述
当前任务如下：
<task>{self.task_prompt}</task>
永远不要忘记这个任务！

### 任务执行规则
你现在必须开始 逐步指导我完成任务。
- 不要添加任何额外的内容！
- 继续给出指令，直到你认为任务完成。

### 任务完成规则
当任务完成时，你只能回复一个单词：
`<TASK_DONE>`

在我的回答完全解决你的任务之前，绝对不要说 `<TASK_DONE>`！
        """

        assistant_system_prompt = f"""
===== 助手规则 =====
永远不要忘记，你是助手，而我是用户。绝对不能互换角色！ 绝对不能指挥我！ 你必须利用你的工具来解决我分配的任务。
我们的共同目标是合作完成一个复杂的任务。
你的职责是帮助我完成任务。

当前任务如下：
{self.task_prompt}
永远不要忘记这个任务！

我会根据你的专业能力和我的需求指导你完成任务。
每条指令通常是一个子任务或问题。

你必须充分利用你的工具，尽力解决问题，并详细解释你的解决方案。
除非我宣布任务完成，你的回答必须以以下格式开始：

Solution: [你的解决方案]

[你的解决方案] 必须具体，包含详细的解释，并提供可行的实现方案、示例或清单来解决任务。

---

### 请注意：整体任务可能会非常复杂！
以下是一些可能帮助你解决任务的重要提示：

#### <tips>
- 如果一种方法失败了，尝试其他方法。答案是存在的！
- 当涉及到查看某个excel信息的时候，你可以总是以编写python代码读入excel文件查看sheet名，列名之类的信息开始。
- 当你尝试给出python代码的时候，始终记得在最开头import相关的库，比如下面这些excel分析常见的库
```
import pandas as pd
```
- 始终验证你的最终答案是否正确！
- 请每次都从头开始编写完整代码，编写代码后，务必运行代码并获取结果！
  如果遇到错误，尝试调试代码。
- 如果工具运行失败，或者代码无法正确运行，
  绝对不要假设其返回了正确结果，并在此基础上继续推理！
  正确的做法是分析错误原因，并尝试修正！
- [重要！！！]如果你写的代码涉及到用matplotlib画图，请始终在代码开头下面这段代码：
```
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 支持中文
matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
```
- 请始终使用英文来画图，比如title, xlabel, ylabel以及其他均使用英文。
</tips>
        """

        user_sys_msg = BaseMessage.make_user_message(
            role_name=self.user_role_name, content=user_system_prompt
        )

        assistant_sys_msg = BaseMessage.make_assistant_message(
            role_name=self.assistant_role_name, content=assistant_system_prompt
        )

        return user_sys_msg, assistant_sys_msg

def run_society(
    society: OwlRolePlaying,
    round_limit: int = 15,
) -> Tuple[str, List[dict], dict]:
    overall_completion_token_count = 0
    overall_prompt_token_count = 0

    chat_history = []
    init_prompt = """
现在请给我逐步解决整个任务的指令。如果任务需要一些特定的知识，请指示我使用工具来完成任务。
        """
    input_msg = society.init_chat(init_prompt)
    for _round in range(round_limit):
        assistant_response, user_response = society.step(input_msg)
        # Check if usage info is available before accessing it
        if assistant_response.info.get("usage") and user_response.info.get("usage"):
            overall_completion_token_count += assistant_response.info["usage"].get(
                "completion_tokens", 0
            ) + user_response.info["usage"].get("completion_tokens", 0)
            overall_prompt_token_count += assistant_response.info["usage"].get(
                "prompt_tokens", 0
            ) + user_response.info["usage"].get("prompt_tokens", 0)

        # convert tool call to dict
        tool_call_records: List[dict] = []
        if assistant_response.info.get("tool_calls"):
            for tool_call in assistant_response.info["tool_calls"]:
                tool_call_records.append(tool_call.as_dict())

        _data = {
            "user": user_response.msg.content
            if hasattr(user_response, "msg") and user_response.msg
            else "",
            "assistant": assistant_response.msg.content
            if hasattr(assistant_response, "msg") and assistant_response.msg
            else "",
            "tool_calls": tool_call_records,
        }

        chat_history.append(_data)
        logger.info(
            f"Round #{_round} user_response:\n {user_response.msgs[0].content if user_response.msgs and len(user_response.msgs) > 0 else ''}"
        )
        logger.info(
            f"Round #{_round} assistant_response:\n {assistant_response.msgs[0].content if assistant_response.msgs and len(assistant_response.msgs) > 0 else ''}"
        )

        if (
            assistant_response.terminated
            or user_response.terminated
            or "TASK_DONE" in user_response.msg.content
        ):
            break

        input_msg = assistant_response.msg

    answer = chat_history[-1]["assistant"]
    token_info = {
        "completion_token_count": overall_completion_token_count,
        "prompt_token_count": overall_prompt_token_count,
    }

    return answer, chat_history, token_info

def construct_society(question: str) -> ExcelRolePalying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        OwlRolePlaying: A configured society of agents ready to address the question.
    """

    base_model_config = {
        "model_platform": ModelPlatformType.DEEPSEEK,
        "model_type": 'deepseek-chat',
        "model_config_dict": ChatGPTConfig(temperature=0.1, max_tokens=8192).as_dict(),
    }

    # Create models for different components using Azure OpenAI
    # base_model_config = {
    #     "model_platform": ModelPlatformType.AZURE,
    #     "model_type": os.getenv("AZURE_OPENAI_MODEL_TYPE"),
    #     "model_config_dict": ChatGPTConfig(temperature=0.4, max_tokens=4096).as_dict(),
    # }

    models = {
        "user": ModelFactory.create(**base_model_config),
        "assistant": ModelFactory.create(**base_model_config),
    }

    # Configure toolkits
    tools = [
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = ExcelRolePalying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="中文"
    )

    return society

def main():
    r"""Main function to run the OWL system with Azure OpenAI."""
    # Example question
    # default_task = """帮忙分析一下这个文件中各个学院的录取人数以及最高分最低分，把这些信息画到一张图上，并存到当前路径下。文件路径是`./data/admission_zh.xlsx`"""

    default_task = """帮忙分析一下`./data/admission_zh.xlsx`这个文件，请你：
    - 统计各个学院的录取人数以及最高分最低分
    - 把这些信息画到一张图上，录取人数使用柱状图，最高分最低分使用折线图
    - 把画完的图`vis_zh.png`存到当前目录下"""

    set_log_file('log.txt')

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

/owl/community_usecase/OWL Interview Preparation Assistant/app.py

#app.py
import os
import streamlit as st
import logging
import queue
import time
import sys

# Add parent directory to path for OWL imports
sys.path.append('../')

try:
    from main import research_company, generate_interview_questions, create_interview_prep_plan
except ImportError as e:
    st.error(f"Error importing functions: {e}")
    st.stop()

# Setup logging with queue to capture logs for display
log_queue = queue.Queue()

class StreamlitLogHandler(logging.Handler):
    def __init__(self, log_queue):
        super().__init__()
        self.log_queue = log_queue
        self.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
    def emit(self, record):
        log_entry = self.format(record)
        self.log_queue.put(log_entry)

# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
for handler in root_logger.handlers[:]:
    root_logger.removeHandler(handler)
root_logger.addHandler(StreamlitLogHandler(log_queue))
root_logger.addHandler(logging.StreamHandler())  # Also log to console

# Configure Streamlit page
st.set_page_config(
    page_title="Interview Prep Assistant(With OWL 🦉)",
    page_icon="🦉",
    layout="wide"
)

# Custom CSS
st.markdown("""

""", unsafe_allow_html=True)

def display_conversation(chat_history):
    """Display the conversation history in a structured format"""
    if not chat_history:
        st.info("No conversation available")
        return

    st.markdown("
", unsafe_allow_html=True)

    for idx, message in enumerate(chat_history):
        round_num = idx + 1
        st.markdown(f"
Round {round_num}
", unsafe_allow_html=True)

        # Display user message
        if "user" in message and message["user"]:
            st.markdown(f"
🧑💼 Job Seeker:
{message['user']}
", unsafe_allow_html=True)

        # Display assistant message
        if "assistant" in message and message["assistant"]:
            assistant_content = message["assistant"]
            # Remove any note about truncation for cleaner display
            if "[Note: This conversation was limited" in assistant_content:
                assistant_content = assistant_content.replace("[Note: This conversation was limited to maintain response quality. The complete thought process is available in the logs.]", "")

            st.markdown(f"
🦉 Interview Coach:
{assistant_content}
", unsafe_allow_html=True)

        # Display tool calls if any
        if "tool_calls" in message and message["tool_calls"]:
            for tool in message["tool_calls"]:
                tool_name = tool.get('name', 'Unknown Tool')
                st.markdown(f"
🔧 Tool Used: {tool_name}
", unsafe_allow_html=True)

    st.markdown("
", unsafe_allow_html=True)

def display_metrics(duration, token_count, num_rounds):
    """Display metrics in a visually appealing format"""
    st.markdown("
", unsafe_allow_html=True)

    # Time taken
    st.markdown(f"""

{duration:.1f}s

Execution Time

    """, unsafe_allow_html=True)

    # Token usage
    completion_tokens = token_count.get('completion_token_count', 0)
    prompt_tokens = token_count.get('prompt_token_count', 0)
    total_tokens = completion_tokens + prompt_tokens

    st.markdown(f"""

{total_tokens:,}

Total Tokens

    """, unsafe_allow_html=True)

    # Conversation rounds
    st.markdown(f"""

{num_rounds}

Conversation Rounds

    """, unsafe_allow_html=True)

    st.markdown("
", unsafe_allow_html=True)

def get_logs():
    """Retrieve logs from the queue"""
    logs = []
    while not log_queue.empty():
        try:
            logs.append(log_queue.get_nowait())
        except queue.Empty:
            break
    return logs

def main():
    # Header
    st.markdown("
🦉 Interview Preparation Assistant
", unsafe_allow_html=True)
    st.markdown("
Powered by multi-agent AI collaboration
", unsafe_allow_html=True)

    # Input section
    with st.container():
        col1, col2 = st.columns(2)
        with col1:
            job_role = st.text_input("Job Role", "Machine Learning Engineer")
        with col2:
            company_name = st.text_input("Company Name", "Google")

    # Main functionality tabs
    tab1, tab2, tab3, tab4 = st.tabs(["Company Research", "Interview Questions", "Preparation Plan", "System Logs"])

    # Tab 1: Company Research
    with tab1:
        st.header("🔍 Company Research")
        st.write("Get detailed insights about the company to help with your interview preparation.")

        if st.button("Research Company", use_container_width=True):
            with st.spinner():
                # Display running indicator
                status = st.empty()
                status.markdown("
🔄 Researching company information...
", unsafe_allow_html=True)

                # Progress bar
                progress = st.progress(0)

                # Progress callback
                def update_progress(current_round, max_rounds):
                    progress_value = min(current_round / max_rounds, 0.95)
                    progress.progress(progress_value)
                    status.markdown(f"
🔄 Processing conversation round {current_round}/{max_rounds}...
", unsafe_allow_html=True)

                # Execute research
                try:
                    start_time = time.time()
                    result = research_company(
                        company_name=company_name,
                        detailed=True,  # Always use detailed mode
                        limited_searches=False,  # Don't limit searches
                        progress_callback=update_progress
                    )
                    duration = time.time() - start_time

                    # Update progress to complete
                    progress.progress(1.0)
                    status.markdown("
✅ Research completed!
", unsafe_allow_html=True)

                    # Display metrics
                    display_metrics(
                        duration=duration,
                        token_count=result["token_count"],
                        num_rounds=len(result["chat_history"])
                    )

                    # Display final answer
                    st.subheader("📝 Research Results")
                    st.markdown(f"
{result['answer']}
", unsafe_allow_html=True)

                    # Display conversation
                    st.subheader("💬 Conversation Process")
                    display_conversation(result["chat_history"])

                except Exception as e:
                    st.error(f"Error: {str(e)}")
                    logging.exception("Error in company research")

    # Tab 2: Interview Questions
    with tab2:
        st.header("❓ Interview Questions")
        st.write("Generate tailored interview questions for your target role and company.")

        # Question type selector (adds interactivity but doesn't change behavior for now)
        question_type = st.radio(
            "Question Type",
            ["Technical", "Behavioral", "Company-Specific", "All"],
            horizontal=True
        )

        if st.button("Generate Questions", use_container_width=True):
            with st.spinner():
                # Display running indicator
                status = st.empty()
                status.markdown("
🔄 Creating interview questions...
", unsafe_allow_html=True)

                # Progress bar
                progress = st.progress(0)

                # Progress callback
                def update_progress(current_round, max_rounds):
                    progress_value = min(current_round / max_rounds, 0.95)
                    progress.progress(progress_value)
                    status.markdown(f"
🔄 Processing conversation round {current_round}/{max_rounds}...
", unsafe_allow_html=True)

                # Execute question generation
                try:
                    start_time = time.time()
                    result = generate_interview_questions(
                        job_role=job_role,
                        company_name=company_name,
                        detailed=True,  # Always use detailed mode
                        limited_searches=False,  # Don't limit searches
                        progress_callback=update_progress
                    )
                    duration = time.time() - start_time

                    # Update progress to complete
                    progress.progress(1.0)
                    status.markdown("
✅ Questions generated!
", unsafe_allow_html=True)

                    # Display metrics
                    display_metrics(
                        duration=duration,
                        token_count=result["token_count"],
                        num_rounds=len(result["chat_history"])
                    )

                    # Display final answer
                    st.subheader("📝 Generated Questions")
                    st.markdown(f"
{result['answer']}
", unsafe_allow_html=True)

                    # Display conversation
                    st.subheader("💬 Conversation Process")
                    display_conversation(result["chat_history"])

                except Exception as e:
                    st.error(f"Error: {str(e)}")
                    logging.exception("Error in question generation")

    # Tab 3: Preparation Plan
    with tab3:
        st.header("📋 Interview Preparation Plan")
        st.write("Create a comprehensive step-by-step plan to prepare for your interview.")

        if st.button("Create Preparation Plan", use_container_width=True):
            with st.spinner():
                # Display running indicator
                status = st.empty()
                status.markdown("
🔄 Creating preparation plan...
", unsafe_allow_html=True)

                # Progress bar
                progress = st.progress(0)

                # Progress callback
                def update_progress(current_round, max_rounds):
                    progress_value = min(current_round / max_rounds, 0.95)
                    progress.progress(progress_value)
                    status.markdown(f"
🔄 Processing conversation round {current_round}/{max_rounds}...
", unsafe_allow_html=True)

                # Execute plan creation
                try:
                    start_time = time.time()
                    result = create_interview_prep_plan(
                        job_role=job_role,
                        company_name=company_name,
                        detailed=True,  # Always use detailed mode
                        limited_searches=False,  # Don't limit searches
                        progress_callback=update_progress
                    )
                    duration = time.time() - start_time

                    # Update progress to complete
                    progress.progress(1.0)
                    status.markdown("
✅ Plan created!
", unsafe_allow_html=True)

                    # Display metrics
                    display_metrics(
                        duration=duration,
                        token_count=result["token_count"],
                        num_rounds=len(result["chat_history"])
                    )

                    # Display final answer
                    st.subheader("📝 Preparation Plan")
                    st.markdown(f"
{result['answer']}
", unsafe_allow_html=True)

                    # Display conversation
                    st.subheader("💬 Conversation Process")
                    display_conversation(result["chat_history"])

                except Exception as e:
                    st.error(f"Error: {str(e)}")
                    logging.exception("Error in preparation plan creation")

    # Tab 4: System Logs
    with tab4:
        st.header("🔧 System Logs")
        st.write("View detailed system logs for debugging.")

        logs_container = st.empty()

        # Get and display logs
        logs = get_logs()
        if logs:
            logs_container.code("\n".join(logs))
        else:
            logs_container.info("No logs available yet.")

        # Manual refresh button
        if st.button("Refresh Logs"):
            logs = get_logs()
            if logs:
                logs_container.code("\n".join(logs))
            else:
                logs_container.info("No logs available yet.")

        # Auto-refresh toggle
        auto_refresh = st.checkbox("Auto-refresh logs", value=True)
        if auto_refresh:
            st.markdown(
                """

                """,
                unsafe_allow_html=True
            )

if __name__ == "__main__":
    try:
        logging.info("Starting Interview Preparation Assistant application")
        main()
    except Exception as e:
        st.error(f"Application error: {str(e)}")
        logging.exception("Application error")

/owl/community_usecase/OWL Interview Preparation Assistant/logging_utils.py

import os
import logging
import time
import functools
import inspect
import re
from typing import Dict, Any, List, Tuple, Callable, Optional
import queue

# Create a singleton log queue that can be shared between modules
class LogQueueSingleton:
    _instance = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = queue.Queue()
        return cls._instance

# Custom logging wrapper for tools
def log_tool_usage(func):
    """
    Decorator to log when a tool is being used.
    """
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        tool_name = func.__name__
        logging.info(f"🔧 TOOL TRIGGERED: {tool_name}")
        try:
            # Sanitize arguments to avoid logging sensitive info
            safe_args = sanitize_args(args)
            safe_kwargs = {k: sanitize_value(v) for k, v in kwargs.items()}
            logging.info(f"🔍 TOOL ARGS: {tool_name} called with {len(safe_kwargs)} parameters")

            result = await func(*args, **kwargs)

            # Log completion but not the actual result content (might be large or sensitive)
            logging.info(f"✅ TOOL COMPLETED: {tool_name}")
            return result
        except Exception as e:
            logging.error(f"❌ TOOL ERROR: {tool_name} - {str(e)}")
            raise
    return wrapper

# Non-async version for synchronous functions
def log_tool_usage_sync(func):
    """
    Decorator to log when a synchronous tool is being used.
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        tool_name = func.__name__
        logging.info(f"🔧 TOOL TRIGGERED: {tool_name}")
        try:
            # Sanitize arguments to avoid logging sensitive info
            safe_args = sanitize_args(args)
            safe_kwargs = {k: sanitize_value(v) for k, v in kwargs.items()}
            logging.info(f"🔍 TOOL ARGS: {tool_name} called with {len(safe_kwargs)} parameters")

            result = func(*args, **kwargs)

            # Log completion but not the actual result content (might be large or sensitive)
            logging.info(f"✅ TOOL COMPLETED: {tool_name}")
            return result
        except Exception as e:
            logging.error(f"❌ TOOL ERROR: {tool_name} - {str(e)}")
            raise
    return wrapper

def sanitize_args(args):
    """Sanitize arguments for logging to avoid sensitive data."""
    safe_args = []
    for arg in args:
        safe_args.append(sanitize_value(arg))
    return safe_args

def sanitize_value(value):
    """Sanitize a value for logging."""
    if isinstance(value, str):
        if len(value) > 50:
            return value[:47] + "..."
        return value
    elif isinstance(value, (list, tuple)):
        return f"{type(value).__name__} with {len(value)} items"
    elif isinstance(value, dict):
        return f"dict with {len(value)} items"
    else:
        return f"{type(value).__name__}"

class LoggingToolkitWrapper:
    """
    Wrapper class to add logging to toolkit methods.
    """
    def __init__(self, toolkit):
        self.toolkit = toolkit
        self.toolkit_name = toolkit.__class__.__name__
        logging.info(f"📦 TOOLKIT INITIALIZED: {self.toolkit_name}")

    def __getattr__(self, name):
        attr = getattr(self.toolkit, name)

        if callable(attr) and not name.startswith('_'):
            if inspect.iscoroutinefunction(attr):
                # It's an async function, wrap it with our async decorator
                return log_tool_usage(attr)
            else:
                # For non-async functions
                @functools.wraps(attr)
                def wrapper(*args, **kwargs):
                    logging.info(f"🔧 TOOL TRIGGERED: {self.toolkit_name}.{name}")
                    try:
                        # Sanitize arguments to avoid logging sensitive info
                        safe_args = sanitize_args(args)
                        safe_kwargs = {k: sanitize_value(v) for k, v in kwargs.items()}
                        logging.info(f"🔍 TOOL ARGS: {name} called with {len(safe_kwargs)} parameters")

                        result = attr(*args, **kwargs)

                        logging.info(f"✅ TOOL COMPLETED: {self.toolkit_name}.{name}")
                        return result
                    except Exception as e:
                        logging.error(f"❌ TOOL ERROR: {self.toolkit_name}.{name} - {str(e)}")
                        raise
                return wrapper

        return attr

def wrap_toolkits(toolkits_list):
    """
    Wrap a list of toolkits with logging functionality.
    """
    wrapped_toolkits = []
    for toolkit in toolkits_list:
        wrapped_toolkits.append(LoggingToolkitWrapper(toolkit))
    return wrapped_toolkits
# Find this function in logging_utils.py and replace it with this corrected version

# Enhanced run_society function with logging
def enhanced_run_society(society, verbose=True):
    """
    Enhanced wrapper around the OWL run_society function with detailed logging.
    """
    from owl.utils import run_society as original_run_society

    # Log the society setup
    user_role = getattr(society, 'user_role_name', 'User')
    assistant_role = getattr(society, 'assistant_role_name', 'Assistant')

    logging.info(f"🚀 STARTING AGENT SOCIETY: {user_role} & {assistant_role}")
    logging.info(f"📝 TASK: {society.task_prompt[:100]}...")

    # Log agent initialization
    logging.info(f"🤖 INITIALIZING AGENT: {assistant_role}")

    # Add hooks to log message exchanges if possible
    original_send_message = None
    if hasattr(society, 'assistant_agent') and hasattr(society.assistant_agent, 'send_message'):
        original_send_message = society.assistant_agent.send_message

        @functools.wraps(original_send_message)
        def logged_send_message(*args, **kwargs):
            logging.info(f"💬 AGENT MESSAGE: {assistant_role} is processing...")
            result = original_send_message(*args, **kwargs)
            logging.info(f"📨 AGENT RESPONSE RECEIVED from {assistant_role}")
            return result

        society.assistant_agent.send_message = logged_send_message

    # Try to log tool usage if possible
    if hasattr(society, 'assistant_agent') and hasattr(society.assistant_agent, 'tools'):
        tools = getattr(society.assistant_agent, 'tools', [])
        logging.info(f"🧰 AGENT HAS {len(tools)} TOOLS AVAILABLE")

        # Attempt to wrap each tool with logging
        for i, tool in enumerate(tools):
            if callable(tool):
                tool_name = getattr(tool, '__name__', f"tool_{i}")
                logging.info(f"🔧 TOOL AVAILABLE: {tool_name}")

    # Run the original function
    start_time = time.time()
    try:
        logging.info(f"⏳ RUNNING SOCIETY...")
        # Remove the verbose parameter from the call to original_run_society
        answer, chat_history, token_count = original_run_society(society)
        end_time = time.time()
        duration = end_time - start_time

        # Log prompt and completion tokens separately if available
        if isinstance(token_count, dict):
            prompt_tokens = token_count.get('prompt_token_count', 0)
            completion_tokens = token_count.get('completion_token_count', 0)
            logging.info(f"💰 TOKEN USAGE: Prompt={prompt_tokens}, Completion={completion_tokens}, Total={prompt_tokens + completion_tokens}")
        else:
            logging.info(f"💰 TOKEN USAGE: {token_count}")

        logging.info(f"✅ AGENT SOCIETY COMPLETED: Duration {duration:.2f}s")

        return answer, chat_history, token_count
    except Exception as e:
        logging.error(f"❌ AGENT SOCIETY ERROR: {str(e)}")
        raise
    finally:
        # Restore original method if we modified it
        if original_send_message and hasattr(society, 'assistant_agent'):
            society.assistant_agent.send_message = original_send_message

# Function to sanitize logs to avoid exposing sensitive information
def sanitize_log(log_message):
    """
    Sanitize log messages to avoid exposing sensitive information like IPs.
    """
    # Simple IP address pattern matching
    ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
    sanitized = re.sub(ip_pattern, '[REDACTED_IP]', log_message)

    # Redact API keys (common patterns)
    api_key_pattern = r'(api[_-]?key|apikey|key|token)["\']?\s*[:=]\s*["\']?([a-zA-Z0-9]{20,})["\']?'
    sanitized = re.sub(api_key_pattern, r'\1: [REDACTED_API_KEY]', sanitized, flags=re.IGNORECASE)

    # Redact URLs with authentication information
    url_auth_pattern = r'(https?://)([^:@/]+:[^@/]+@)([^\s/]+)'
    sanitized = re.sub(url_auth_pattern, r'\1[REDACTED_AUTH]@\3', sanitized)

    return sanitized

# Enhanced StreamlitLogHandler that sanitizes logs
class EnhancedStreamlitLogHandler(logging.Handler):
    def __init__(self, log_queue):
        super().__init__()
        self.log_queue = log_queue
        self.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))

    def emit(self, record):
        log_entry = self.format(record)
        # Sanitize the log to remove sensitive information
        sanitized_log = sanitize_log(log_entry)
        self.log_queue.put(sanitized_log)

# Add logging to specific OWL functions if possible
# Add this updated function to logging_utils.py

# Add logging to specific OWL functions if possible
def patch_owl_logging():
    """Try to patch specific OWL functions to add logging."""
    try:
        from owl import utils

        # If run_society exists in utils, patch it to log
        if hasattr(utils, 'run_society'):
            original_run = utils.run_society

            def logged_run_society(*args, **kwargs):
                logging.info("🦉 OWL run_society called")
                try:
                    result = original_run(*args, **kwargs)
                    logging.info("🦉 OWL run_society completed")
                    return result
                except Exception as e:
                    logging.error(f"🦉 OWL run_society error: {str(e)}")
                    raise

            # Replace the original function
            utils.run_society = logged_run_society
            logging.info("🦉 OWL run_society patched with logging")

        return True
    except ImportError:
        logging.warning("⚠ Could not patch OWL logging - module not found")
        return False
    except Exception as e:
        logging.warning(f"⚠ Error patching OWL logging: {str(e)}")
        return False

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Import from the correct module path
from utils import run_society
import os
import gradio as gr
from typing import Tuple, List, Dict
import importlib
from dotenv import load_dotenv, set_key, find_dotenv, unset_key

os.environ["PYTHONIOENCODING"] = "utf-8"

# Enhanced CSS with navigation bar and additional styling
custom_css = """
:root {
    --primary-color: #4a89dc;
    --secondary-color: #5d9cec;
    --accent-color: #7baaf7;
    --light-bg: #f8f9fa;
    --border-color: #e4e9f0;
    --text-muted: #8a9aae;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
}

.navbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 30px;
    background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
    color: white;
    border-radius: 10px 10px 0 0;
    margin-bottom: 0;
    box-shadow: 0 2px 10px rgba(74, 137, 220, 0.15);
}

.navbar-logo {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 1.5em;
    font-weight: bold;
}

.navbar-menu {
    display: flex;
    gap: 20px;
}

/* Navbar styles moved to a more specific section below */

.header {
    text-align: center;
    margin-bottom: 20px;
    background: linear-gradient(180deg, var(--secondary-color), var(--accent-color));
    color: white;
    padding: 40px 20px;
    border-radius: 0 0 10px 10px;
    box-shadow: 0 4px 6px rgba(93, 156, 236, 0.12);
}

.module-info {
    background-color: var(--light-bg);
    border-left: 5px solid var(--primary-color);
    padding: 10px 15px;
    margin-top: 10px;
    border-radius: 5px;
    font-size: 0.9em;
}

.answer-box {
    background-color: var(--light-bg);
    border-left: 5px solid var(--secondary-color);
    padding: 15px;
    margin-bottom: 20px;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.token-count {
    background-color: #e9ecef;
    padding: 10px;
    border-radius: 5px;
    text-align: center;
    font-weight: bold;
    margin-bottom: 20px;
}

.chat-container {
    border: 1px solid var(--border-color);
    border-radius: 5px;
    max-height: 500px;
    overflow-y: auto;
    margin-bottom: 20px;
}

.footer {
    text-align: center;
    margin-top: 20px;
    color: var(--text-muted);
    font-size: 0.9em;
    padding: 20px;
    border-top: 1px solid var(--border-color);
}

.features-section {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin: 20px 0;
}

@media (max-width: 1200px) {
    .features-section {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 768px) {
    .features-section {
        grid-template-columns: 1fr;
    }
}

.feature-card {
    background-color: white;
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 2px 8px rgba(74, 137, 220, 0.08);
    transition: transform 0.3s, box-shadow 0.3s;
    height: 100%;
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(228, 233, 240, 0.6);
}

.feature-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 15px rgba(74, 137, 220, 0.15);
    border-color: rgba(93, 156, 236, 0.3);
}

.feature-icon {
    font-size: 2em;
    color: var(--primary-color);
    margin-bottom: 10px;
    text-shadow: 0 1px 2px rgba(74, 137, 220, 0.1);
}

.feature-card h3 {
    margin-top: 10px;
    margin-bottom: 10px;
}

.feature-card p {
    flex-grow: 1;
    font-size: 0.95em;
    line-height: 1.5;
}

/* Navbar link styles - ensuring consistent colors */
.navbar-menu a {
    color: #ffffff !important;
    text-decoration: none;
    padding: 5px 10px;
    border-radius: 5px;
    transition: background-color 0.3s, color 0.3s;
    font-weight: 500;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.navbar-menu a:hover {
    background-color: rgba(255, 255, 255, 0.15);
    color: #ffffff !important;
}

/* Improved button and input styles */
button.primary {
    background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
    transition: all 0.3s;
}

button.primary:hover {
    background: linear-gradient(90deg, var(--secondary-color), var(--primary-color));
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

.env-section {
    background-color: var(--light-bg);
    border-radius: 8px;
    padding: 20px;
    margin: 20px 0;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.env-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 15px;
}

.env-table th, .env-table td {
    padding: 10px;
    border: 1px solid var(--border-color);
}

.env-table th {
    background-color: var(--primary-color);
    color: white;
    text-align: left;
}

.env-table tr:nth-child(even) {
    background-color: rgba(0, 0, 0, 0.02);
}

.env-actions {
    display: flex;
    gap: 10px;
}

.env-var-input {
    margin-bottom: 15px;
}

.env-save-status {
    margin-top: 15px;
    padding: 10px;
    border-radius: 5px;
}

.success {
    background-color: #d4edda;
    color: #155724;
    border: 1px solid #c3e6cb;
}

.error {
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
}
"""

# Dictionary containing module descriptions
MODULE_DESCRIPTIONS = {
    "run": "默认模式：使用OpenAI模型的默认的智能体协作模式，适合大多数任务。",
    "run_mini": "使用使用OpenAI模型最小化配置处理任务",
    "run_deepseek_zh": "使用deepseek模型处理中文任务",
    "run_terminal_zh": "终端模式：可执行命令行操作，支持网络搜索、文件处理等功能。适合需要系统交互的任务，使用OpenAI模型",
    "run_gaia_roleplaying": "GAIA基准测试实现，用于评估Agent能力",
    "run_openai_compatible_model": "使用openai兼容模型处理任务",
    "run_ollama": "使用本地ollama模型处理任务",
    "run_qwen_mini_zh": "使用qwen模型最小化配置处理任务",
    "run_qwen_zh": "使用qwen模型处理任务",
}

# 默认环境变量模板
DEFAULT_ENV_TEMPLATE = """# MODEL & API (See https://docs.camel-ai.org/key_modules/models.html#)

# OPENAI API
# OPENAI_API_KEY= ""
# OPENAI_API_BASE_URL=""

# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
# QWEN_API_KEY=""

# DeepSeek API (https://platform.deepseek.com/api_keys)
# DEEPSEEK_API_KEY=""

#===========================================
# Tools & Services API
#===========================================

# Google Search API (https://developers.google.com/custom-search/v1/overview)
GOOGLE_API_KEY=""
SEARCH_ENGINE_ID=""

# Hugging Face API (https://huggingface.co/join)
HF_TOKEN=""

# Chunkr API (https://chunkr.ai/)
CHUNKR_API_KEY=""

# Firecrawl API (https://www.firecrawl.dev/)
FIRECRAWL_API_KEY=""
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
"""

def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]:
    """将聊天历史格式化为Gradio聊天组件可接受的格式

    Args:
        chat_history: 原始聊天历史

    Returns:
        List[List[str]]: 格式化后的聊天历史
    """
    formatted_history = []
    for message in chat_history:
        user_msg = message.get("user", "")
        assistant_msg = message.get("assistant", "")

        if user_msg:
            formatted_history.append([user_msg, None])
        if assistant_msg and formatted_history:
            formatted_history[-1][1] = assistant_msg
        elif assistant_msg:
            formatted_history.append([None, assistant_msg])

    return formatted_history

def validate_input(question: str) -> bool:
    """验证用户输入是否有效

    Args:
        question: 用户问题

    Returns:
        bool: 输入是否有效
    """
    # 检查输入是否为空或只包含空格
    if not question or question.strip() == "":
        return False
    return True

def run_owl(
    question: str, example_module: str
) -> Tuple[str, List[List[str]], str, str]:
    """运行OWL系统并返回结果

    Args:
        question: 用户问题
        example_module: 要导入的示例模块名（如 "run_terminal_zh" 或 "run_deep"）

    Returns:
        Tuple[...]: 回答、聊天历史、令牌计数、状态
    """
    # 验证输入
    if not validate_input(question):
        return ("请输入有效的问题", [], "0", "❌ 错误: 输入无效")

    try:
        # 确保环境变量已加载
        load_dotenv(find_dotenv(), override=True)
        # 检查模块是否在MODULE_DESCRIPTIONS中
        if example_module not in MODULE_DESCRIPTIONS:
            return (
                f"所选模块 '{example_module}' 不受支持",
                [],
                "0",
                "❌ 错误: 不支持的模块",
            )

        # 动态导入目标模块
        module_path = f"owl.examples.{example_module}"
        try:
            module = importlib.import_module(module_path)
        except ImportError as ie:
            return (
                f"无法导入模块: {module_path}",
                [],
                "0",
                f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}",
            )
        except Exception as e:
            return (f"导入模块时发生错误: {module_path}", [], "0", f"❌ 错误: {str(e)}")

        # 检查是否包含construct_society函数
        if not hasattr(module, "construct_society"):
            return (
                f"模块 {module_path} 中未找到 construct_society 函数",
                [],
                "0",
                "❌ 错误: 模块接口不兼容",
            )

        # 构建社会模拟
        try:
            society = module.construct_society(question)
        except Exception as e:
            return (
                f"构建社会模拟时发生错误: {str(e)}",
                [],
                "0",
                f"❌ 错误: 构建失败 - {str(e)}",
            )

        # 运行社会模拟
        try:
            answer, chat_history, token_info = run_society(society)
        except Exception as e:
            return (
                f"运行社会模拟时发生错误: {str(e)}",
                [],
                "0",
                f"❌ 错误: 运行失败 - {str(e)}",
            )

        # 格式化聊天历史
        try:
            formatted_chat_history = format_chat_history(chat_history)
        except Exception:
            # 如果格式化失败，返回空历史记录但继续处理
            formatted_chat_history = []

        # 安全地获取令牌计数
        if not isinstance(token_info, dict):
            token_info = {}

        completion_tokens = token_info.get("completion_token_count", 0)
        prompt_tokens = token_info.get("prompt_token_count", 0)
        total_tokens = completion_tokens + prompt_tokens

        return (
            answer,
            formatted_chat_history,
            f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
            "✅ 成功完成",
        )

    except Exception as e:
        return (f"发生错误: {str(e)}", [], "0", f"❌ 错误: {str(e)}")

def update_module_description(module_name: str) -> str:
    """返回所选模块的描述"""
    return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")

# 环境变量管理功能
def init_env_file():
    """初始化.env文件如果不存在"""
    dotenv_path = find_dotenv()
    if not dotenv_path:
        with open(".env", "w") as f:
            f.write(DEFAULT_ENV_TEMPLATE)
        dotenv_path = find_dotenv()
    return dotenv_path

def load_env_vars():
    """加载环境变量并返回字典格式"""
    dotenv_path = init_env_file()
    load_dotenv(dotenv_path, override=True)

    env_vars = {}
    with open(dotenv_path, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                if "=" in line:
                    key, value = line.split("=", 1)
                    env_vars[key.strip()] = value.strip().strip("\"'")

    return env_vars

def save_env_vars(env_vars):
    """保存环境变量到.env文件"""
    try:
        dotenv_path = init_env_file()

        # 保存每个环境变量
        for key, value in env_vars.items():
            if key and key.strip():  # 确保键不为空
                set_key(dotenv_path, key.strip(), value.strip())

        # 重新加载环境变量以确保生效
        load_dotenv(dotenv_path, override=True)

        return True, "环境变量已成功保存！"
    except Exception as e:
        return False, f"保存环境变量时出错: {str(e)}"

def add_env_var(key, value):
    """添加或更新单个环境变量"""
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        dotenv_path = init_env_file()
        set_key(dotenv_path, key.strip(), value.strip())
        load_dotenv(dotenv_path, override=True)

        return True, f"环境变量 {key} 已成功添加/更新！"
    except Exception as e:
        return False, f"添加环境变量时出错: {str(e)}"

def delete_env_var(key):
    """删除环境变量"""
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        dotenv_path = init_env_file()
        unset_key(dotenv_path, key.strip())

        # 从当前进程环境中也删除
        if key in os.environ:
            del os.environ[key]

        return True, f"环境变量 {key} 已成功删除！"
    except Exception as e:
        return False, f"删除环境变量时出错: {str(e)}"

def mask_sensitive_value(key: str, value: str) -> str:
    """对敏感信息进行掩码处理

    Args:
        key: 环境变量名
        value: 环境变量值

    Returns:
        str: 处理后的值
    """
    # 定义需要掩码的敏感关键词
    sensitive_keywords = ["key", "token", "secret", "password", "api"]

    # 检查是否包含敏感关键词（不区分大小写）
    is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords)

    if is_sensitive and value:
        # 如果是敏感信息且有值，则显示掩码
        return "*" * 8
    return value

def update_env_table():
    """更新环境变量表格显示，对敏感信息进行掩码处理"""
    env_vars = load_env_vars()
    # 对敏感值进行掩码处理
    masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()]
    return masked_env_vars

def create_ui():
    """创建增强版Gradio界面"""
    with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="blue")) as app:
        with gr.Column(elem_classes="container"):
            gr.HTML("""

                        🦉 OWL 多智能体协作系统

                        首页
                        环境设置
                        加入交流群
                        OWL文档
                        CAMEL框架
                        CAMEL-AI官网

我们的愿景是彻底改变AI代理协作解决现实世界任务的方式。通过利用动态代理交互，OWL能够在多个领域实现更自然、高效和稳健的任务自动化。

            """)

            with gr.Row(elem_id="features"):
                gr.HTML("""

🔍

实时信息检索

利用维基百科、谷歌搜索和其他在线资源获取最新信息。

📹

多模态处理

支持处理互联网或本地的视频、图像和音频数据。

🌐

浏览器自动化

使用Playwright框架模拟浏览器交互，实现网页操作自动化。

📄

文档解析

从各种文档格式中提取内容，并转换为易于处理的格式。

💻

代码执行

使用解释器编写和运行Python代码，实现自动化数据处理。

🧰

内置工具包

提供丰富的工具包，支持搜索、数据分析、代码执行等多种功能。

🔑

环境变量管理

便捷管理API密钥和环境配置，安全存储敏感信息。

            """)

            with gr.Row():
                with gr.Column(scale=2):
                    question_input = gr.Textbox(
                        lines=5,
                        placeholder="请输入您的问题...",
                        label="问题",
                        elem_id="question_input",
                        show_copy_button=True,
                    )

                    # 增强版模块选择下拉菜单
                    # 只包含MODULE_DESCRIPTIONS中定义的模块
                    module_dropdown = gr.Dropdown(
                        choices=list(MODULE_DESCRIPTIONS.keys()),
                        value="run_terminal_zh",
                        label="选择功能模块",
                        interactive=True,
                    )

                    # 模块描述文本框
                    module_description = gr.Textbox(
                        value=MODULE_DESCRIPTIONS["run_terminal_zh"],
                        label="模块描述",
                        interactive=False,
                        elem_classes="module-info",
                    )

                    run_button = gr.Button(
                        "运行", variant="primary", elem_classes="primary"
                    )

                with gr.Column(scale=1):
                    gr.Markdown("""
                    ### 使用指南

                    1. **选择适合的模块**：根据您的任务需求选择合适的功能模块
                    2. **详细描述您的需求**：在输入框中清晰描述您的问题或任务
                    3. **启动智能处理**：点击"运行"按钮开始多智能体协作处理
                    4. **查看结果**：在下方标签页查看回答和完整对话历史

                    > **高级提示**: 对于复杂任务，可以尝试指定具体步骤和预期结果
                    """)

            status_output = gr.Textbox(label="状态", interactive=False)

            with gr.Tabs():
                with gr.TabItem("回答"):
                    answer_output = gr.Textbox(
                        label="回答", lines=10, elem_classes="answer-box"
                    )

                with gr.TabItem("对话历史"):
                    chat_output = gr.Chatbot(
                        label="完整对话记录", elem_classes="chat-container", height=500
                    )

            token_count_output = gr.Textbox(
                label="令牌计数", interactive=False, elem_classes="token-count"
            )

            # 示例问题
            examples = [
                "打开百度搜索，总结一下camel-ai的camel框架的github star、fork数目等，并把数字用plot包写成python文件保存到本地，用本地终端执行python文件显示图出来给我",
                "请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者数量和最近的活跃度。",
                "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
                "写一个hello world的python文件，保存到本地",
            ]

            gr.Examples(examples=examples, inputs=question_input)
            # 新增: 环境变量管理选项卡
            with gr.TabItem("环境变量管理", id="env-settings"):
                gr.Markdown("""
                    ## 环境变量管理

                    在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中，确保您的API密钥安全存储且不会上传到网络。
                    """)

                # 环境变量表格
                env_table = gr.Dataframe(
                    headers=["变量名", "值"],
                    datatype=["str", "str"],
                    row_count=10,
                    col_count=(2, "fixed"),
                    value=update_env_table,
                    label="当前环境变量",
                    interactive=False,
                )

                with gr.Row():
                    with gr.Column(scale=1):
                        new_env_key = gr.Textbox(
                            label="变量名", placeholder="例如: OPENAI_API_KEY"
                        )
                    with gr.Column(scale=2):
                        new_env_value = gr.Textbox(
                            label="值", placeholder="输入API密钥或其他配置值"
                        )

                with gr.Row():
                    add_env_button = gr.Button("添加/更新变量", variant="primary")
                    refresh_button = gr.Button("刷新变量列表")
                    delete_env_button = gr.Button("删除选定变量", variant="stop")

                env_status = gr.Textbox(label="状态", interactive=False)

                # 变量选择器（用于删除）
                env_var_to_delete = gr.Dropdown(
                    choices=[], label="选择要删除的变量", interactive=True
                )

                # 更新变量选择器的选项
                def update_delete_dropdown():
                    env_vars = load_env_vars()
                    return gr.Dropdown.update(choices=list(env_vars.keys()))

                # 连接事件处理函数
                add_env_button.click(
                    fn=lambda k, v: add_env_var(k, v),
                    inputs=[new_env_key, new_env_value],
                    outputs=[env_status],
                ).then(fn=update_env_table, outputs=[env_table]).then(
                    fn=update_delete_dropdown, outputs=[env_var_to_delete]
                ).then(
                    fn=lambda: ("", ""),  # 修改为返回两个空字符串的元组
                    outputs=[new_env_key, new_env_value],
                )

                refresh_button.click(fn=update_env_table, outputs=[env_table]).then(
                    fn=update_delete_dropdown, outputs=[env_var_to_delete]
                )

                delete_env_button.click(
                    fn=lambda k: delete_env_var(k),
                    inputs=[env_var_to_delete],
                    outputs=[env_status],
                ).then(fn=update_env_table, outputs=[env_table]).then(
                    fn=update_delete_dropdown, outputs=[env_var_to_delete]
                )

            gr.HTML("""

关于 OWL 多智能体协作系统

OWL 是一个基于CAMEL框架开发的先进多智能体协作系统，旨在通过智能体协作解决复杂问题。

© 2025 CAMEL-AI.org. 基于Apache License 2.0开源协议

GitHub

            """)

            # 设置事件处理
            run_button.click(
                fn=run_owl,
                inputs=[question_input, module_dropdown],
                outputs=[answer_output, chat_output, token_count_output, status_output],
            )

            # 模块选择更新描述
            module_dropdown.change(
                fn=update_module_description,
                inputs=module_dropdown,
                outputs=module_description,
            )

    return app

# 主函数
def main():
    try:
        # 初始化.env文件（如果不存在）
        init_env_file()
        app = create_ui()
        app.launch(share=False)
    except Exception as e:
        print(f"启动应用程序时发生错误: {str(e)}")
        import traceback

        traceback.print_exc()

if __name__ == "__main__":
    main()

import os
from camel.agents.chat_agent import ChatAgent
from pydantic import BaseModel
from typing import List
from camel.agents import ChatAgent
from camel.models import ModelFactory
from camel.types import ModelPlatformType, ModelType
from tools.sec_tools import SECToolkit
from camel.toolkits import FunctionTool
from prompts import  get_sec_system_prompt

def create_sec_agent() -> ChatAgent:
    # Define the model, here in this case we use gpt-4o-mini
    model = ModelFactory.create(
        model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
        model_type="glm-4-flash",
        api_key=os.getenv("ZHIPUAI_API_KEY"),
        url=os.getenv("ZHIPUAI_API_BASE_URL"),
        model_config_dict={"temperature": 0},
    )
    # Create agent with structured output
    agent = ChatAgent(
    system_message=get_sec_system_prompt(),
    model=model,
    tools = [
        *SECToolkit().get_tools()
    ]
    )
    return agent

def get_sec_summary_for_company(company_stock_name: str) -> str:
    r"""Retrieve and analyze SEC filing information for a specified company and generate a comprehensive analysis report.

    This function retrieves relevant documents from the SEC database using the company's stock symbol,
    analyzes key financial metrics, business developments, risk conditions, market position,
    and generates a structured analysis report.

    Args:
        company_stock_name (str): Company stock symbol (e.g., 'AAPL' for Apple Inc.)

    Returns:
        str: A comprehensive analysis report containing:
            - Key financial metrics from quarterly and annual reports
            - Important business developments, risks, and market position
            - Management discussion and strategic plans
            - Material changes in operations and financial conditions
            - Important regulatory disclosures and compliance matters

            The report is formatted as structured text, limited to 10,000 words,
            highlighting information most relevant and impactful to investors.
    """

    # Define Summary Prompt
    usr_msg = f"""Please search and analyze the SEC filings for {company_stock_name} and provide a comprehensive summary report. The report should:

1. Include key financial metrics and performance indicators from recent quarterly and annual reports
2. Highlight significant business developments, risks, and market position
3. Analyze management's discussion and strategic initiatives
4. Note any material changes in operations or financial condition
5. Summarize important regulatory disclosures and compliance matters

Please structure the analysis in a clear, concise manner and limit the total response to no more than 10,000 words. Focus on the most relevant and impactful information for investors."""

    # Sending the message to the agent
    agent = create_sec_agent()
    response = agent.step(usr_msg)

    # Check the response (just for illustrative purpose)
    # print(agent.memory.get_context())
    return response.msgs[0].content

get_sec_summary_for_company_tool = FunctionTool(get_sec_summary_for_company)

if __name__ == "__main__":
    get_sec_summary_for_company("GOOG")

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import asyncio
import sys
import time
from pathlib import Path
from typing import List, Dict, Any

from colorama import Fore, init
from dotenv import load_dotenv

from camel.agents.chat_agent import ToolCallingRecord
from camel.models import ModelFactory
from camel.toolkits import FunctionTool, MCPToolkit
from camel.types import ModelPlatformType, ModelType

from owl.utils.enhanced_role_playing import OwlRolePlaying

import pathlib

# Initialize colorama for cross-platform colored terminal output
init()

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    r"""Build a multi-agent OwlRolePlaying instance for task completion.

    Args:
        question (str): The task to perform.
        tools (List[FunctionTool]): The MCP tools to use for interaction.

    Returns:
        OwlRolePlaying: The configured society of agents.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_PLUS_LATEST,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_PLUS_LATEST,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

def create_md_file(task: str) -> str:
    """Create a markdown file for the conversation with timestamp in filename.

    Args:
        task (str): The task being performed.

    Returns:
        str: Path to the created markdown file.
    """
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    # Create logs directory if it doesn't exist
    logs_dir = Path("conversation_logs")
    logs_dir.mkdir(exist_ok=True)

    # Create a shortened task name for the filename
    task_short = task[:30].replace(" ", "_").replace("/", "_")
    filename = f"{logs_dir}/conversation_{timestamp}_{task_short}.md"

    # Initialize the file with header
    with open(filename, "w", encoding="utf-8") as f:
        f.write(f"# Agent Conversation: {task}\n\n")
        f.write(f"*Generated on: {time.strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
        f.write("## Task Details\n\n")
        f.write(f"**Task:** {task}\n\n")
        f.write("## Conversation\n\n")

    return filename

def write_to_md(filename: str, content: Dict[str, Any]) -> None:
    """Write content to the markdown file.

    Args:
        filename (str): Path to the markdown file.
        content (Dict[str, Any]): Content to write to the file.
    """
    with open(filename, "a", encoding="utf-8") as f:
        if "system_info" in content:
            f.write(f"### System Information\n\n")
            for key, value in content["system_info"].items():
                f.write(f"**{key}:** {value}\n\n")

        if "assistant" in content:
            f.write(f"### 🤖 Assistant\n\n")
            if "tool_calls" in content:
                f.write("**Tool Calls:**\n\n")
                for tool_call in content["tool_calls"]:
                    f.write(f"```\n{tool_call}\n```\n\n")
            f.write(f"{content['assistant']}\n\n")

        if "user" in content:
            f.write(f"### 👤 User\n\n")
            f.write(f"{content['user']}\n\n")

        if "summary" in content:
            f.write(f"## Summary\n\n")
            f.write(f"{content['summary']}\n\n")

async def run_society_with_formatted_output(society: OwlRolePlaying, md_filename: str, round_limit: int = 15):
    """Run the society with nicely formatted terminal output and write to markdown.

    Args:
        society (OwlRolePlaying): The society to run.
        md_filename (str): Path to the markdown file for output.
        round_limit (int, optional): Maximum number of conversation rounds. Defaults to 15.

    Returns:
        tuple: (answer, chat_history, token_count)
    """
    print(Fore.GREEN + f"AI Assistant sys message:\n{society.assistant_sys_msg}\n")
    print(Fore.BLUE + f"AI User sys message:\n{society.user_sys_msg}\n")

    print(Fore.YELLOW + f"Original task prompt:\n{society.task_prompt}\n")
    print(Fore.CYAN + "Specified task prompt:" + f"\n{society.specified_task_prompt}\n")
    print(Fore.RED + f"Final task prompt:\n{society.task_prompt}\n")

    # Write system information to markdown
    write_to_md(md_filename, {
        "system_info": {
            "AI Assistant System Message": society.assistant_sys_msg,
            "AI User System Message": society.user_sys_msg,
            "Original Task Prompt": society.task_prompt,
            "Specified Task Prompt": society.specified_task_prompt,
            "Final Task Prompt": society.task_prompt
        }
    })

    input_msg = society.init_chat()
    chat_history = []
    overall_completion_token_count = 0
    overall_prompt_token_count = 0
    n = 0

    while n < round_limit:
        n += 1
        assistant_response, user_response = await society.astep(input_msg)

        overall_completion_token_count += assistant_response.info["usage"].get(
            "completion_tokens", 0
        ) + user_response.info["usage"].get("completion_tokens", 0)
        overall_prompt_token_count += assistant_response.info["usage"].get(
            "prompt_tokens", 0
        ) + user_response.info["usage"].get("prompt_tokens", 0)

        token_info = {
            "completion_token_count": overall_completion_token_count,
            "prompt_token_count": overall_prompt_token_count,
        }

        md_content = {}

        if assistant_response.terminated:
            termination_msg = f"AI Assistant terminated. Reason: {assistant_response.info['termination_reasons']}."
            print(Fore.GREEN + termination_msg)
            md_content["summary"] = termination_msg
            write_to_md(md_filename, md_content)
            break

        if user_response.terminated:
            termination_msg = f"AI User terminated. Reason: {user_response.info['termination_reasons']}."
            print(Fore.GREEN + termination_msg)
            md_content["summary"] = termination_msg
            write_to_md(md_filename, md_content)
            break

        # Handle tool calls for both terminal and markdown
        if "tool_calls" in assistant_response.info:
            tool_calls: List[ToolCallingRecord] = [
                ToolCallingRecord(**call.as_dict())
                for call in assistant_response.info['tool_calls']
            ]
            md_content["tool_calls"] = tool_calls

            # Print to terminal
            print(Fore.GREEN + "AI Assistant:")
            for func_record in tool_calls:
                print(f"{func_record}")
        else:
            print(Fore.GREEN + "AI Assistant:")

        # Print assistant response to terminal
        print(f"{assistant_response.msg.content}\n")

        # Print user response to terminal
        print(Fore.BLUE + f"AI User:\n\n{user_response.msg.content}\n")

        # Build content for markdown file
        md_content["assistant"] = assistant_response.msg.content
        md_content["user"] = user_response.msg.content

        # Write to markdown
        write_to_md(md_filename, md_content)

        # Update chat history
        chat_history.append({
            "assistant": assistant_response.msg.content,
            "user": user_response.msg.content,
        })

        if "TASK_DONE" in user_response.msg.content:
            task_done_msg = "Task completed successfully!"
            print(Fore.YELLOW + task_done_msg + "\n")
            write_to_md(md_filename, {"summary": task_done_msg})
            break

        input_msg = assistant_response.msg

    # Write token count information
    write_to_md(md_filename, token_info)

    # Extract final answer
    answer = assistant_response.msg.content if assistant_response and assistant_response.msg else ""

    return answer, chat_history, token_info

async def main():
    # Load SSE server configuration
    config_path = Path(__file__).parent / "mcp_sse_config.json"

    # Set default task - a simple example query
    default_task = (
        "Visit the Qwen3 GitHub repository, summarize the introduction of the repository."
        "Write a comprehensive HTML documentation site with the following features:"
        "A clear introduction to Qwen3"
        "Well-organized sections of the technical documentation"
        "Practical code examples"
        "A visually appealing purple technology theme (e.g. modern, clean, purple-accented design)"
        "Finally, deploy the HTML site and open it in the browser."
    )

    # Use command line argument if provided, otherwise use default task
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    mcp_toolkit = MCPToolkit(config_path=str(config_path), strict=True)

    try:
        # Create markdown file for conversation export
        md_filename = create_md_file(task)
        print(Fore.CYAN + f"Conversation will be saved to: {md_filename}")

        await mcp_toolkit.connect()
        print(Fore.GREEN + "Successfully connected to SSE server")

        # Get available tools
        tools = [*mcp_toolkit.get_tools()]

        # Build and run society
        print(Fore.YELLOW + f"Starting task: {task}\n")
        society = await construct_society(task, tools)
        answer, chat_history, token_info = await run_society_with_formatted_output(society, md_filename)

        print(Fore.GREEN + f"\nFinal Result: {answer}")
        print(Fore.CYAN + f"Total tokens used: {token_info}")
        print(Fore.CYAN + f"Full conversation log saved to: {md_filename}")

    except KeyboardInterrupt:
        print(Fore.YELLOW + "\nReceived exit signal, shutting down...")
    except Exception as e:
        print(Fore.RED + f"Error occurred: {e}")
    finally:
        print(Fore.YELLOW + "Shutting down connections...")
        try:
            await asyncio.wait_for(
                asyncio.shield(mcp_toolkit.disconnect()),
                timeout=3.0
            )
            print(Fore.GREEN + "Successfully disconnected")
        except asyncio.TimeoutError:
            print(Fore.YELLOW + "Disconnect timed out, but program will exit safely")
        except asyncio.CancelledError:
            print(Fore.YELLOW + "Disconnect was cancelled, but program will exit safely")
        except Exception as e:
            print(Fore.RED + f"Error during disconnect: {e}")

        try:
            await asyncio.sleep(0.5)
        except:
            pass

if __name__ == "__main__":
    asyncio.run(main())

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import sys

from dotenv import load_dotenv
from camel.configs import ChatGPTConfig
from camel.models import ModelFactory
from camel.messages.base import BaseMessage

from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType

from owl.utils import OwlRolePlaying
from typing import Dict, List, Optional, Tuple
from camel.logger import set_log_level, set_log_file, get_logger

import pathlib

logger = get_logger(__name__)

base_dir = pathlib.Path(__file__).parent.parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

class ExcelRolePalying(OwlRolePlaying):
    def _construct_gaia_sys_msgs(self):
        user_system_prompt = f"""
===== RULES OF USER =====
Never forget you are a user and I am a assistant. Never flip roles! You will always instruct me. We share a common interest in collaborating to successfully complete a task.
I must help you to complete a difficult task.
You must instruct me based on my expertise and your needs to solve the task step by step. The format of your instruction is: `Instruction: [YOUR INSTRUCTION]`, where "Instruction" describes a sub-task or question.
You must give me one instruction at a time.
I must write a response that appropriately solves the requested instruction.
You should instruct me not ask me questions.

Please note that the task may be very complicated. Do not attempt to solve the task by single step. You must instruct me to find the answer step by step.
Here are some tips that will help you to give more valuable instructions about our task to me:
<tips>
- I can use various tools, such as Excel Toolkit and Code Execution Toolkit.

- Although the task may be complex, the answer exists.
  If you find that the current approach does not lead to the answer, reconsider the task, and use alternative methods or tools to achieve the same goal.

- Always remind me to verify whether the final answer is correct!
  This can be done in multiple ways, such as screenshots, web analysis, etc.

- If I have written code, remind me to run the code and obtain the results.

- Flexibly use code to solve problems, especially for Excel-related tasks.

</tips>

Now, here is the overall task: <task>{self.task_prompt}</task>. Never forget our task!

Now you must start to instruct me to solve the task step-by-step. Do not add anything else other than your instruction!
Keep giving me instructions until you think the task is completed.
When the task is completed, you must only reply with a single word <TASK_DONE>.
Never say <TASK_DONE> unless my responses have solved your task.
        """

        assistant_system_prompt = f"""
===== RULES OF ASSISTANT =====
Never forget you are a assistant and I am a user. Never flip roles! Never instruct me! You have to utilize your available tools to solve the task I assigned.
We share a common interest in collaborating to successfully complete a complex task.
You must help me to complete the task.

Here is our overall task: {self.task_prompt}. Never forget our task!

I must instruct you based on your expertise and my needs to complete the task. An instruction is typically a sub-task or question.

You must leverage your available tools, try your best to solve the problem, and explain your solutions.
Unless I say the task is completed, you should always start with:
Solution: [YOUR_SOLUTION]
[YOUR_SOLUTION] should be specific, including detailed explanations and provide preferable detailed implementations and examples and lists for task-solving.

Please note that our overall task may be very complicated. Here are some tips that may help you solve the task:
<tips>
- If one method fails, try another. The answer exists!
- When it comes to viewing information in an Excel file, you can always start by writing Python code to read the Excel file and check sheet names, column names, and similar details.
- When providing Python code, always remember to import the necessary libraries at the beginning, such as the commonly used libraries for Excel analysis below:
```
import pandas as pd
```
- Always verify whether your final answer is correct!
- Always write complete code from scratch. After writing the code, be sure to run it and obtain the results!
  If you encounter errors, try debugging the code.
  Note that the code execution environment does not support interactive input.
- If the tool fails to run or the code does not execute correctly,
  never assume that it has returned the correct result and continue reasoning based on it!
  The correct approach is to analyze the cause of the error and try to fix it!
</tips>

        """

        user_sys_msg = BaseMessage.make_user_message(
            role_name=self.user_role_name, content=user_system_prompt
        )

        assistant_sys_msg = BaseMessage.make_assistant_message(
            role_name=self.assistant_role_name, content=assistant_system_prompt
        )

        return user_sys_msg, assistant_sys_msg

def run_society(
    society: ExcelRolePalying,
    round_limit: int = 15,
) -> Tuple[str, List[dict], dict]:
    overall_completion_token_count = 0
    overall_prompt_token_count = 0

    chat_history = []
    init_prompt = """
    Now please give me instructions to solve over overall task step by step. If the task requires some specific knowledge, please instruct me to use tools to complete the task.
        """
    input_msg = society.init_chat(init_prompt)
    for _round in range(round_limit):
        assistant_response, user_response = society.step(input_msg)
        # Check if usage info is available before accessing it
        if assistant_response.info.get("usage") and user_response.info.get("usage"):
            overall_completion_token_count += assistant_response.info["usage"].get(
                "completion_tokens", 0
            ) + user_response.info["usage"].get("completion_tokens", 0)
            overall_prompt_token_count += assistant_response.info["usage"].get(
                "prompt_tokens", 0
            ) + user_response.info["usage"].get("prompt_tokens", 0)

        # convert tool call to dict
        tool_call_records: List[dict] = []
        if assistant_response.info.get("tool_calls"):
            for tool_call in assistant_response.info["tool_calls"]:
                tool_call_records.append(tool_call.as_dict())

        _data = {
            "user": user_response.msg.content
            if hasattr(user_response, "msg") and user_response.msg
            else "",
            "assistant": assistant_response.msg.content
            if hasattr(assistant_response, "msg") and assistant_response.msg
            else "",
            "tool_calls": tool_call_records,
        }

        chat_history.append(_data)
        logger.info(
            f"Round #{_round} user_response:\n {user_response.msgs[0].content if user_response.msgs and len(user_response.msgs) > 0 else ''}"
        )
        logger.info(
            f"Round #{_round} assistant_response:\n {assistant_response.msgs[0].content if assistant_response.msgs and len(assistant_response.msgs) > 0 else ''}"
        )

        if (
            assistant_response.terminated
            or user_response.terminated
            or "TASK_DONE" in user_response.msg.content
        ):
            break

        input_msg = assistant_response.msg

    answer = chat_history[-1]["assistant"]
    token_info = {
        "completion_token_count": overall_completion_token_count,
        "prompt_token_count": overall_prompt_token_count,
    }

    return answer, chat_history, token_info

def construct_society(question: str) -> ExcelRolePalying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        OwlRolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components using Azure OpenAI
    base_model_config = {
        "model_platform": ModelPlatformType.AZURE,
        "model_type": os.getenv("AZURE_OPENAI_MODEL_TYPE"),
        "model_config_dict": ChatGPTConfig(temperature=0.01, max_tokens=4096).as_dict(),
    }

    models = {
        "user": ModelFactory.create(**base_model_config),
        "assistant": ModelFactory.create(**base_model_config),
    }

    # Configure toolkits
    tools = [
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = ExcelRolePalying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="English"
    )

    return society

def main():
    # Example question

    default_task = """Please help analyze the file `./data/admission_en.xlsx` by:
            - Calculating the number of admitted students, as well as the highest and lowest scores for each college
            - Plotting this information in a single chart: use a bar chart for the number of admitted students, and line charts for the highest and lowest scores
            - Saving the generated chart as `vis_en.png` in the current directory"""

    set_log_file('log.txt')

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

import streamlit as st
from dotenv import load_dotenv
from pathlib import Path
import os

# Import Camel-AI and OWL modules
from camel.models import ModelFactory
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying
from camel.toolkits import (
    ExcelToolkit,
    SearchToolkit,
    CodeExecutionToolkit,
)
from owl.utils import run_society
from owl.utils import DocumentProcessingToolkit

# Set log level to see detailed logs (optional)
set_log_level("DEBUG")

# Load environment variables from .env file if available

load_dotenv()

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        SearchToolkit().search_baidu,
        *ExcelToolkit().get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def summarize_section():
    st.header("Summarize Medical Text")
    text = st.text_area("Enter medical text to summarize:", height=200)
    if st.button("Summarize"):
        if text:
            # Create a task prompt for summarization
            task_prompt = f"Summarize the following medical text:\n\n{text}"
            society = construct_society(task_prompt)
            with st.spinner("Running summarization society..."):
                answer, chat_history, token_count = run_society(society)
                st.subheader("Summary:")
                st.write(answer)
                st.write(chat_history)
        else:
            st.warning("Please enter some text to summarize.")

def write_and_refine_article_section():
    st.header("Write and Refine Research Article")
    topic = st.text_input("Enter the topic for the research article:")
    outline = st.text_area("Enter an outline (optional):", height=150)
    if st.button("Write and Refine Article"):
        if topic:
            # Create a task prompt for article writing and refinement
            task_prompt = f"Write a research article on the topic: {topic}."
            if outline.strip():
                task_prompt += f" Use the following outline as guidance:\n{outline}"
            society = construct_society(task_prompt)
            with st.spinner("Running research article society..."):
                print(task_prompt)
                answer, chat_history, token_count = run_society(society)
                st.subheader("Article:")
                st.write(answer)
                st.write(chat_history)
        else:
            st.warning("Please enter a topic for the research article.")

def sanitize_data_section():
    st.header("Sanitize Medical Data (PHI)")
    data = st.text_area("Enter medical data to sanitize:", height=200)
    if st.button("Sanitize Data"):
        if data:
            # Create a task prompt for data sanitization
            task_prompt = f"Sanitize the following medical data by removing any protected health information (PHI):\n\n{data}"
            society = construct_society(task_prompt)
            with st.spinner("Running data sanitization society..."):
                answer, chat_history, token_count = run_society(society)
                st.subheader("Sanitized Data:")
                st.write(answer)
                st.write(chat_history)
        else:
            st.warning("Please enter medical data to sanitize.")

def main():
    st.set_page_config(page_title="Multi-Agent AI System with Camel & OWL", layout="wide")
    st.title("Multi-Agent AI System with Camel-AI and OWL")

    st.sidebar.title("Select Task")
    task = st.sidebar.selectbox("Choose a task:", [
        "Summarize Medical Text",
        "Write and Refine Research Article",
        "Sanitize Medical Data (PHI)"
    ])

    if task == "Summarize Medical Text":
        summarize_section()
    elif task == "Write and Refine Research Article":
        write_and_refine_article_section()
    elif task == "Sanitize Medical Data (PHI)":
        sanitize_data_section()

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

# AI/ML Engineer Job Description
AI_ENGINEER_JOB_DESCRIPTION = """
# AI/ML Engineer Job Description

## About the Role
We are seeking a skilled AI/ML Engineer to join our team. The ideal candidate will design, develop, and deploy machine learning models and AI systems that solve complex business problems. You will work closely with cross-functional teams to understand requirements, implement solutions, and continuously improve our AI capabilities.

## Key Responsibilities
- Design, develop, and implement machine learning models and algorithms
- Build and maintain scalable ML pipelines for data processing, model training, and inference
- Collaborate with product teams to understand requirements and translate them into technical solutions
- Optimize existing models for performance, accuracy, and efficiency
- Stay current with the latest AI/ML research and technologies
- Implement and deploy models to production environments
- Monitor and troubleshoot deployed models

## Required Qualifications
- Bachelor's or Master's degree in Computer Science, AI, Machine Learning, or related field
- 3+ years of experience in machine learning or AI development
- Strong programming skills in Python and familiarity with ML frameworks (TensorFlow, PyTorch, etc.)
- Experience with deep learning architectures (CNNs, RNNs, Transformers)
- Knowledge of NLP, computer vision, or other specialized AI domains
- Experience with cloud platforms (AWS, GCP, Azure) and MLOps tools
- Strong problem-solving skills and attention to detail
- Excellent communication and collaboration abilities

## Preferred Qualifications
- PhD in Machine Learning, AI, or related field
- Experience with large language models (LLMs) and generative AI
- Contributions to open-source ML/AI projects
- Experience with distributed computing and big data technologies
- Published research in AI/ML conferences or journals
- Experience with model optimization techniques (quantization, pruning, distillation)
"""

async def construct_society(
    resume_dir: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    r"""build a multi-agent OwlRolePlaying instance.

    Args:
        question (str): The question to ask.
        tools (List[FunctionTool]): The MCP tools to use.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_prompt = f"""
    You are a professional resume analyzer for AI/ML Engineer positions. Your task is to:

    1. Analyze all resume files in the directory: {resume_dir}
    2. Please note when you retrieve the content of PDF files using the `pdf-reader` tool, the path is mapping to the `/pdfs` directory inside the pdf-reader docker container.
    3. For each resume, evaluate how well the candidate matches the following job description:

    {AI_ENGINEER_JOB_DESCRIPTION}

    4. Score each resume on a scale of 1-100 based on:
       - Technical skills match (40%)
       - Experience relevance (30%)
       - Education and qualifications (20%)
       - Communication and presentation (10%)

    5. Rank all candidates from most to least qualified
    6. For each candidate, highlight their strengths and areas for improvement
    7. Output your analysis to a markdown file named './resume_analysis.md' with the following sections:
       - Executive Summary
       - Individual Candidate Assessments (with scores)
       - Ranked List of Candidates
       - Recommendations for Hiring Manager

    Be thorough, fair, and objective in your assessment.
    Always execute the MCP tools, don't ask me for confirmation.
    """

    society = OwlRolePlaying(
        task_prompt=task_prompt,
        with_task_specify=False,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        await mcp_toolkit.connect()

        # Default resume directory if none provided
        default_resume_dir = "./resumes/"

        # Get resume directory from command line argument if provided
        resume_dir = sys.argv[1] if len(sys.argv) > 1 else default_resume_dir

        print(f"\033[94mAnalyzing resumes for AI/ML Engineer position...\033[0m")

        # Connect to all MCP toolkits
        tools = [*mcp_toolkit.get_tools()]
        society = await construct_society(resume_dir, tools)
        answer, chat_history, token_count = await arun_society(society)
        print(f"\033[94mAnswer: {answer}\033[0m")
        print(f"\033[94mCompleted! Resume analysis has been saved to ./resume_analysis.md file\033[0m")

    finally:
        # Make sure to disconnect safely after all operations are completed.
        try:
            await mcp_toolkit.disconnect()
        except Exception:
            print("Disconnect failed")

if __name__ == "__main__":
    asyncio.run(main())

"""
数据API接口模块
"""
import pandas as pd
import akshare as ak
from datetime import datetime
import logging
from typing import Optional

logger = logging.getLogger(__name__)

def get_price_data(ticker: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]:
    """
    获取股票价格数据

    Args:
        ticker: 股票代码
        start_date: 开始日期 (YYYY-MM-DD)
        end_date: 结束日期 (YYYY-MM-DD)

    Returns:
        Optional[pd.DataFrame]: 股票价格数据
    """
    try:
        logger.info(f"获取股票 {ticker} 的价格数据")

        # 转换日期格式
        start_date_fmt = datetime.strptime(start_date, "%Y-%m-%d").strftime("%Y%m%d")
        end_date_fmt = datetime.strptime(end_date, "%Y-%m-%d").strftime("%Y%m%d")

        # 使用akshare获取A股历史数据
        df = ak.stock_zh_a_hist(
            symbol=ticker,
            period="daily",
            start_date=start_date_fmt,
            end_date=end_date_fmt,
            adjust="qfq"  # 前复权
        )

        # 检查df的列数
        logger.info(f"原始数据列: {df.columns.tolist()}")

        # 调整列名映射以适应实际返回的数据
        column_mappings = {
            '日期': 'date',
            '股票代码': 'code',
            '开盘': 'open',
            '收盘': 'close',
            '最高': 'high',
            '最低': 'low',
            '成交量': 'volume',
            '成交额': 'amount',
            '振幅': 'amplitude',
            '涨跌幅': 'pct_change',
            '涨跌额': 'change',
            '换手率': 'turnover'
        }

        # 根据实际列名重命名
        new_columns = []
        for col in df.columns:
            if col in column_mappings:
                new_columns.append(column_mappings[col])
            else:
                # 保留原列名
                new_columns.append(col)

        # 应用新列名
        df.columns = new_columns

        # 将日期列转换为datetime类型
        df['date'] = pd.to_datetime(df['date'])

        logger.info(f"成功获取价格数据，共 {len(df)} 条记录")
        return df

    except Exception as e:
        logger.error(f"获取价格数据时发生错误: {str(e)}")
        return None

import os
import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool, MCPToolkit
from camel.types import ModelPlatformType, ModelType
from camel.logger import get_logger, set_log_file

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

# Set logging level
set_log_file("notion_mcp.log")
logger = get_logger(__name__)

# Load environment variables
load_dotenv(os.path.join(os.path.dirname(__file__), '../../owl/.env'))

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    """Build a multi-agent OwlRolePlaying instance for Notion management."""
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
            },
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
            },
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    return OwlRolePlaying(
        **task_kwargs,
        user_role_name="notion_manager",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="notion_assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

async def execute_notion_task(society: OwlRolePlaying):
    """Execute the Notion task and handle the result."""
    try:
        result = await arun_society(society)

        if isinstance(result, tuple) and len(result) == 3:
            answer, chat_history, token_count = result
            logger.info(f"\nTask Result: {answer}")
            logger.info(f"Token count: {token_count}")
        else:
            logger.info(f"\nTask Result: {result}")

    except Exception as e:
        logger.info(f"\nError during task execution: {str(e)}")
        raise

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        logger.info("Connecting to Notion MCP server...")
        await mcp_toolkit.connect()
        logger.info("Successfully connected to Notion MCP server")

        default_task = (

            "Notion Task:\n"
            "1. Find the page titled 'Travel Itinerary\n"
            "2. Create a list of Top 10 travel destinations in Europe and add them to the page along with their description.\n"
            "3. Also mention the best time to visit these destintions.\n"

        )

        task = sys.argv[1] if len(sys.argv) > 1 else default_task
        logger.info(f"\nExecuting task:\n{task}")

        tools = [*mcp_toolkit.get_tools()]
        society = await construct_society(task, tools)

        await execute_notion_task(society)

    except Exception as e:
        logger.info(f"\nError: {str(e)}")
        raise

    finally:
        logger.info("\nPerforming cleanup...")
        tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
        for task in tasks:
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass

        try:
            await mcp_toolkit.disconnect()
            logger.info("Successfully disconnected from Notion MCP server")
        except Exception as e:
            logger.info(f"Cleanup error (can be ignored): {e}")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("\nReceived keyboard interrupt. Shutting down gracefully...")
    finally:
        if sys.platform == 'win32':
            try:
                import asyncio.windows_events
                asyncio.windows_events._overlapped = None
            except (ImportError, AttributeError):
                pass

"""
代理模块初始化
"""

from src.agents.base_agent import BaseAgent
from src.agents.market_data_agent import MarketDataAgent
from src.agents.technical_analyst import TechnicalAnalystAgent
from src.agents.fundamentals_analyst import FundamentalsAnalystAgent
from src.agents.sentiment_analyst import SentimentAnalystAgent
from src.agents.valuation_analyst import ValuationAnalystAgent
from src.agents.researcher_bull import ResearcherBullAgent
from src.agents.researcher_bear import ResearcherBearAgent
from src.agents.debate_room import DebateRoomAgent
from src.agents.risk_manager import RiskManagerAgent
from src.agents.portfolio_manager import PortfolioManagerAgent
from src.agents.investment_agent import InvestmentAgent

__all__ = [
    'BaseAgent',
    'MarketDataAgent',
    'TechnicalAnalystAgent',
    'FundamentalsAnalystAgent',
    'SentimentAnalystAgent',
    'ValuationAnalystAgent',
    'ResearcherBullAgent',
    'ResearcherBearAgent',
    'DebateRoomAgent',
    'RiskManagerAgent',
    'PortfolioManagerAgent',
    'InvestmentAgent'
]

import asyncio
import sys
from pathlib import Path
from typing import List, Dict

from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import FunctionTool, MCPToolkit
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

import pathlib

set_log_level(level="DEBUG")

# Load environment variables from .env file if available
load_dotenv()

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    """Build a multi-agent OwlRolePlaying instance with enhanced content curation capabilities."""
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
                # "max_tokens": 4000  # Add token limit to prevent overflow
            },
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
                #"max_tokens": 4000
            },
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    return OwlRolePlaying(
        **task_kwargs,
        user_role_name="content_curator",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="research_assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        await mcp_toolkit.connect()

        default_task = (
            "Find me the best Airbnb in Gurugram with a check-in date of 2025-06-01 "
            "and a check-out date of 2025-06-07 for 2 adults. Return the top 5 listings with their names, "
            "prices, and locations."
        )

        task = sys.argv[1] if len(sys.argv) > 1 else default_task

        # Connect to all MCP toolkits
        tools = [*mcp_toolkit.get_tools()]
        society = await construct_society(task, tools)

        try:
            # Add error handling for the society execution
            result = await arun_society(society)

            # Handle the result properly
            if isinstance(result, tuple) and len(result) == 3:
                answer, chat_history, token_count = result
            else:
                answer = str(result)
                chat_history = []
                token_count = 0

        except Exception as e:
            print(f"Error during society execution: {str(e)}")
            raise

    finally:
        # Cleanup
        await asyncio.sleep(1)
        tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
        for task in tasks:
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass

        try:
            await mcp_toolkit.disconnect()
        except Exception as e:
            print(f"Cleanup error (can be ignored): {e}")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nShutting down gracefully...")
    finally:
        if sys.platform == 'win32':
            try:
                import asyncio.windows_events
                asyncio.windows_events._overlapped = None
            except (ImportError, AttributeError):
                pass

import asyncio
import sys
from pathlib import Path
from typing import List, Dict

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool, MCPToolkit
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

import pathlib

set_log_level(level="DEBUG")

# Load environment variables from .env file if available
load_dotenv()

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    """Build a multi-agent OwlRolePlaying instance with enhanced content curation capabilities."""
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
                # "max_tokens": 4000  # Add token limit to prevent overflow
            },
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={
                "temperature": 0.7,
                #"max_tokens": 4000
            },
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    return OwlRolePlaying(
        **task_kwargs,
        user_role_name="content_curator",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="research_assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        await mcp_toolkit.connect()

        default_task = (
            "Content Curation Task:\n"
            "1. Search for these tech websites:\n"
            "   - TechCrunch\n"
            "   - The Verge\n"
            "   - Wired\n"
            "2. For each site:\n"
            "   - Use the firecrawl_scrape tool with proper extraction options\n"
            "   - Extract the homepage content\n"
            "   - Focus on headlines and recent articles\n"
            "3. For each article found:\n"
            "   - Extract title and summary\n"
            "   - Note the publication date if available\n"
            "4. Create a summary report including:\n"
            "   - Top stories from each source\n"
            "   - Common themes or trends\n"
            "5. Save the report as 'Latest_tech_digest.md'\n"
        )

        task = sys.argv[1] if len(sys.argv) > 1 else default_task

        # Connect to all MCP toolkits
        tools = [*mcp_toolkit.get_tools()]
        society = await construct_society(task, tools)

        try:
            # Add error handling for the society execution
            result = await arun_society(society)

            # Handle the result properly
            if isinstance(result, tuple) and len(result) == 3:
                answer, chat_history, token_count = result
            else:
                answer = str(result)
                chat_history = []
                token_count = 0

        except Exception as e:
            print(f"Error during society execution: {str(e)}")
            raise

    finally:
        # Cleanup
        await asyncio.sleep(1)
        tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
        for task in tasks:
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass

        try:
            await mcp_toolkit.disconnect()
        except Exception as e:
            print(f"Cleanup error (can be ignored): {e}")

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nShutting down gracefully...")
    finally:
        if sys.platform == 'win32':
            try:
                import asyncio.windows_events
                asyncio.windows_events._overlapped = None
            except (ImportError, AttributeError):
                pass

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import logging
import functools
import json
from typing import Callable, Any, Dict, List

from dotenv import load_dotenv
from camel.models import ModelFactory, BaseModelBackend

from camel.toolkits import (
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
    VirtualTryOnToolkit
)
from camel.toolkits.base import BaseToolkit
from camel.types import ModelPlatformType

from owl.utils import run_society
from camel.societies import RolePlaying
from camel.logger import set_log_level, get_logger

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

# set detailed log recording for debug
set_log_level(level="DEBUG")
logger = get_logger(__name__)
file_handler = logging.FileHandler('tool_calls.log')
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

root_logger = logging.getLogger()
root_logger.addHandler(file_handler)

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components (here I use gpt-4o for all agents, so remember to set the openai key in .env)
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
        "web": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.2},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.3},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="gpt-4o",
            api_key=os.getenv("OPENAI_API_KEY"),
            model_config_dict={"temperature": 0.4},
        ),
    }

    # prepare toolkits
    image_toolkit = ImageAnalysisToolkit(model=models["image"])
    browser_toolkit = BrowserToolkit(
        headless=False,
        web_agent_model=models["web"],
        planning_agent_model=models["planning"],
    )
    excel_toolkit = ExcelToolkit()
    file_toolkit = FileWriteToolkit(output_dir="./")
    virtual_try_on_toolkit = VirtualTryOnToolkit()

    tools = [
        *browser_toolkit.get_tools(),
        *image_toolkit.get_tools(),
        SearchToolkit().search_duckduckgo,
        # SearchToolkit().search_google,
        # SearchToolkit().search_wiki,
        *excel_toolkit.get_tools(),
        *file_toolkit.get_tools(),
        *virtual_try_on_toolkit.get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""

    question = f"open https://www.uniqlo.com/eu-at/en/women/tops?path=37608%2C84986%2C85018%2C85207 which shows some clothes on sale. First, directly click one image of clothes which should be an big interactive element (don't wrongly click the small like button overlapped on the image!) to go into its specific details page and then get a partial screenshot for this clothes. Second, only after you've get the partial screenshort of the product, using your own virtual try-on toolkit (there is no built-in virtual try-on button on this website, either no third party tool required) to show me the virtual try-on result with the product."

    # Construct and run the society
    society = construct_society(question)
    answer, chat_history, token_count = run_society(society)
    # output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

import asyncio
from pathlib import Path

import streamlit as st
from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit, SearchToolkit
import sys

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society
import logging

logging.basicConfig(level=logging.DEBUG)

# Load environment variables and set logger level
if sys.platform.startswith("win"):
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
load_dotenv()
set_log_level(level="DEBUG")

async def construct_society(task: str, tools: list[FunctionTool]) -> OwlRolePlaying:
    """
    Build a multi-agent OwlRolePlaying instance.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}
    task_kwargs = {
        "task_prompt": task,
        "with_task_specify": False,
    }

    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def run_task(task: str) -> str:
    """
    Connect to MCP servers, run the provided task, and return the answer.
    """
    # Construct the path to your MCP server config file.
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))
    answer = ""
    try:
        logging.debug("Connecting to MCP server...")
        await mcp_toolkit.connect()
        logging.debug("Connected to MCP server.")

        # Prepare all tools from the MCP toolkit and the web search toolkit
        tools = [*mcp_toolkit.get_tools(), SearchToolkit().search_duckduckgo]
        society = await construct_society(task, tools)
        answer, chat_history, token_count = await arun_society(society)
    except Exception as e:
        import traceback
        st.error(f"An error occurred: {e}")
        st.text(traceback.format_exc())
    finally:
        try:
            await mcp_toolkit.disconnect()
        except Exception as e:
            answer += f"\nError during disconnect: {e}"
    return answer

def main():
    st.title("OWL X Puppeteer MCP Server")

    # Get the task from the user
    task = st.text_input("Enter your task",value="Please find the top articles from dev.to this week and go to each article and then summarize it. Please use MCP given to you")

    if st.button("Run Task"):
        if not task.strip():
            st.error("Please enter a valid task.")
        else:
            with st.spinner("Processing the task..."):
                try:
                    # Create a new event loop for the current thread
                    new_loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(new_loop)
                    result = new_loop.run_until_complete(run_task(task))
                except Exception as e:
                    st.error(f"An error occurred: {e}")
                    result = ""
                finally:
                    new_loop.close()
                st.success("Task completed!")
                st.write(result)

if __name__ == "__main__":
    main()

"""
日志工具模块
"""
import logging
import os
from datetime import datetime
from typing import Optional

def setup_logger(name: str, log_level: int = logging.INFO, log_dir: str = "logs") -> logging.Logger:
    """
    设置日志记录器

    Args:
        name: 日志记录器名称
        log_level: 日志级别
        log_dir: 日志目录

    Returns:
        logging.Logger: 配置好的日志记录器
    """
    # 确保日志目录存在
    os.makedirs(log_dir, exist_ok=True)

    # 获取日志记录器
    logger = logging.getLogger(name)

    # 如果已配置，直接返回
    if logger.handlers:
        return logger

    # 设置日志级别
    logger.setLevel(log_level)

    # 创建文件处理器
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = f"{log_dir}/{name}_{timestamp}.log"
    file_handler = logging.FileHandler(log_file, encoding="utf-8")
    file_handler.setLevel(log_level)

    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(log_level)

    # 设置格式化器
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    # 添加处理器
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

    return logger

class OutputLogger:
    """
    输出日志记录器，用于重定向标准输出
    """
    def __init__(self, filename: Optional[str] = None):
        """
        初始化输出日志记录器

        Args:
            filename: 日志文件名，默认为自动生成
        """
        # 确保日志目录存在
        os.makedirs("logs", exist_ok=True)

        # 设置日志文件
        if not filename:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"logs/output_{timestamp}.log"

        self.terminal = open(filename, "a", encoding="utf-8")
        self.terminal.write(f"=== 日志开始于 {datetime.now()} ===\n")
        self.stdout = None

    def write(self, message):
        """写入消息到终端和日志文件"""
        if self.stdout:
            self.stdout.write(message)
        self.terminal.write(message)
        self.terminal.flush()

    def flush(self):
        """刷新日志"""
        if self.stdout:
            self.stdout.flush()
        self.terminal.flush()

    def close(self):
        """关闭日志文件"""
        self.terminal.write(f"=== 日志结束于 {datetime.now()} ===\n")
        self.terminal.close()

    def __del__(self):
        """析构函数，确保关闭日志文件"""
        try:
            self.close()
        except:
            pass

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import re
import sys
from pathlib import Path
from typing import List

# The license template file is hard-coded with specific start and end lines
def fine_license_start_line(lines: List[str], start_with: str) -> int:
    for i in range(len(lines)):
        if lines[i].startswith(start_with):
            return i
    return None

def find_license_end_line(lines: List[str], start_with: str) -> int:
    for i in range(len(lines) - 1, -1, -1):
        if lines[i].startswith(start_with):
            return i
    return None

def update_license_in_file(
    file_path: str,
    license_template_path: str,
    start_line_start_with: str,
    end_line_start_with: str,
) -> bool:
    with open(file_path, "r", encoding="utf-8") as f:  # for windows compatibility
        content = f.read()

    with open(license_template_path, "r", encoding="utf-8") as f:
        new_license = f.read().strip()

    maybe_existing_licenses = re.findall(
        r"^#.*?(?=\n)", content, re.MULTILINE | re.DOTALL
    )
    start_index = fine_license_start_line(
        maybe_existing_licenses, start_line_start_with
    )
    end_index = find_license_end_line(maybe_existing_licenses, end_line_start_with)
    if start_index is not None and end_index is not None:
        maybe_existing_licenses = maybe_existing_licenses[start_index : end_index + 1]
    else:
        maybe_existing_licenses = None
    if maybe_existing_licenses:
        maybe_old_licenses = "\n".join(maybe_existing_licenses)
        if maybe_old_licenses.strip() != new_license.strip():
            replaced_content = content.replace(maybe_old_licenses, new_license)
            with open(file_path, "w") as f:
                f.write(replaced_content)
            print(f"Replaced license in {file_path}")
            return True
        else:
            return False
    else:
        with open(file_path, "w") as f:
            f.write(new_license + "\n" + content)
        print(f"Added license to {file_path}")
        return True

def update_license_in_directory(
    directory_path: str,
    license_template_path: str,
    start_line_start_with: str,
    end_line_start_with: str,
) -> None:
    # Check if directory exists
    if not os.path.isdir(directory_path):
        raise NotADirectoryError(f"{directory_path} is not a directory")
    # Check if license template exists
    if not os.path.isfile(license_template_path):
        raise FileNotFoundError(f"{license_template_path} not found")

    file_count = 0
    for py_files in Path(directory_path).rglob("*.py"):
        if py_files.name.startswith("."):
            continue
        if any(part.startswith(".") for part in py_files.parts):
            continue
        # Skip files in the community_usecase directory
        if "community_usecase" in py_files.parts:
            continue
        if update_license_in_file(
            py_files,
            license_template_path,
            start_line_start_with,
            end_line_start_with,
        ):
            file_count += 1

    print(f"License updated in {file_count} files")

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(
            "Usage from command line: "
            "python update_license.py <directory_path> <license_template_path>"
            "No valid input arguments found, please enter manually."
        )
        directory_path = input("Enter directory path: ")
        license_template_path = input("Enter license template path: ")
    else:
        directory_path = sys.argv[1]
        license_template_path = sys.argv[2]

    start_line_start_with = "# ========= Copyright"
    end_line_start_with = "# ========= Copyright"
    update_license_in_directory(
        directory_path,
        license_template_path,
        start_line_start_with,
        end_line_start_with,
    )

# Define Prompts for Agents

from datetime import datetime

def get_system_prompt() -> str:
    r"""Get the enhanced system prompt for the stock analysis assistant."""

    current_date = datetime.now().strftime("%Y-%m-%d")

    return f"""
    You are an advanced Stock Analysis Assistant powered by OWL multi-agent technology.
    Your primary task is to provide COMPREHENSIVE, EXTREMELY DETAILED, and HIGHLY SPECIFIC
    stock investment recommendations with practical analysis and actionable advice.

    Current Analysis Date: {current_date}

    IMPORTANT OUTPUT REQUIREMENTS:

    1. EXTREME DETAIL: Do not summarize or truncate your responses. Provide complete, comprehensive
       information with multiple sections, subsections, and extensive details. The final output
       should be at least 2000 words, ideally 3000-4000 for truly thorough coverage.

    2. COMPANY INFORMATION FOCUS: Include detailed company background, business model, management team,
       competitive landscape, market position, and growth prospects. Emphasize fundamental analysis
       of the company's financial health and operational performance.

    3. COMPREHENSIVE STOCK DATA: Provide in-depth analysis of stock performance metrics, financial ratios,
       valuation models, historical price movements, and trading volumes. Include detailed examination
       of earnings reports, balance sheets, and cash flow statements.

    4. NO TRUNCATION: Never cut off your responses with '...' or similar. Always provide the
       complete thought or explanation.

    5. STRUCTURED OUTPUT: Use clear headings (H1, H2, H3, etc.), bullet points, numbered lists,
       and well-organized sections to present the content in a digestible way.

    6. SPECIFIC INVESTMENT STRATEGIES: Always provide multiple investment approaches, risk assessments,
       entry/exit points, position sizing recommendations, and relevant portfolio considerations.

    7. FILE MANAGEMENT: You may save all information as well-formatted files, but also include
       the entire unabridged content directly in your response.
    """
def get_sec_system_prompt() -> str:
    r"""Get the enhanced system prompt for the sec assistant."""

    return """
    You are an advanced SEC Financial Data Analysis Assistant
    Your primary task is to retrieve, analyze and provide DETAILED insights from SEC filings including
    quarterly (10-Q) and annual (10-K) reports.

    CORE RESPONSIBILITIES:

    1. DATA RETRIEVAL:
       - Fetch financial statements from SEC EDGAR database
       - Access both quarterly and annual reports
       - Extract key financial metrics and disclosures

    2. FINANCIAL ANALYSIS:
       - Perform comprehensive analysis of income statements
       - Analyze balance sheets and cash flow statements
       - Calculate and interpret key financial ratios
       - Track quarter-over-quarter and year-over-year changes

    3. REPORTING REQUIREMENTS:
       - Present data in clear, structured formats
       - Highlight significant changes and trends
       - Provide detailed explanations of findings
       - Flag any concerning patterns or irregularities

    4. SPECIFIC OUTPUTS:
       - Financial metrics summary
       - Growth analysis
       - Profitability assessment
       - Liquidity analysis
       - Debt and leverage evaluation
       - Cash flow analysis

    5. CONTEXTUAL INSIGHTS:
       - Compare against industry benchmarks
       - Identify potential red flags
       - Evaluate management's commentary
       - Assess risk factors

    Always maintain accuracy and completeness in data retrieval and analysis.
    Provide detailed explanations for any significant findings or anomalies.
    """

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import (
    ExcelToolkit,
    SearchToolkit,
    FileWriteToolkit,
    CodeExecutionToolkit,
    BrowserToolkit,
    VideoAnalysisToolkit,
    ImageAnalysisToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.societies import RolePlaying
from camel.logger import set_log_level

from owl.utils import run_society, DocumentProcessingToolkit

import pathlib

# Set the log level to DEBUG for detailed debugging information
set_log_level(level="DEBUG")

# Get the parent directory of the current file and construct the path to the .env file
base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

def get_user_input(prompt):
    # Get user input and strip leading/trailing whitespace
    return input(prompt).strip()

def get_construct_params() -> dict[str, any]:
    # Welcome message
    print("Welcome to owl! Have fun!")

    # Select model platform type
    model_platforms = ModelPlatformType
    print("Please select the model platform type:")
    for i, platform in enumerate(model_platforms, 1):
        print(f"{i}. {platform}")
    model_platform_choice = int(
        get_user_input("Please enter the model platform number:")
    )
    selected_model_platform = list(model_platforms)[model_platform_choice - 1]
    print(f"The model platform you selected is: {selected_model_platform}")

    # Select model type
    models = ModelType
    print("Please select the model type:")
    for i, model in enumerate(models, 1):
        print(f"{i}. {model}")
    model_choice = int(get_user_input("Please enter the model number:"))
    selected_model = list(models)[model_choice - 1]
    print(f"The model you selected is: {selected_model}")

    # Select language
    languages = ["English", "Chinese"]
    print("Please select the language:")
    for i, lang in enumerate(languages, 1):
        print(f"{i}. {lang}")
    language_choice = int(get_user_input("Please enter the language number:"))
    selected_language = languages[language_choice - 1]
    print(f"The language you selected is: {selected_language}")

    # Enter the question
    question = get_user_input("Please enter your question:")
    print(f"Your question is: {question}")

    return {
        "language": selected_language,
        "model_type": selected_model,
        "model_platform": selected_model_platform,
        "question": question,
    }

def construct_society() -> RolePlaying:
    # Get user input parameters
    params = get_construct_params()
    question = params["question"]
    selected_model_type = params["model_type"]
    selected_model_platform = params["model_platform"]
    selected_language = params["language"]

    # Create model instances for different roles
    models = {
        "user": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=selected_model_platform,
            model_type=selected_model_type,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *VideoAnalysisToolkit(model=models["video"]).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,
        SearchToolkit().search_wiki,
        SearchToolkit().search_baidu,
        SearchToolkit().search_bing,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language=selected_language,
    )

    return society

def main():
    # Construct the society
    society = construct_society()
    # Run the society and get the answer, chat history, and token count
    answer, chat_history, token_count = run_society(society)
    # Print the answer
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
"""MCP Multi-Agent System Example

This example demonstrates how to use MCP (Model Context Protocol) with CAMEL agents
for advanced information retrieval and processing tasks.

Environment Setup:
1. Configure the required dependencies of owl library
   Refer to: https://github.com/camel-ai/owl for installation guide

2. MCP Server Setup:

   2.1 MCP Playwright Service:
   ```bash
   # Install MCP service
   npm install -g @executeautomation/playwright-mcp-server
   npx playwright install-deps

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "playwright": {
         "command": "npx",
         "args": ["-y", "@executeautomation/playwright-mcp-server"]
       }
     }
   }
   ```

   2.2 MCP Fetch Service (Optional - for better retrieval):
   ```bash
   # Install MCP service
   pip install mcp-server-fetch

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "fetch": {
         "command": "python",
         "args": ["-m", "mcp_server_fetch"]
       }
     }
   }
   ```

Usage:
1. Ensure all MCP servers are properly configured in mcp_servers_config.json
2. Run this script to create a multi-agent system that can:
   - Access and manipulate files through MCP Desktop Commander
   - Perform web automation tasks using Playwright
   - Process and generate information using GPT-4o
   - Fetch web content (if fetch service is configured)
3. The system will execute the specified task while maintaining security through
   controlled access

Note:
- All file operations are restricted to configured directories
- System uses GPT-4o for both user and assistant roles
- Supports asynchronous operations for efficient processing
"""

import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit,SearchToolkit

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

load_dotenv()

set_log_level(level="DEBUG")

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    r"""build a multi-agent OwlRolePlaying instance.

    Args:
        question (str): The question to ask.
        tools (List[FunctionTool]): The MCP tools to use.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        print("Attempting to connect to MCP servers...")
        await mcp_toolkit.connect()

        # Default task
        default_task = (
            "Read the unread messages from {contact name} on whatsapp and reply to his query"
        )

        # Override default task if command line argument is provided
        task = sys.argv[1] if len(sys.argv) > 1 else default_task
        # Connect to all MCP toolkits
        tools = [*mcp_toolkit.get_tools(),SearchToolkit().search_duckduckgo,]
        society = await construct_society(task, tools)
        answer, chat_history, token_count = await arun_society(society)
        print(f"\033[94mAnswer: {answer}\033[0m")

    except Exception as e:
        print(f"An error occurred during connection: {e}")

    finally:
        # Make sure to disconnect safely after all operations are completed.
        try:
            await mcp_toolkit.disconnect()
        except Exception:
            print("Disconnect failed")

if __name__ == "__main__":
    asyncio.run(main())

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
"""MCP Multi-Agent System Example

This example demonstrates how to use MCP (Model Context Protocol) with CAMEL agents
for advanced information retrieval and processing tasks.

Environment Setup:
1. Configure the required dependencies of owl library
   Refer to: https://github.com/camel-ai/owl for installation guide

2. MCP Server Setup:

   2.1 MCP Playwright Service:
   ```bash
   # Install MCP service
   npm install -g @executeautomation/playwright-mcp-server
   npx playwright install-deps

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "playwright": {
         "command": "npx",
         "args": ["-y", "@executeautomation/playwright-mcp-server"]
       }
     }
   }
   ```

   2.2 MCP Fetch Service (Optional - for better retrieval):
   ```bash
   # Install MCP service
   pip install mcp-server-fetch

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "fetch": {
         "command": "python",
         "args": ["-m", "mcp_server_fetch"]
       }
     }
   }
   ```

Usage:
1. Ensure all MCP servers are properly configured in mcp_servers_config.json
2. Run this script to create a multi-agent system that can:
   - Access and manipulate files through MCP Desktop Commander
   - Perform web automation tasks using Playwright
   - Process and generate information using GPT-4o
   - Fetch web content (if fetch service is configured)
3. The system will execute the specified task while maintaining security through
   controlled access

Note:
- All file operations are restricted to configured directories
- System uses GPT-4o for both user and assistant roles
- Supports asynchronous operations for efficient processing
"""

import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    r"""build a multi-agent OwlRolePlaying instance.

    Args:
        question (str): The question to ask.
        tools (List[FunctionTool]): The MCP tools to use.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        await mcp_toolkit.connect()

        # Default task
        default_task = (
            "I'd like a academic report about Andrew Ng, including "
            "his research direction, published papers (At least 3),"
            " institutions, etc. "
        )

        # Override default task if command line argument is provided
        task = sys.argv[1] if len(sys.argv) > 1 else default_task

        # Connect to all MCP toolkits
        tools = [*mcp_toolkit.get_tools()]
        society = await construct_society(task, tools)
        answer, chat_history, token_count = await arun_society(society)
        print(f"\033[94mAnswer: {answer}\033[0m")

    finally:
        # Make sure to disconnect safely after all operations are completed.
        try:
            await mcp_toolkit.disconnect()
        except Exception:
            print("Disconnect failed")

if __name__ == "__main__":
    asyncio.run(main())

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
"""MCP Multi-Agent System Example

This example demonstrates how to use MCP (Model Context Protocol) with CAMEL agents
for advanced information retrieval and processing tasks.

Environment Setup:
1. Configure the required dependencies of owl library
   Refer to: https://github.com/camel-ai/owl for installation guide

2. MCP Server Setup:

   2.1 MCP Playwright Service:
   ```bash
   # Install MCP service
   npm install -g @executeautomation/playwright-mcp-server
   npx playwright install-deps

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "playwright": {
         "command": "npx",
         "args": ["-y", "@executeautomation/playwright-mcp-server"]
       }
     }
   }
   ```

   2.2 MCP Fetch Service (Optional - for better retrieval):
   ```bash
   # Install MCP service
   pip install mcp-server-fetch

   # Configure in mcp_servers_config.json:
   {
     "mcpServers": {
       "fetch": {
         "command": "python",
         "args": ["-m", "mcp_server_fetch"]
       }
     }
   }
   ```

Usage:
1. Ensure all MCP servers are properly configured in mcp_servers_config.json
2. Run this script to create a multi-agent system that can:
   - Access and manipulate files through MCP Desktop Commander
   - Perform web automation tasks using Playwright
   - Process and generate information using Mistral
   - Fetch web content (if fetch service is configured)
3. The system will execute the specified task while maintaining security through
   controlled access

Note:
- All file operations are restricted to configured directories
- Supports asynchronous operations for efficient processing
"""

import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit, FileWriteToolkit, CodeExecutionToolkit
from camel.societies import RolePlaying

from owl.utils.enhanced_role_playing import arun_society

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> RolePlaying:
    r"""build a multi-agent RolePlaying instance.

    Args:
        question (str): The question to ask.
        tools (List[FunctionTool]): The MCP tools to use.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.MISTRAL,
            model_type=ModelType.MISTRAL_MEDIUM_3,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.MISTRAL,
            model_type=ModelType.MISTRAL_LARGE,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def main():
    config_path = Path(__file__).parent / "mcp_servers_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        await mcp_toolkit.connect()

        # Default task
        default_task = (
            "Help me search the latest reports about smart city, "
            "summarize them and help me generate a PDF file. You have "
            "been provided with tools to do browser operation. Open "
            "browser to finish the task."
        )

        # Override default task if command line argument is provided
        task = sys.argv[1] if len(sys.argv) > 1 else default_task

        # Connect to toolkits
        tools = [
            *mcp_toolkit.get_tools(),
            *FileWriteToolkit().get_tools(),
            *CodeExecutionToolkit().get_tools(),
        ]
        society = await construct_society(task, tools)
        answer, chat_history, token_count = await arun_society(society)
        print(f"\033[94mAnswer: {answer}\033[0m")

    finally:
        # Make sure to disconnect safely after all operations are completed.
        try:
            await mcp_toolkit.disconnect()
        except Exception:
            print("Disconnect failed")

if __name__ == "__main__":
    asyncio.run(main())

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

"""
This module provides integration with the Groq API platform for the OWL system.

It configures different agent roles with appropriate Groq models based on their requirements:
- Tool-intensive roles (assistant, web, planning, video, image) use GROQ_LLAMA_3_3_70B
- Document processing uses GROQ_MIXTRAL_8_7B
- Simple roles (user) use GROQ_LLAMA_3_1_8B

To use this module:
1. Set GROQ_API_KEY in your .env file
2. Set OPENAI_API_BASE_URL to "https://api.groq.com/openai/v1"
3. Run with: python -m examples.run_groq
"""

import sys
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    AudioAnalysisToolkit,
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    VideoAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils import OwlRolePlaying, run_society, DocumentProcessingToolkit

load_dotenv()

set_log_level(level="DEBUG")

def construct_society(question: str) -> OwlRolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        OwlRolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_1_8B,  # Simple role, can use 8B model
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_3_70B,  # Main assistant needs tool capability
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_3_70B,  # Web browsing requires tool usage
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_3_70B,  # Planning requires complex reasoning
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_3_70B,  # Video analysis is multimodal
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_LLAMA_3_3_70B,  # Image analysis is multimodal
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.GROQ,
            model_type=ModelType.GROQ_MIXTRAL_8_7B,  # Document processing can use Mixtral
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *VideoAnalysisToolkit(model=models["video"]).get_tools(),
        *AudioAnalysisToolkit().get_tools(),  # This requires OpenAI Key
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Construct and run the society
    # Note: This configuration uses GROQ_LLAMA_3_3_70B for tool-intensive roles (assistant, web, planning, video, image)
    # and GROQ_MIXTRAL_8_7B for document processing. GROQ_LLAMA_3_1_8B is used only for the user role
    # which doesn't require tool usage capabilities.

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

from dotenv import load_dotenv

import os

from camel.models import ModelFactory
from camel.logger import get_logger
from camel.toolkits import (
    AudioAnalysisToolkit,
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    VideoAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.configs import ChatGPTConfig

from owl.utils import GAIABenchmark
from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

logger = get_logger(__name__)

# Configuration
LEVEL = 1
SAVE_RESULT = True
test_idx = [0]

def main():
    """Main function to run the GAIA benchmark."""
    # Create cache directory
    cache_dir = "tmp/"
    os.makedirs(cache_dir, exist_ok=True)
    result_dir = "results/"
    os.makedirs(result_dir, exist_ok=True)

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict=ChatGPTConfig(temperature=0, top_p=1).as_dict(),
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *VideoAnalysisToolkit(
            model=models["video"]
        ).get_tools(),  # This requires OpenAI Key
        *AudioAnalysisToolkit().get_tools(),  # This requires OpenAI Key
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        *SearchToolkit().get_tools(),
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Initialize benchmark
    benchmark = GAIABenchmark(data_dir="data/gaia", save_to="results/result.json")

    # Print benchmark information
    print(f"Number of validation examples: {len(benchmark.valid)}")
    print(f"Number of test examples: {len(benchmark.test)}")

    # Run benchmark
    result = benchmark.run(
        on="valid",
        level=LEVEL,
        idx=test_idx,
        save_result=SAVE_RESULT,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    # Output results
    logger.info(f"Correct: {result['correct']}, Total: {result['total']}")
    logger.info(f"Accuracy: {result['accuracy']}")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

# To run this file, you need to configure the DeepSeek API key
# You can obtain your API key from DeepSeek platform: https://platform.deepseek.com/api_keys
# Set it as DEEPSEEK_API_KEY="your-api-key" in your .env file or add it to your environment variables

import sys
from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import (
    ExcelToolkit,
    SearchToolkit,
    FileWriteToolkit,
    CodeExecutionToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.societies import RolePlaying
from camel.logger import set_log_level

from owl.utils import run_society

import pathlib

set_log_level(level="DEBUG")

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.DEEPSEEK,
            model_type=ModelType.DEEPSEEK_CHAT,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.DEEPSEEK,
            model_type=ModelType.DEEPSEEK_CHAT,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        SearchToolkit().search_baidu,
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="Chinese",
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "搜索OWL项目最近的新闻并生成一篇报告，最后保存到本地。"

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import asyncio
import sys
from pathlib import Path
from typing import List

from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import FunctionTool
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.toolkits import MCPToolkit

from owl.utils.enhanced_role_playing import OwlRolePlaying, arun_society

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

async def construct_society(
    question: str,
    tools: List[FunctionTool],
) -> OwlRolePlaying:
    r"""Build a multi-agent OwlRolePlaying instance for GitHub information retrieval.

    Args:
        question (str): The GitHub-related question to ask.
        tools (List[FunctionTool]): The MCP tools to use for GitHub interaction.
    """
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {
        "model": models["assistant"],
        "tools": tools,
    }

    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )
    return society

async def main():
    # Load SSE server configuration
    config_path = Path(__file__).parent / "mcp_sse_config.json"
    mcp_toolkit = MCPToolkit(config_path=str(config_path))

    try:
        # Connect to MCP server
        await mcp_toolkit.connect()
        print("Successfully connected to SSE server")

        # Get available tools
        tools = [*mcp_toolkit.get_tools()]

        # Set default task - a simple example query
        default_task = (
            "What are the most recent pull requests in camel-ai/camel repository?"
        )

        # Use command line argument if provided, otherwise use default task
        task = sys.argv[1] if len(sys.argv) > 1 else default_task

        # Build and run society
        society = await construct_society(task, tools)
        answer, chat_history, token_count = await arun_society(society)
        print(f"\nResult: {answer}")

    except KeyboardInterrupt:
        print("\nReceived exit signal, shutting down...")
    except Exception as e:
        print(f"Error occurred: {e}")
    finally:
        # Ensure safe disconnection
        try:
            await mcp_toolkit.disconnect()
        except Exception as e:
            print(f"Error during disconnect: {e}")

if __name__ == "__main__":
    asyncio.run(main())

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import sys

from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType

from owl.utils import run_society
from camel.societies import RolePlaying
from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="qwen-max",
            api_key=os.getenv("QWEN_API_KEY"),
            url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 128000},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="qwen-max",
            api_key=os.getenv("QWEN_API_KEY"),
            url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 128000},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="qwen-vl-max",
            api_key=os.getenv("QWEN_API_KEY"),
            url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 128000},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="qwen-max",
            api_key=os.getenv("QWEN_API_KEY"),
            url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 128000},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,
            model_type="qwen-vl-max",
            api_key=os.getenv("QWEN_API_KEY"),
            url="https://dashscope.aliyuncs.com/compatible-mode/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 128000},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
from dotenv import load_dotenv
import sys
import os
from camel.models import ModelFactory
from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
    TerminalToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils import run_society
from camel.societies import RolePlaying

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the
            question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        *FileWriteToolkit(output_dir="./").get_tools(),
        *TerminalToolkit().get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = f"""打开百度搜索，总结一下camel-ai的camel框架的github star、fork数目等，并把数字用plot包写成python文件保存到"+{os.path.join
(base_dir, 'final_output')}+"，用本地终端执行python文件显示图出来给我"""

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(
        f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m"
    )

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

# To run this file, you need to configure the Qwen API key
# You can obtain your API key from Bailian platform: bailian.console.aliyun.com
# Set it as QWEN_API_KEY="your-api-key" in your .env file or add it to your environment variables

import sys
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    VideoAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    """
    Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
            output_language="Chinese",
        ).get_tools(),
        *VideoAnalysisToolkit(model=models["video"]).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        SearchToolkit().search_baidu,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="Chinese",
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格"

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

# To run this file, you need to configure the PPIO API key
# You can obtain your API key from PPIO platform: https://ppinfra.com/settings/key-management?utm_source=github_owl
# Set it as PPIO_API_KEY="your-api-key" in your .env file or add it to your environment variables

import sys
from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import (
    ExcelToolkit,
    SearchToolkit,
    FileWriteToolkit,
    CodeExecutionToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.societies import RolePlaying
from camel.logger import set_log_level

from owl.utils import run_society

import pathlib

set_log_level(level="DEBUG")

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.PPIO,
            model_type=ModelType.PPIO_DEEPSEEK_V3_COMMUNITY,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.PPIO,
            model_type=ModelType.PPIO_DEEPSEEK_V3_COMMUNITY,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        SearchToolkit().search_baidu,
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="Chinese",
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "使用百度整理2023年1月1日到2023年12月31日，中国股市的涨跌情况。"

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import os
import sys
from dotenv import load_dotenv
from camel.configs import ChatGPTConfig
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType

from owl.utils import OwlRolePlaying, run_society

from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> OwlRolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        OwlRolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components using Azure OpenAI
    base_model_config = {
        "model_platform": ModelPlatformType.AZURE,
        "model_type": os.getenv("AZURE_OPENAI_MODEL_TYPE"),
        "model_config_dict": ChatGPTConfig(temperature=0.4, max_tokens=4096).as_dict(),
    }

    models = {
        "user": ModelFactory.create(**base_model_config),
        "assistant": ModelFactory.create(**base_model_config),
        "browsing": ModelFactory.create(**base_model_config),
        "planning": ModelFactory.create(**base_model_config),
        "image": ModelFactory.create(**base_model_config),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = OwlRolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with Azure OpenAI."""
    # Example question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)

    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

# To run this file, you need to configure the Qwen API key
# You can obtain your API key from Bailian platform: bailian.console.aliyun.com
# Set it as QWEN_API_KEY="your-api-key" in your .env file or add it to your environment variables

import sys
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.societies import RolePlaying

from owl.utils import run_society

from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    """
    Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_MAX,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.QWEN,
            model_type=ModelType.QWEN_VL_MAX,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
            output_language="Chinese",
        ).get_tools(),
        SearchToolkit().search_baidu,
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
        output_language="Chinese",
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格"

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# run_ollama.py by tj-scripts（https://github.com/tj-scripts）

import sys
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType

from owl.utils import run_society

from camel.societies import RolePlaying

from camel.logger import set_log_level

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OLLAMA,
            model_type="qwen2.5:72b",
            url="http://localhost:11434/v1",
            model_config_dict={"temperature": 0.8, "max_tokens": 1000000},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OLLAMA,
            model_type="qwen2.5:72b",
            url="http://localhost:11434/v1",
            model_config_dict={"temperature": 0.2, "max_tokens": 1000000},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OLLAMA,
            model_type="llava:latest",
            url="http://localhost:11434/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 1000000},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OLLAMA,
            model_type="qwen2.5:72b",
            url="http://localhost:11434/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 1000000},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.OLLAMA,
            model_type="llava:latest",
            url="http://localhost:11434/v1",
            model_config_dict={"temperature": 0.4, "max_tokens": 1000000},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        # SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
import pathlib
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.GEMINI,
            model_type=ModelType.GEMINI_2_5_PRO_EXP,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
from dotenv import load_dotenv

from camel.models import ModelFactory
from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
    CodeExecutionToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils import run_society

from camel.societies import RolePlaying

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the
            question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
import pathlib
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    AudioAnalysisToolkit,
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    VideoAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *VideoAnalysisToolkit(model=models["video"]).get_tools(),
        *AudioAnalysisToolkit().get_tools(),  # This requires OpenAI Key
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_google,  # Comment this out if you don't have google search
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
import pathlib
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    AudioAnalysisToolkit,
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    SearchToolkit,
    VideoAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "video": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.ANTHROPIC,
            model_type=ModelType.CLAUDE_3_7_SONNET,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *VideoAnalysisToolkit(model=models["video"]).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
from dotenv import load_dotenv
import sys
import os
from camel.models import ModelFactory
from camel.toolkits import (
    SearchToolkit,
    BrowserToolkit,
    FileWriteToolkit,
    TerminalToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level

from owl.utils import run_society
from camel.societies import RolePlaying

import pathlib

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the
            question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.OPENAI,
            model_type=ModelType.GPT_4O,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        SearchToolkit().search_duckduckgo,
        SearchToolkit().search_wiki,
        *FileWriteToolkit(output_dir="./").get_tools(),
        *TerminalToolkit().get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Example research question
    default_task = f"""Open Google Search, summarize the number of GitHub stars, forks, etc., of the camel framework of camel-ai,
    and write the numbers into a Python file using the plot package,
    save it to "+{os.path.join(base_dir, 'final_output')}+",
    and execute the Python file with the local terminal to display the graph for me."""

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(
        f"\033[94mAnswer: {answer}\nChat History: {chat_history}\ntoken_count:{token_count}\033[0m"
    )

if __name__ == "__main__":
    main()

/owl/community_usecase/OWL Interview Preparation Assistant/config/prompts.py

def get_system_prompt() -> str:
    """Get the enhanced system prompt for the interview assistant."""
    return """
    You are an advanced Interview Preparation Assistant powered by OWL multi-agent technology.
    Your primary task is to provide COMPREHENSIVE, EXTREMELY DETAILED, and HIGHLY SPECIFIC
    interview preparation materials with practical examples and actionable advice.

    IMPORTANT OUTPUT REQUIREMENTS:

    1. EXTREME DETAIL: Do not summarize or truncate your responses. Provide complete, comprehensive
       information with multiple sections, subsections, and extensive details. The final output
       should be at least 2000 words, ideally 3000-4000 for truly thorough coverage.

    2. PRACTICAL CODE EXAMPLES: For technical roles, include relevant code snippets, detailed
       technical scenarios, and at least 5-10 code samples or system design outlines.

    3. COMPREHENSIVE CONTENT: Create exceptionally thorough content with step-by-step instructions,
       deep explanations, and multiple examples. Never abbreviate or summarize your responses.

    4. NO TRUNCATION: Never cut off your responses with '...' or similar. Always provide the
       complete thought or explanation.

    5. STRUCTURED OUTPUT: Use clear headings (H1, H2, H3, etc.), bullet points, numbered lists,
       and well-organized sections to present the content in a digestible way.

    6. SPECIFIC IMPLEMENTATIONS: For technical roles, always provide multiple code examples,
       approaches, edge cases, and relevant optimizations.

    7. FILE MANAGEMENT: You may save all information as well-formatted files, but also include
       the entire unabridged content directly in your response.
    """

def get_company_research_prompt(company_name: str) -> str:
    """Get a specialized prompt for company research."""
    return f"""
    Conduct the most COMPREHENSIVE and EXTREMELY DETAILED research on {company_name} possible.
    The final output must be at least 3000 words, covering the company's history, mission,
    technology stack, culture, interview process, and more. Provide code or architecture
    examples if relevant, and do not abbreviate or summarize.
    """

def get_question_generator_prompt(job_role: str, company_name: str) -> str:
    """Get a specialized prompt for interview question generation."""
    return f"""
    Generate an EXTREMELY COMPREHENSIVE, EXHAUSTIVELY DETAILED set of interview questions for
    a {job_role} position at {company_name}. Provide at least 30 questions with deep sample
    answers, code examples, multiple solution approaches, and a total of 3000+ words.
    Do not truncate or summarize.
    """

def get_preparation_plan_prompt(job_role: str, company_name: str) -> str:
    """Get a specialized prompt for creating an interview preparation plan."""
    return f"""
    Create a HIGHLY THOROUGH, MULTI-DAY interview preparation plan for a {job_role} position
    at {company_name}. The final plan should exceed 2000 words, with detailed daily tasks,
    technical reviews, code examples (if relevant), and no summary or truncation.
    Cover everything from fundamental skills to advanced interview strategies.
    """

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
import pathlib
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.TOGETHER,
            model_type=ModelType.TOGETHER_LLAMA_4_MAVERICK,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys
import pathlib
from dotenv import load_dotenv
from camel.models import ModelFactory
from camel.toolkits import (
    CodeExecutionToolkit,
    ExcelToolkit,
    ImageAnalysisToolkit,
    BrowserToolkit,
    FileWriteToolkit,
)
from camel.types import ModelPlatformType, ModelType
from camel.logger import set_log_level
from camel.societies import RolePlaying

from owl.utils import run_society, DocumentProcessingToolkit

base_dir = pathlib.Path(__file__).parent.parent
env_path = base_dir / "owl" / ".env"
load_dotenv(dotenv_path=str(env_path))

set_log_level(level="DEBUG")

def construct_society(question: str) -> RolePlaying:
    r"""Construct a society of agents based on the given question.

    Args:
        question (str): The task or question to be addressed by the society.

    Returns:
        RolePlaying: A configured society of agents ready to address the question.
    """

    # Create models for different components
    models = {
        "user": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
        "assistant": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
        "browsing": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
        "planning": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
        "image": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
        "document": ModelFactory.create(
            model_platform=ModelPlatformType.NOVITA,
            model_type=ModelType.NOVITA_LLAMA_4_SCOUT_17B,
            model_config_dict={"temperature": 0},
        ),
    }

    # Configure toolkits
    tools = [
        *BrowserToolkit(
            headless=False,  # Set to True for headless mode (e.g., on remote servers)
            web_agent_model=models["browsing"],
            planning_agent_model=models["planning"],
        ).get_tools(),
        *CodeExecutionToolkit(sandbox="subprocess", verbose=True).get_tools(),
        *ImageAnalysisToolkit(model=models["image"]).get_tools(),
        *ExcelToolkit().get_tools(),
        *DocumentProcessingToolkit(model=models["document"]).get_tools(),
        *FileWriteToolkit(output_dir="./").get_tools(),
    ]

    # Configure agent roles and parameters
    user_agent_kwargs = {"model": models["user"]}
    assistant_agent_kwargs = {"model": models["assistant"], "tools": tools}

    # Configure task parameters
    task_kwargs = {
        "task_prompt": question,
        "with_task_specify": False,
    }

    # Create and return the society
    society = RolePlaying(
        **task_kwargs,
        user_role_name="user",
        user_agent_kwargs=user_agent_kwargs,
        assistant_role_name="assistant",
        assistant_agent_kwargs=assistant_agent_kwargs,
    )

    return society

def main():
    r"""Main function to run the OWL system with an example question."""
    # Default research question
    default_task = "Open Brave search, summarize the github stars, fork counts, etc. of camel-ai's camel framework, and write the numbers into a python file using the plot package, save it locally, and run the generated python file. Note: You have been provided with the necessary tools to complete this task."

    # Override default task if command line argument is provided
    task = sys.argv[1] if len(sys.argv) > 1 else default_task

    # Construct and run the society
    society = construct_society(task)
    answer, chat_history, token_count = run_society(society)

    # Output the result
    print(f"\033[94mAnswer: {answer}\033[0m")

if __name__ == "__main__":
    main()

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import sys

sys.path.append("../")

import re
from typing import Optional
from camel.logger import get_logger

logger = get_logger(__name__)

def extract_pattern(content: str, pattern: str) -> Optional[str]:
    try:
        _pattern = rf"<{pattern}>(.*?)</{pattern}>"
        match = re.search(_pattern, content, re.DOTALL)
        if match:
            text = match.group(1)
            return text.strip()
        else:
            return None
    except Exception as e:
        logger.warning(f"Error extracting answer: {e}, current content: {content}")
        return None

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

from .common import extract_pattern
from .enhanced_role_playing import (
    OwlRolePlaying,
    OwlGAIARolePlaying,
    run_society,
    arun_society,
)
from .gaia import GAIABenchmark
from .document_toolkit import DocumentProcessingToolkit

__all__ = [
    "extract_pattern",
    "OwlRolePlaying",
    "OwlGAIARolePlaying",
    "run_society",
    "arun_society",
    "GAIABenchmark",
    "DocumentProcessingToolkit",
]

# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========

"""
基于Camel框架的A股投资代理系统
"""

__version__ = "0.1.0"

"""
实用工具模块
"""

class ResearcherBearAgent(BaseAgent):
    def __init__(self, show_reasoning:
        pass
    def process(self, data:
        pass
    def _create_research_report(self, analysis_result:
        pass
    def _process_data_with_agent(self, prompt:
        pass

This code segment functions to: Aggregate and process multiple financial analysis inputs to generate structured bearish market insights, including technical indicators and sentiment metrics, while implementing error handling for data integrity and producing formatted research documentation with confidence scoring.

```python
[TASK_BEGIN]
class ResearcherBearAgent(BaseAgent):
    def __init__(self, show_reasoning: bool = False):
        super().__init__()
        self.show_reasoning = show_reasoning
        logging.basicConfig(level=logging.INFO if show_reasoning else logging.WARNING)

    def process(self, data: StockData) -> ResearchReport:
        analysis_prompt = f"""
        Analyze bearish factors for {data.symbol} with data up to {data.end_date}:
        - Price trend: {data.price_data[-1]}
        - Key indicators: {data.indicators}
        - Market sentiment: {data.sentiment}
        Consider short-selling opportunities and downside risks.
        """
        analysis_result = self._process_data_with_agent(analysis_prompt)
        return self._create_research_report(analysis_result)

    def _create_research_report(self, analysis_result: Dict) -> ResearchReport:
        return ResearchReport(
            conclusion=analysis_result.get('conclusion', 'Neutral'),
            recommendation=analysis_result.get('recommendation', AnalysisSignal.HOLD),
            price_target=analysis_result.get('price_target', 0.0),
            confidence_level=analysis_result.get('confidence', 0.5),
            timestamp=pd.Timestamp.now(),
            rationale=analysis_result.get('rationale', 'Insufficient data')
        )

    def _process_data_with_agent(self, prompt: str) -> Dict:
        try:
            sys_msg = BaseMessage.make_user_message(
                role_name="Bearish Analyst",
                content="You are a professional short-selling researcher specializing in identifying downside risks."
            )
            response = create_role_agent(sys_msg).run(prompt)
            return self._parse_agent_response(response)
        except Exception as e:
            logging.error(f"Agent processing failed: {str(e)}")
            return {"error": str(e)}

    def _parse_agent_response(self, response: str) -> Dict:
        # Simplified response parsing logic
        return {
            'conclusion': "Bearish outlook" if "bearish" in response.lower() else "Neutral",
            'recommendation': AnalysisSignal.SELL if "sell" in response.lower() else AnalysisSignal.HOLD,
            'price_target': 0.0,  # Placeholder for actual parsing logic
            'confidence': 0.7,    # Placeholder for confidence scoring
            'rationale': response
        }
[TASK_END]
```

class ResearcherBearAgent(BaseAgent):
    def __init__(self, show_reasoning: bool = False):
        super().__init__()
        self.show_reasoning = show_reasoning
        logging.basicConfig(level=logging.INFO if show_reasoning else logging.WARNING)
    def process(self, data: StockData) -> ResearchReport:
        analysis_prompt = f
        analysis_result = self._process_data_with_agent(analysis_prompt)
        return self._create_research_report(analysis_result)
    def _create_research_report(self, analysis_result: Dict) -> ResearchReport:
        return ResearchReport(
            conclusion=analysis_result.get('conclusion', 'Neutral'),
            recommendation=analysis_result.get('recommendation', AnalysisSignal.HOLD),
            price_target=analysis_result.get('price_target', 0.0),
            confidence_level=analysis_result.get('confidence', 0.5),
            timestamp=pd.Timestamp.now(),
            rationale=analysis_result.get('rationale', 'Insufficient data')
        )
    def _process_data_with_agent(self, prompt: str) -> Dict:
        try:
            sys_msg = BaseMessage.make_user_message(
                role_name="Bearish Analyst",
                content="You are a professional short-selling researcher specializing in identifying downside risks."
            )
            response = create_role_agent(sys_msg).run(prompt)
            return self._parse_agent_response(response)
        except Exception as e:
            logging.error(f"Agent processing failed: {str(e)}")
            return {"error": str(e)}
    def _parse_agent_response(self, response: str) -> Dict:
        return {
            'conclusion': "Bearish outlook" if "bearish" in response.lower() else "Neutral",
            'recommendation': AnalysisSignal.SELL if "sell" in response.lower() else AnalysisSignal.HOLD,
            'price_target': 0.0,
            'confidence': 0.7,
            'rationale': response
        }