1 line
67 KiB
JSON
1 line
67 KiB
JSON
{"repo": "blackeagle686/phoenix-ai", "files": {".env.example": {"ed576e37f50bdedaec571b603d984f6814ec099e": {"t": 1785604441.254423, "n": 897, "h": "ffb8aae0bb42f070", "c": "# 🐦🔥 Phoenix AI - Environment Configuration Template\n\n# --- LLM Provider (OpenAI/LongCat/etc) ---\n# Default is OpenAI. If using LongCat, change BASE_URL and MODEL.\nOPENAI_API_KEY=\"\"\nOPENAI_BASE_URL=\"https://api.openai.com/v1\"\nOPENAI_LLM_MODEL=\"gpt-4o\"\n\n# --- Infrastructure ---\nREDIS_URL=\"redis://localhost:6379/0\"\nVECTOR_DB_TYPE=\"chroma\" # \"chroma\" or \"qdrant\"\nCHROMA_PERSIST_DIR=\"./chroma_db\"\nQDRANT_URL=\"http://localhost:6333\"\n\n# --- Local Models (Requires [full] installation) ---\n# Set these to \"true\" to load local models into memory\nLOAD_LOCAL_LLM=false\nLOAD_LOCAL_VLM=false\nLOCAL_LLM_TEXT_MODEL=\"Qwen/Qwen2-1.5B-Instruct\"\nLOCAL_VLM_MODEL=\"Qwen/Qwen2-VL-2B-Instruct\"\n\n# --- RAG & Search ---\nRAG_QUERY_EXPANSION=true\nRAG_HYDE_ENABLED=true\nRAG_MMR_ENABLED=true\nRAG_MAX_CONTEXT_CHUNKS=5\n\n# --- Security ---\nSECURITY_MAX_INPUT_LENGTH=4096\nSECURITY_ENABLE_HALLUCINATION_CHECK=false\n"}}, "__init__.py": {"d10a34d7d7a10c0fe6c4a05f1d8e7d22ce4b2a97": {"t": 1785604446.2265124, "n": 416, "h": "d919cd2a93cba73e", "c": "from .phoenix import (\n\tinit_phoenix,\n\tstartup_phoenix,\n\tinit_phoenix_full,\n\tget_rag_pipeline,\n\tset_providers,\n\tget_providers,\n\tget_insight_engine,\n\tget_vlm_pipeline,\n\tget_llm,\n\tget_memory,\n\tChatBot,\n)\n\n__all__ = [\n\t\"init_phoenix\",\n\t\"startup_phoenix\",\n\t\"init_phoenix_full\",\n\t\"get_rag_pipeline\",\n\t\"set_providers\",\n\t\"get_providers\",\n\t\"get_insight_engine\",\n\t\"get_vlm_pipeline\",\n\t\"get_llm\",\n\t\"get_memory\",\n\t\"ChatBot\",\n]\n"}}, "custom_math.py": {"5b8a155d4d2028accfde60eec76d78c03ad60e46": {"t": 1785604448.2481394, "n": 400, "h": "b867ce9abbf340da", "c": "def sum(a, b):\n \"\"\"Return the sum of two numbers.\"\"\"\n return a + b\n\ndef subtract(a, b):\n \"\"\"Return the difference of two numbers (a - b).\"\"\"\n return a - b\n\ndef multiply(a, b):\n \"\"\"Return the product of two numbers.\"\"\"\n return a * b\n\ndef divide(a, b):\n \"\"\"Return the quotient of a divided by b.\"\"\"\n if b == 0:\n raise ValueError(\"Cannot divide by zero\")\n return a / b\n"}}, "install.sh": {"362f965c6cb2eb7d5931789466b6669e8c733bc4": {"t": 1785604453.228496, "n": 1127, "h": "a75ae2f7fb3b19ee", "c": "#!/bin/bash\necho \"🐦🔥 Initializing Phoenix AI Installation...\"\n\n# Install Python dependencies\necho \"📦 Installing Python dependencies...\"\npip install -r requirements.txt\n\n# System Dependencies (Redis)\nif [[ \"$OSTYPE\" == \"linux-gnu\"* ]]; then\n echo \"🔍 Checking for Redis...\"\n if ! command -v redis-server &> /dev/null; then\n echo \"📥 Installing Redis Server...\"\n sudo apt update && sudo apt install redis-server -y\n fi\n \n echo \"⚙️ Starting Redis in daemon mode...\"\n sudo redis-server --daemonize yes\n \n # Verify Redis is running\n REDIS_STATUS=$(redis-cli ping)\n if [ \"$REDIS_STATUS\" == \"PONG\" ]; then\n echo \"✅ Redis is active (PONG).\"\n else\n echo \"⚠️ Redis installation finished but server failed to start. Please check logs.\"\n fi\nelif [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n echo \"🔍 Checking for Redis (macOS)...\"\n if ! command -v redis-server &> /dev/null; then\n echo \"📥 Installing Redis via Homebrew...\"\n brew install redis\n fi\n echo \"⚙️ Starting Redis service...\"\n brew services start redis\nfi\n\necho \"✅ Phoenix AI Installation Complete!\"\n"}}, "phoenix_api.py": {"7bb5299d24b42462024c2b29ef92eb4add9f4e39": {"t": 1785604454.6545813, "n": 2953, "h": "d46777f4d62a4537", "c": "from fastapi import FastAPI, File, UploadFile, Form, HTTPException\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom typing import Optional\nimport uvicorn\nimport os\nimport shutil\nfrom phoenix import ChatBot, init_phoenix_full\n\napp = FastAPI(title=\"Phoenix AI API\", version=\"1.0.0\")\n\n# Enable CORS for Flutter, .NET, and Web apps\napp.add_middleware(\n CORSMiddleware,\n allow_origins=[\"*\"],\n allow_credentials=True,\n allow_methods=[\"*\"],\n allow_headers=[\"*\"],\n)\n\n# Global ChatBot instance\nbot = None\n\n@app.on_event(\"startup\")\nasync def startup_event():\n global bot\n print(\"🐦🔥 Initializing Phoenix AI Services...\")\n await init_phoenix_full()\n \n # Initialize the high-level ChatBot (uses env vars automatically)\n bot = (ChatBot(local=False, vlm=True)\n .with_memory()\n .build())\n print(\"🐦🔥 Phoenix AI API is ready.\")\n\n@app.post(\"/chat\")\nasync def chat(\n prompt: str = Form(...),\n session_id: Optional[str] = Form(None),\n image: Optional[UploadFile] = File(None),\n audio: Optional[UploadFile] = File(None)\n):\n \"\"\"\n Unified /chat endpoint for Text, Vision, and Audio interactions.\n Compatible with Flutter, .NET, and any HTTP client.\n \"\"\"\n try:\n image_path = None\n audio_path = None\n \n # Handle Image Upload\n if image:\n os.makedirs(\"./temp_uploads\", exist_ok=True)\n image_path = f\"./temp_uploads/{image.filename}\"\n with open(image_path, \"wb\") as buffer:\n shutil.copyfileobj(image.file, buffer)\n \n # Handle Audio Upload\n if audio:\n os.makedirs(\"./temp_uploads\", exist_ok=True)\n audio_path = f\"./temp_uploads/{audio.filename}\"\n with open(audio_path, \"wb\") as buffer:\n shutil.copyfileobj(audio.file, buffer)\n\n # Set session if provided\n if bot is None:\n raise HTTPException(status_code=503, detail=\"Phoenix AI Bot not initialized. Check your API keys and environment.\")\n\n if session_id:\n bot.set_session(session_id)\n \n # Execute chat\n response = await bot.chat(\n text=prompt,\n image_path=image_path,\n audio_path=audio_path\n )\n \n # Cleanup temp files\n if image_path and os.path.exists(image_path):\n os.remove(image_path)\n if audio_path and os.path.exists(audio_path):\n os.remove(audio_path)\n \n return {\n \"status\": \"success\",\n \"reply\": response,\n \"session_id\": session_id or \"new_session\"\n }\n \n except HTTPException:\n raise\n except Exception as e:\n raise HTTPException(status_code=500, detail=str(e))\n\n@app.get(\"/health\")\nasync def health_check():\n return {\"status\": \"alive\", \"service\": \"Phoenix AI\"}\n\nif __name__ == \"__main__\":\n uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n"}}, "pyproject.toml": {"16242767be26a7cbb8f93758bc12aa21164cb2a5": {"t": 1785604464.1880317, "n": 2210, "h": "4ea1ee38c8520f5e", "c": "[build-system]\nrequires = [\"setuptools>=61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"phx-ashborn\"\nversion = \"0.4.0\"\ndescription = \"Advanced AI Infrastructure SDK for Agentic Applications\"\nreadme = \"README.md\"\nrequires-python = \">=3.9\"\ndependencies = [\n \"python-dotenv>=1.0.0\",\n \"requests>=2.28.0\",\n \"openai>=1.0.0\",\n \"pydantic\"\n]\n\n[project.optional-dependencies]\nagent = [\n \"pydantic\",\n \"redis>=4.5.0\",\n \"celery>=5.3.0\",\n \"duckduckgo-search\",\n \"chromadb>=0.4.0\",\n \"qdrant-client>=1.6.0\"\n]\nchatbot = [\n \"markdown>=3.4.0\",\n \"beautifulsoup4>=4.11.0\",\n \"pypdf>=3.10.0\",\n \"python-docx>=0.8.11\",\n \"sqlalchemy>=2.0.0\",\n \"django>=5.0.0\",\n \"pandas\",\n \"openpyxl\",\n \"Pillow\",\n \"fastapi\",\n \"uvicorn\",\n \"python-multipart\"\n]\nsensorium = [\n \"pyserial>=3.5\",\n \"paho-mqtt>=1.6.0\",\n \"psutil\"\n]\nlocal = [\n \"torch\",\n \"transformers\",\n \"accelerate\",\n \"bitsandbytes\",\n \"sentence-transformers>=2.2.0\"\n]\naudio = [\n \"gTTS\",\n \"SpeechRecognition\",\n \"pydub\"\n]\nvector = [\n \"chromadb>=0.4.0\",\n \"qdrant-client>=1.6.0\"\n]\nrag = [\n \"chromadb>=0.4.0\",\n \"sentence-transformers>=2.2.0\",\n \"pypdf>=3.10.0\",\n \"python-docx>=0.8.11\",\n \"beautifulsoup4>=4.11.0\",\n \"sqlalchemy>=2.0.0\",\n \"httpx>=0.24.0\",\n \"google-api-python-client>=2.0.0\",\n \"google-auth-oauthlib>=1.0.0\",\n \"redis>=4.5.0\"\n]\nbrain = [\n \"maturin>=1.0.0\"\n]\nfull = [\n \"pydantic\",\n \"redis>=4.5.0\",\n \"celery>=5.3.0\",\n \"duckduckgo-search\",\n \"chromadb>=0.4.0\",\n \"qdrant-client>=1.6.0\",\n \"markdown>=3.4.0\",\n \"beautifulsoup4>=4.11.0\",\n \"pypdf>=3.10.0\",\n \"python-docx>=0.8.11\",\n \"sqlalchemy>=2.0.0\",\n \"django>=5.0.0\",\n \"pandas\",\n \"openpyxl\",\n \"Pillow\",\n \"fastapi\",\n \"uvicorn\",\n \"python-multipart\",\n \"pyserial>=3.5\",\n \"paho-mqtt>=1.6.0\",\n \"psutil\",\n \"torch\",\n \"transformers\",\n \"accelerate\",\n \"bitsandbytes\",\n \"sentence-transformers>=2.2.0\",\n \"gTTS\",\n \"SpeechRecognition\",\n \"pydub\",\n \"httpx>=0.24.0\",\n \"google-api-python-client>=2.0.0\",\n \"google-auth-oauthlib>=1.0.0\",\n \"maturin>=1.0.0\"\n]\n\n[tool.setuptools.packages.find]\ninclude = [\"phoenix*\"]\n"}}, "setup.py": {"1ec2505d0523d5784a34ee6c3393aba8a918af45": {"t": 1785604465.6163578, "n": 1895, "h": "96d8c001f0b10b6a", "c": "from setuptools import setup, find_packages\n\nEXTRAS = {\n \"agent\": [\n \"pydantic\",\n \"redis>=4.5.0\",\n \"celery>=5.3.0\",\n \"duckduckgo-search\",\n \"chromadb>=0.4.0\",\n \"qdrant-client>=1.6.0\"\n ],\n \"chatbot\": [\n \"markdown>=3.4.0\",\n \"beautifulsoup4>=4.11.0\",\n \"pypdf>=3.10.0\",\n \"python-docx>=0.8.11\",\n \"sqlalchemy>=2.0.0\",\n \"django>=5.0.0\",\n \"pandas\",\n \"openpyxl\",\n \"Pillow\",\n \"fastapi\",\n \"uvicorn\",\n \"python-multipart\"\n ],\n \"sensorium\": [\n \"pyserial>=3.5\",\n \"paho-mqtt>=1.6.0\",\n \"psutil\"\n ],\n \"local\": [\n \"torch\",\n \"transformers\",\n \"accelerate\",\n \"bitsandbytes\",\n \"sentence-transformers>=2.2.0\"\n ],\n \"audio\": [\n \"gTTS\",\n \"SpeechRecognition\",\n \"pydub\"\n ],\n \"vector\": [\n \"chromadb>=0.4.0\",\n \"qdrant-client>=1.6.0\"\n ],\n \"rag\": [\n \"chromadb>=0.4.0\",\n \"sentence-transformers>=2.2.0\",\n \"pypdf>=3.10.0\",\n \"python-docx>=0.8.11\",\n \"beautifulsoup4>=4.11.0\",\n \"sqlalchemy>=2.0.0\",\n \"httpx>=0.24.0\",\n \"google-api-python-client>=2.0.0\",\n \"google-auth-oauthlib>=1.0.0\",\n \"redis>=4.5.0\"\n ],\n \"brain\": [\n \"maturin>=1.0.0\"\n ]\n}\n\nEXTRAS[\"full\"] = list(set(dep for deps in EXTRAS.values() for dep in deps))\n\nsetup(\n name=\"phx-ashborn\",\n version=\"0.4.0\",\n packages=find_packages(),\n install_requires=[\n \"python-dotenv>=1.0.0\",\n \"requests>=2.28.0\",\n \"openai>=1.0.0\",\n \"pydantic\"\n ],\n extras_require=EXTRAS,\n author=\"blackeagle686\",\n description=\"Advanced AI Infrastructure SDK for Agentic Applications\",\n long_description=open(\"README.md\").read(),\n long_description_content_type=\"text/markdown\",\n python_requires=\">=3.9\",\n)\n"}}, "update_schema.py": {"ff31815dbe4a58644607c3edc608cf6aad8c4a1d": {"t": 1785604467.0060525, "n": 4963, "h": "4a4d10e9dfe8e5f4", "c": "import re\n\nwith open('phoenix/framework/agent/cognition/planner/schema.py', 'r') as f:\n content = f.read()\n\n# We'll use regex and string replacement to update the schemas.\n# First, remove BaseTaskInputSchema and BaseTaskMeta since we'll redefine them.\ncontent = re.sub(r'class BaseTaskInputSchema\\(BaseModel\\):.*?file_meta: BaseFileMeta = Field\\(\\.\\.\\., description=\"Meta information of the file\"\\)', '', content, flags=re.DOTALL)\ncontent = re.sub(r'class BaseTaskMeta\\(BaseModel\\):.*?(?=\\nclass WriteTask)', '', content, flags=re.DOTALL)\n\n# Add our new base schemas after FileIOMeta\nnew_base_schemas = \"\"\"\nclass BaseTaskInputSchema(BaseModel):\n task_id: str = Field(..., description=\"Task ID\")\n task_description: str = Field(..., description=\"Task description\")\n task_type: TaskType = Field(..., description=\"Task type\")\n\nclass BaseTaskOutputSchema(BaseModel):\n task_id: str = Field(..., description=\"Task ID\")\n success: bool = Field(..., description=\"Whether the task execution was successful\")\n error: Optional[str] = Field(None, description=\"Error message if the task failed\")\n\nclass BaseFileTaskInputSchema(BaseTaskInputSchema):\n file_meta: BaseFileMeta = Field(..., description=\"Meta information of the file\")\n\nclass PlannerInputSchema(BaseModel):\n prompt: Prompt = Field(..., description=\"The user prompt and session details\")\n context: Optional[str] = Field(None, description=\"Additional context or memory for the planner\")\n existing_tasks: List[Task] = Field(default_factory=list, description=\"Current state of existing tasks\")\n previous_results: Optional[str] = Field(None, description=\"Results from previous actions or executions\")\n\nclass PlannerOutputSchema(BaseModel):\n plan_id: UUID = Field(default_factory=uuid4, description=\"Unique ID for this planner interaction\")\n response: str = Field(..., description=\"Conversational response or direct answer to the user\")\n problems: List[Problem] = Field(default_factory=list, description=\"Identified problems and complexities\")\n solutions: List[Solution] = Field(default_factory=list, description=\"Direct solutions, code snippets, or fast answers provided\")\n tasks: List[Task] = Field(default_factory=list, description=\"The ordered sequence of actionable tasks to execute if needed\")\n summary: str = Field(..., description=\"A high-level summary of the planner's reasoning and output\")\n\"\"\"\n\ncontent = content.replace(\"class FileIOMeta(BaseFileMeta):\", new_base_schemas + \"\\nclass FileIOMeta(BaseFileMeta):\")\n\n# Now update individual tasks\ncontent = content.replace(\"class FileReadTask(BaseModel):\", \"class FileReadTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class FileReadResult(BaseModel):\", \"class FileReadResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class FileWriteTask(BaseModel):\", \"class FileWriteTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class FileWriteResult(BaseModel):\", \"class FileWriteResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class FileSearchTask(BaseModel):\", \"class FileSearchTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class FileSearchResult(BaseModel):\", \"class FileSearchResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class FileUpdateTask(BaseModel):\", \"class FileUpdateTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class FileUpdateResult(BaseModel):\", \"class FileUpdateResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class WebSearchTask(BaseModel):\", \"class WebSearchTask(BaseTaskInputSchema):\")\ncontent = content.replace(\"class WebSearchResult(BaseModel):\", \"class WebSearchResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class CodeExecutionTask(BaseModel):\", \"class CodeExecutionTask(BaseTaskInputSchema):\")\ncontent = content.replace(\"class CodeExecutionResult(BaseModel):\", \"class CodeExecutionResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class PythonAnalysisTask(BaseModel):\", \"class PythonAnalysisTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class PythonAnalysisResult(BaseModel):\", \"class PythonAnalysisResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class MultiBlockUpdateTask(BaseModel):\", \"class MultiBlockUpdateTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class MultiBlockUpdateResult(BaseModel):\", \"class MultiBlockUpdateResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class CommandExecutionTask(BaseModel):\", \"class CommandExecutionTask(BaseTaskInputSchema):\")\ncontent = content.replace(\"class CommandExecutionResult(BaseModel):\", \"class CommandExecutionResult(BaseTaskOutputSchema):\")\n\ncontent = content.replace(\"class CodeCompileTask(BaseModel):\", \"class CodeCompileTask(BaseFileTaskInputSchema):\")\ncontent = content.replace(\"class CodeCompileResult(BaseModel):\", \"class CodeCompileResult(BaseTaskOutputSchema):\")\n\nwith open('phoenix/framework/agent/cognition/planner/schema_new.py', 'w') as f:\n f.write(content)\n\n"}}, "phoenix/framework/agent/tools/io.py": {"c90cb83c79622ef024d92952f3f7f95b60745e72": {"t": 1785674301.6048803, "n": 25597, "h": "270c6981a69d38c3", "c": "from phoenix.framework.agent.tools.base import BaseTool, ToolResult\nimport re\nfrom typing import List, Dict, Union, Optional, Any\nfrom phoenix.framework.agent.cognition.schema import FileContent\nfrom phoenix.framework.agent.cognition.schema import *\nfrom phoenix.services.llm.openai import OpenAILLM\nfrom phoenix.core.config import config\n\nimport os\nimport asyncio\nfrom itertools import islice\nimport ast\nimport json\nimport difflib\n\n\nMODEL_NAME = getattr(config, \"LLM_MODEL\", \"LongCat-2.0-Preview\")\nMODEL_API_KEY = getattr(config, \"LLM_API_KEY\", \"ak_2yp3Xw1Ny7ky2pF7er9x93ZO9jj6G\")\nMODEL_BASE_URL = getattr(config, \"LLM_BASE_URL\", \"https://api.longcat.chat/openai\")\n\n\n \n\ndef _auto_correct_path(file_path: str, is_edit_mode: bool = True, tool_name: str = \"Tool\") -> str:\n import os\n import difflib\n\n if os.path.isabs(file_path) and os.path.exists(file_path):\n return file_path\n \n if not os.path.isabs(file_path) and not is_edit_mode:\n raise ValueError(\n f\"[{tool_name}] SAFETY ERROR: Refusing to create file using relative or ambiguous path '{file_path}'. \"\n f\"You MUST provide an ABSOLUTE full path to ensure the file is placed correctly. \"\n f\"The current working directory is '{os.getcwd()}'.\"\n )\n\n parent_dir = os.path.dirname(file_path) or '.'\n base_name = os.path.basename(file_path)\n \n # Direct check\n if os.path.exists(file_path):\n return os.path.abspath(file_path)\n if os.path.exists(parent_dir) and os.path.exists(os.path.join(parent_dir, base_name)):\n return os.path.abspath(os.path.join(parent_dir, base_name))\n \n corrected_path = None\n\n if is_edit_mode:\n search_root = os.getcwd()\n # Find project root by looking for .git\n curr = search_root\n while curr and curr != '/':\n if os.path.isdir(os.path.join(curr, '.git')):\n search_root = curr\n break\n curr = os.path.dirname(curr)\n \n IGNORE_DIRS = {'.git', 'venv', 'env', '__pycache__', '.idea', '.vscode', 'node_modules', 'dist', 'build'}\n \n exact_matches = []\n partial_matches = []\n \n for root, dirs, files in os.walk(search_root):\n dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not d.startswith('.')]\n for f in files:\n if f == base_name:\n exact_matches.append(os.path.join(root, f))\n elif base_name.lower() in f.lower():\n partial_matches.append(os.path.join(root, f))\n \n candidates = exact_matches if exact_matches else partial_matches\n \n if len(candidates) == 1:\n corrected_path = candidates[0]\n elif len(candidates) > 1:\n rel_file_path = file_path if not os.path.isabs(file_path) else os.path.relpath(file_path, search_root)\n rel_candidates = [os.path.relpath(c, search_root) for c in candidates]\n \n matches = difflib.get_close_matches(rel_file_path, rel_candidates, n=1, cutoff=0.1)\n if matches:\n corrected_path = os.path.join(search_root, matches[0])\n else:\n corrected_path = candidates[0]\n if corrected_path:\n print(f\"[{tool_name}] Path '{file_path}' corrected to: '{corrected_path}'\")\n return corrected_path\n else:\n # If absolute path but parent doesn't exist, auto create it safely\n if os.path.isabs(file_path) and not is_edit_mode:\n parent = os.path.dirname(file_path)\n if parent and not os.path.exists(parent):\n os.makedirs(parent, exist_ok=True)\n return file_path\n\ndef get_blocks_from_lines(lines: List[str], file_path: str, block_size: int = 100) -> tuple[List[FileContent], int]:\n total_lines = len(lines)\n if total_lines == 0:\n return [], 0\n \n blocks = []\n for i in range(0, total_lines, block_size):\n chunk_end = min(i + block_size, total_lines)\n content_block = \"\".join(lines[i:chunk_end])\n blocks.append(FileContent(\n file_path=file_path,\n content_block=content_block,\n from_line=i + 1,\n to_line=chunk_end,\n block_summary=None\n ))\n return blocks, total_lines\n\nclass FileReadTool(BaseTool):\n name = \"file_read\"\n description = (\n \"Use this tool to read, analysis and understanding the files content.\\n\"\n \"Use this for python files and for directorys files \\n\"\n \"\\n Analyzes a file or directory and provides a comprehensive overall summary. Input: 'file_path' (str).\"\n \"\\n this tool provide summary of a file or directory with information about\"\n \"\\n language, imports, functions, classes and file summary\"\n \"\\n and for the directory it provide summary of all the files in the directory \\n\"\n )\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n def _indexing_file(self, file_path: str):\n\n file_data = {\n \"language\": \"\", \n \"imports\": [], \n \"functions\": [], \n \"classes\": [],\n \"file_summary\": \"\" \n }\n\n _, ext = os.path.splitext(file_path)\n ext = ext.lower()\n \n if not os.path.exists(file_path):\n return file_data, \"\"\n\n try:\n with open(file_path, 'r', encoding='utf-8') as f:\n content = f.read()\n except Exception:\n return file_data, \"\"\n\n if ext == '.py':\n file_data[\"language\"] = \"python\"\n try:\n tree = ast.parse(content)\n for node in ast.walk(tree):\n if isinstance(node, ast.Import):\n for alias in node.names:\n file_data[\"imports\"].append(alias.name)\n elif isinstance(node, ast.ImportFrom):\n module = node.module or \"\"\n for alias in node.names:\n file_data[\"imports\"].append(f\"{module}.{alias.name}\")\n elif isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):\n file_data[\"functions\"].append(f\"{node.name} (L{node.lineno}-L{getattr(node, 'end_lineno', node.lineno)})\")\n elif isinstance(node, ast.ClassDef):\n file_data[\"classes\"].append(f\"{node.name} (L{node.lineno}-L{getattr(node, 'end_lineno', node.lineno)})\")\n except SyntaxError:\n pass\n\n elif ext in ['.js', '.jsx', '.ts', '.tsx']:\n file_data[\"language\"] = \"javascript/typescript\"\n import_matches = re.findall(r'import\\s+.*?\\s+from\\s+[\\'\"](.*?)[\\'\"]', content)\n file_data[\"imports\"].extend(import_matches)\n \n line_starts = [0] + [m.start() + 1 for m in re.finditer(r'\\n', content)]\n def get_line(offset):\n import bisect\n return bisect.bisect_right(line_starts, offset)\n\n for m in re.finditer(r'(?:function\\s+([a-zA-Z0-9_]+))|(?:const|let|var)\\s+([a-zA-Z0-9_]+)\\s*=\\s*(?:async\\s*)?(?:\\([^)]*\\)|[a-zA-Z0-9_]+)\\s*=>', content):\n func_name = m.group(1) or m.group(2)\n if func_name:\n lineno = get_line(m.start())\n file_data[\"functions\"].append(f\"{func_name} (L{lineno})\")\n \n for m in re.finditer(r'class\\s+([a-zA-Z0-9_]+)', content):\n class_name = m.group(1)\n lineno = get_line(m.start())\n file_data[\"classes\"].append(f\"{class_name} (L{lineno})\")\n\n elif ext in ['.html']:\n file_data[\"language\"] = \"html\"\n elif ext in ['.css', '.scss']:\n file_data[\"language\"] = \"css\"\n elif ext in ['.sh', '.bash']:\n file_data[\"language\"] = \"shell\"\n elif ext in ['.json']:\n file_data[\"language\"] = \"json\"\n elif ext in ['.md', '.txt']:\n file_data[\"language\"] = \"text\"\n else:\n file_data[\"language\"] = ext.replace('.', '')\n\n file_data[\"imports\"] = list(set(file_data[\"imports\"]))\n file_data[\"functions\"] = list(set(file_data[\"functions\"]))\n file_data[\"classes\"] = list(set(file_data[\"classes\"]))\n \n return file_data, content\n\n async def execute(self, file_path: str, block_size: int = 500, **kwargs) -> ToolResult:\n\n try:\n llm = OpenAILLM(\n model=MODEL_NAME,\n api_key=MODEL_API_KEY,\n base_url=MODEL_BASE_URL\n )\n await llm.init()\n\n IGNORE_DIRS = {'.git', 'venv', 'env', '__pycache__', '.idea', '.vscode', 'node_modules', 'dist', 'build'}\n ALLOWED_EXTENSIONS = {\n '.py', '.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', \n '.json', '.md', '.txt', '.yml', '.yaml', '.toml', '.ini', \n '.sh', '.bash', '.c', '.cpp', '.h', '.java', '.go', '.rs'\n }\n \n target_files = []\n \n file_path = _auto_correct_path(file_path, is_edit_mode=True, tool_name=\"FileReadTool\")\n\n if not os.path.exists(file_path):\n return ToolResult(success=False, output=None, error=f\"File or directory not found: {file_path}\")\n\n if os.path.isdir(file_path):\n for root, dirs, files in os.walk(file_path):\n dirs[:] = [d for d in dirs if d not in IGNORE_DIRS and not d.startswith('.')]\n for file in files:\n if file.startswith('.'):\n continue\n _, ext = os.path.splitext(file)\n if ext.lower() in ALLOWED_EXTENSIONS:\n target_files.append(os.path.join(root, file))\n elif os.path.isfile(file_path):\n target_files.append(file_path)\n\n if not target_files:\n return ToolResult(success=True, output=\"No valid text or code files found to analyze.\")\n\n async def _understand_file(target_file: str):\n file_data, content = self._indexing_file(target_file)\n if not content:\n return target_file, file_data\n\n outline = f\"Language: {file_data['language']}\\nImports: {', '.join(file_data['imports'][:20])}\\nClasses: {', '.join(file_data['classes'])}\\nFunctions: {', '.join(file_data['functions'])}\"\n sample_content = content[:2000]\n \n try:\n summary = await llm.generate(\n f\"Provide a brief summary (max 100 words) of this file's purpose based on its outline and sample content.\\nOutline:\\n{outline}\\n\\nSample Content:\\n{sample_content}\",\n max_tokens=150\n )\n file_data[\"file_summary\"] = summary\n except Exception:\n file_data[\"file_summary\"] = \"Error generating summary.\"\n \n return target_file, file_data\n\n file_results = await asyncio.gather(*[_understand_file(tf) for tf in target_files])\n \n final_output = {}\n global_context = \"\"\n for fp, data in file_results:\n final_output[fp] = data\n global_context += f\"File: {fp}\\nSummary: {data['file_summary']}\\n\\n\"\n \n global_summary = \"\"\n if global_context.strip():\n try:\n global_summary = await llm.generate(\n f\"Provide a comprehensive overall summary for these files based on their individual summaries:\\n{global_context}\",\n max_tokens=500\n )\n except Exception as e:\n global_summary = f\"Failed to generate global summary: {e}\"\n\n result_dict = {\n \"global_summary\": global_summary,\n \"files\": final_output\n }\n \n return ToolResult(success=True, output=json.dumps(result_dict, indent=2))\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\nclass FileWriteTool(BaseTool):\n name = \"file_write\"\n description = (\n \"Writes or overwrites content in a file. \"\n \"Payload must include: 'file_path' (str), 'write_content' (str, the actual code/text). \"\n \"Optional: 'from_line' (int, 1-indexed), 'to_line' (int).\"\n )\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n async def execute(self, file_path: str, write_content: str = None, from_line: int = 1, to_line: Optional[int] = None, content: str = None, **kwargs) -> ToolResult:\n try:\n actual_content = write_content if write_content is not None else content\n if actual_content is None:\n actual_content = \"\"\n \n file_path = _auto_correct_path(file_path, is_edit_mode=(from_line > 1 or to_line is not None), tool_name=\"FileWriteTool\")\n\n read_result = await self.runtime.execute_io(\"read\", file_path)\n lines = read_result.output.splitlines(keepends=True) if (read_result.success and read_result.output) else []\n \n start_idx = max(0, from_line - 1)\n end_line = to_line if to_line is not None else max(1, len(lines))\n \n if start_idx > len(lines):\n lines.extend([\"\\n\"] * (start_idx - len(lines)))\n \n new_lines = actual_content.splitlines(keepends=True)\n if actual_content and not actual_content.endswith('\\n'):\n if new_lines:\n new_lines[-1] += '\\n'\n else:\n new_lines.append('\\n')\n \n lines[start_idx:end_line] = new_lines\n \n final_content = \"\".join(lines)\n write_result = await self.runtime.execute_io(\"edit\", file_path, final_content)\n if not write_result.success:\n return ToolResult(success=False, output=None, error=write_result.error)\n \n blocks, total_lines = get_blocks_from_lines(lines, file_path)\n result = FileWriteResult(\n file_path=file_path,\n content=blocks,\n total_lines=total_lines\n )\n return ToolResult(success=True, output=result.dict())\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\nclass FileAppendTool(BaseTool):\n name = \"file_append\"\n description = \"Appends content to the end of a file and returns FileWriteResult.\"\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n async def execute(self, file_path: str, content: str, **kwargs) -> ToolResult:\n try:\n file_path = _auto_correct_path(file_path, is_edit_mode=True, tool_name=\"FileAppendTool\")\n \n append_result = await self.runtime.execute_io(\"append\", file_path, content)\n if not append_result.success:\n return ToolResult(success=False, output=None, error=append_result.error)\n \n read_result = await self.runtime.execute_io(\"read\", file_path)\n lines = read_result.output.splitlines(keepends=True) if (read_result.success and read_result.output) else []\n \n blocks, total_lines = get_blocks_from_lines(lines, file_path)\n result = FileWriteResult(\n file_path=file_path,\n content=blocks,\n total_lines=total_lines\n )\n return ToolResult(success=True, output=result.dict())\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\nclass FileEditTool(BaseTool):\n name = \"file_edit\"\n description = (\n \"Updates/Edits a file using line-based chunks. \"\n \"Payload must include: 'file_path' (str), 'chunks' (list of dicts). \"\n \"Each chunk dict must have: 'from_line' (int, 1-indexed start), \"\n \"'to_line' (int, 1-indexed end, inclusive), and 'replacement_content' (str, the new code/text to insert). \"\n \"Optional chunk field: 'target_content' (str). \"\n \"Example chunk: {\\\"from_line\\\": 1, \\\"to_line\\\": 2, \\\"replacement_content\\\": \\\"def sum(a, b):\\\\n return a + b\\\\n\\\"}\"\n )\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n async def execute(self, file_path: str, chunks: List[Union[ReplacementChunk, Dict[str, Any]]] = None, edits: list = None, upsert: bool = True, **kwargs) -> ToolResult:\n try:\n file_path = _auto_correct_path(file_path, is_edit_mode=True, tool_name=\"FileEditTool\")\n \n read_result = await self.runtime.execute_io(\"read\", file_path)\n lines = read_result.output.splitlines(keepends=True) if (read_result.success and read_result.output) else []\n\n if chunks:\n processed_chunks = []\n for chunk in chunks:\n if isinstance(chunk, dict):\n processed_chunks.append(ReplacementChunk(**chunk))\n else:\n processed_chunks.append(chunk)\n \n processed_chunks.sort(key=lambda c: c.from_line, reverse=True)\n \n for chunk in processed_chunks:\n start_idx = chunk.from_line - 1\n end_idx = chunk.to_line\n \n if start_idx > len(lines):\n lines.extend([\"\\n\"] * (start_idx - len(lines)))\n \n new_lines = chunk.replacement_content.splitlines(keepends=True)\n if chunk.replacement_content and not chunk.replacement_content.endswith('\\n'):\n if new_lines:\n new_lines[-1] += '\\n'\n else:\n new_lines.append('\\n')\n \n lines[start_idx:end_idx] = new_lines\n \n elif edits:\n content = \"\".join(lines)\n updated = content\n applied = 0\n upserted = 0\n\n for edit in edits:\n search = edit.get(\"search\", \"\")\n replace = edit.get(\"replace\", \"\")\n if not search:\n continue\n\n if search in updated:\n updated = updated.replace(search, replace)\n applied += 1\n elif upsert:\n if updated and not updated.endswith(\"\\n\"):\n updated += \"\\n\"\n updated += replace\n if replace and not replace.endswith(\"\\n\"):\n updated += \"\\n\"\n upserted += 1\n else:\n return ToolResult(\n success=False,\n output=None,\n error=f\"Search text not found: {search[:80]}\"\n )\n lines = updated.splitlines(keepends=True)\n\n final_content = \"\".join(lines)\n write_result = await self.runtime.execute_io(\"edit\", file_path, final_content)\n if not write_result.success:\n return ToolResult(success=False, output=None, error=write_result.error)\n\n blocks, total_lines = get_blocks_from_lines(lines, file_path)\n result = FileUpdateResult(\n file_path=file_path,\n success=True,\n content=blocks,\n total_lines=total_lines\n )\n return ToolResult(success=True, output=result.dict())\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\nclass FileSearchTool(BaseTool):\n name = \"file_search\"\n description = \"Searches for a query string or regex pattern in a file or directory using bash grep.\"\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n async def execute(self, file_path: str = None, search_query: str = None, is_regex: bool = False, case_sensitive: bool = False, path: str = None, pattern: str = None, query: str = None, **kwargs) -> ToolResult:\n try:\n target_path = file_path or path\n final_query = search_query or pattern or query\n \n if not target_path:\n return ToolResult(success=False, output=None, error=\"No file path provided to search.\")\n if not final_query:\n return ToolResult(success=False, output=None, error=\"No search query/pattern provided.\")\n \n # Optimize search using execute_command\n import shlex\n \n grep_flags = \"-n\"\n if not case_sensitive:\n grep_flags += \" -i\"\n if is_regex:\n grep_flags += \" -E\"\n else:\n grep_flags += \" -F\"\n \n # If target_path is a directory, add recursive flag\n # Wait, since we are doing this strictly via runtime, we might not have `os.path.isdir`.\n # Let's check with a command first if it's a directory\n check_cmd = f\"test -d {shlex.quote(target_path)} && echo 'dir' || echo 'file'\"\n check_result = await self.runtime.execute_command(check_cmd)\n is_dir = \"dir\" in check_result.output\n \n if is_dir:\n grep_flags += \" -r\"\n \n cmd = f\"grep {grep_flags} {shlex.quote(final_query)} {shlex.quote(target_path)}\"\n search_result = await self.runtime.execute_command(cmd)\n \n matches = []\n if search_result.success and search_result.output:\n for line in search_result.output.splitlines():\n if not line.strip(): continue\n parts = line.split(\":\", 2 if is_dir else 1)\n if len(parts) >= 2:\n try:\n # format: file:line:content or line:content\n if is_dir and len(parts) >= 3:\n line_num = int(parts[1])\n line_content = parts[2]\n else:\n line_num = int(parts[0])\n line_content = parts[1]\n \n block_idx = max(0, line_num - 1) // 100\n matches.append(FileSearchMatch(\n line_number=line_num,\n line_content=line_content.strip(),\n block_index=block_idx\n ))\n except ValueError:\n pass\n \n result = FileSearchResult(\n file_path=target_path,\n matches=matches,\n total_matches=len(matches)\n )\n return ToolResult(success=True, output=result.dict())\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\nclass FileDeleteTool(BaseTool):\n name = \"file_delete\"\n description = \"Deletes a file. Input: 'file_path' (str).\"\n\n def __init__(self, runtime=None):\n super().__init__()\n from phoenix.framework.agent.runtime.restricted_python import RestrictedPythonRuntime\n self.runtime = runtime or RestrictedPythonRuntime()\n\n async def execute(self, file_path: str, **kwargs) -> ToolResult:\n import os\n try:\n if not os.path.exists(file_path):\n return ToolResult(success=False, output=None, error=f\"Safety Check Failed: Exact file not found at {file_path}. Auto-correction is disabled for deletion.\")\n \n exe_result = await self.runtime.execute_io(\"delete\", file_path)\n if not exe_result.success:\n return ToolResult(success=False, output=None, error=exe_result.error)\n return ToolResult(success=True, output=f\"Successfully deleted {file_path}\")\n except Exception as e:\n return ToolResult(success=False, output=None, error=str(e))\n\n\nif __name__ == \"__main__\":\n import asyncio\n analysis = FileReadTool()\n result = asyncio.run(analysis.execute(\"io.py\"))\n print(result.output)"}}, "phoenix/services/llm/openai.py": {"c90cb83c79622ef024d92952f3f7f95b60745e72": {"t": 1785674301.8980677, "n": 13369, "h": "ae6814259baf7dfd", "c": "from phoenix.services.llm.base import BaseLLM\nfrom phoenix.core.config import config\nfrom openai import AsyncOpenAI\nfrom phoenix.services.observability.tracing import tracer\nfrom phoenix.services.observability.logger import get_logger\nfrom phoenix.core.container import container\nfrom typing import Optional, List, Dict, Any\nimport asyncio\nimport re\nimport ast\nimport operator\n\nlogger = get_logger(\"Phoenix AI.LLM.OpenAI\")\n\nclass OpenAILLM(BaseLLM):\n def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, model: Optional[str] = None):\n self.api_key = api_key or getattr(config, \"OPENAI_API_KEY\", None)\n self.base_url = base_url or getattr(config, \"OPENAI_BASE_URL\", None)\n self.model = model or getattr(config, \"OPENAI_LLM_MODEL\", None) or \"LongCat-Flash-Chat\"\n self.client = None\n\n @staticmethod\n def _safe_eval_math_expression(expression: str) -> Optional[float]:\n \"\"\"\n Evaluate simple arithmetic expressions safely.\n Supported: +, -, *, /, %, **, unary +/- and parentheses.\n \"\"\"\n allowed_ops = {\n ast.Add: operator.add,\n ast.Sub: operator.sub,\n ast.Mult: operator.mul,\n ast.Div: operator.truediv,\n ast.Mod: operator.mod,\n ast.Pow: operator.pow,\n ast.USub: operator.neg,\n ast.UAdd: operator.pos,\n }\n\n def _eval(node):\n if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):\n return node.value\n if isinstance(node, ast.BinOp) and type(node.op) in allowed_ops:\n return allowed_ops[type(node.op)](_eval(node.left), _eval(node.right))\n if isinstance(node, ast.UnaryOp) and type(node.op) in allowed_ops:\n return allowed_ops[type(node.op)](_eval(node.operand))\n raise ValueError(\"Unsupported math expression.\")\n\n try:\n parsed = ast.parse(expression, mode=\"eval\")\n return float(_eval(parsed.body))\n except Exception:\n return None\n\n def _run_light_tools(self, prompt: str) -> List[str]:\n \"\"\"\n Lightweight local helpers to speed up structured tasks.\n These tools do not call external services.\n \"\"\"\n tool_outputs: List[str] = []\n\n # Calculator helper: detect \"calc: <expression>\"\n calc_match = re.search(r\"(?:^|\\n)\\s*calc\\s*:\\s*([0-9\\.\\+\\-\\*\\/%\\(\\)\\s\\^]+)\", prompt, flags=re.IGNORECASE)\n if calc_match:\n expr = calc_match.group(1).replace(\"^\", \"**\").strip()\n calc_value = self._safe_eval_math_expression(expr)\n if calc_value is not None:\n if calc_value.is_integer():\n tool_outputs.append(f\"Calculator result: {expr} = {int(calc_value)}\")\n else:\n tool_outputs.append(f\"Calculator result: {expr} = {calc_value}\")\n\n # Optimization helper: extract objectives/constraints from common keywords.\n lower = prompt.lower()\n has_opt_signal = any(k in lower for k in [\"optimize\", \"optimization\", \"minimize\", \"maximize\"])\n if has_opt_signal:\n constraints = re.findall(r\"(?:constraint|limit|must|should)\\s*:\\s*([^\\n]+)\", prompt, flags=re.IGNORECASE)\n objective = re.search(r\"(?:optimize|minimize|maximize)\\s*[:\\-]?\\s*([^\\n]+)\", prompt, flags=re.IGNORECASE)\n\n if objective or constraints:\n objective_text = objective.group(1).strip() if objective else \"Not explicitly provided\"\n constraints_text = \"; \".join(c.strip() for c in constraints[:5]) if constraints else \"None explicit\"\n tool_outputs.append(\n \"Optimization parser:\\n\"\n f\"- Objective: {objective_text}\\n\"\n f\"- Constraints: {constraints_text}\\n\"\n \"- Suggested strategy: solve constraints first, then optimize objective.\"\n )\n\n return tool_outputs\n\n async def _collect_memory_context(self, memory, session_id: str, prompt: str, smart_mode: bool) -> Dict[str, object]:\n \"\"\"\n Collect memory in parallel for lower latency.\n \"\"\"\n history = []\n history_context = \"\"\n semantic_context = \"\"\n\n if not session_id or not memory:\n return {\n \"history\": history,\n \"history_context\": history_context,\n \"semantic_context\": semantic_context\n }\n\n async def _safe(coro, default):\n try:\n return await coro\n except Exception:\n return default\n\n history_task = _safe(memory.history.get(session_id), [])\n history_context_task = _safe(memory.get_context(session_id), \"\")\n semantic_task = _safe(memory.search_memory(session_id, prompt), \"\")\n\n history, history_context, semantic_context = await asyncio.gather(\n history_task, history_context_task, semantic_task\n )\n\n # Keep only the latest turns in smart mode for faster token usage.\n if smart_mode and isinstance(history, list) and len(history) > 8:\n history = history[-8:]\n\n return {\n \"history\": history if isinstance(history, list) else [],\n \"history_context\": history_context if isinstance(history_context, str) else \"\",\n \"semantic_context\": semantic_context if isinstance(semantic_context, str) else \"\"\n }\n\n def _build_system_prompt(self, smart_mode: bool, semantic_context: str, history_context: str, tool_outputs: List[str]) -> str:\n base = \"You are a helpful AI assistant.\"\n if smart_mode:\n base = (\n \"You are a fast and accurate AI reasoning assistant.\\n\"\n \"Priorities:\\n\"\n \"1) Be correct.\\n\"\n \"2) Be concise and action-oriented.\\n\"\n \"3) Use planning internally, but return clear final answers.\\n\"\n \"4) When tool outputs are provided, use them as trusted context.\"\n )\n\n segments = [base]\n\n if semantic_context:\n segments.append(f\"Semantic context from previous conversations:\\n{semantic_context}\")\n if history_context:\n segments.append(f\"Conversation summary context:\\n{history_context}\")\n if tool_outputs:\n segments.append(\"Lightweight tool outputs:\\n\" + \"\\n\\n\".join(tool_outputs))\n\n return \"\\n\\n\".join(segments)\n\n def is_available(self) -> bool:\n # LongCat keys often start with 'ak_', standard OpenAI keys start with 'sk-'\n return bool(self.api_key) and bool(self.model)\n \n async def init(self):\n if not self.api_key:\n logger.warning(\"OPENAI_API_KEY is missing. Operating in mock mode.\")\n\n base_url = self.base_url\n if base_url and base_url.endswith(\"/\"):\n base_url = base_url[:-1]\n\n import httpx\n self.client = AsyncOpenAI(\n api_key=self.api_key,\n base_url=base_url if base_url else None,\n timeout=httpx.Timeout(120.0, connect=60.0)\n )\n\n async def generate(\n self,\n prompt: str,\n session_id: Optional[str] = None,\n max_tokens: Optional[int] = None,\n mode: Optional[str] = None\n ) -> str:\n if not self.client:\n raise RuntimeError(\"OpenAILLM is not initialized.\")\n\n requested_mode = (mode or getattr(config, \"OPENAI_GENERATION_MODE\", \"standard\") or \"standard\").lower()\n smart_mode = requested_mode in {\"smart\", \"fast_smart\", \"thinking\", \"planning\"}\n\n # Handle memory\n memory = None\n current_messages = []\n\n try:\n memory = container.get(\"memory\")\n except KeyError:\n pass\n\n # Run local helper tools only in smart mode to keep standard path minimal.\n tool_outputs = self._run_light_tools(prompt) if smart_mode else []\n\n mem_data = await self._collect_memory_context(memory, session_id, prompt, smart_mode)\n history = mem_data[\"history\"]\n history_context = mem_data[\"history_context\"]\n semantic_context = mem_data[\"semantic_context\"]\n\n if session_id and memory:\n system_prompt = self._build_system_prompt(\n smart_mode=smart_mode,\n semantic_context=semantic_context,\n history_context=history_context,\n tool_outputs=tool_outputs\n )\n current_messages.append({\"role\": \"system\", \"content\": system_prompt})\n for item in history:\n if isinstance(item, dict) and \"content\" in item:\n current_messages.append(item[\"content\"])\n else:\n # For stateless smart mode, still provide smart system behavior.\n if smart_mode:\n current_messages.append({\n \"role\": \"system\",\n \"content\": self._build_system_prompt(\n smart_mode=True,\n semantic_context=\"\",\n history_context=\"\",\n tool_outputs=tool_outputs\n )\n })\n current_messages.append({\"role\": \"user\", \"content\": prompt})\n\n # Mock mode\n if not self.api_key:\n return f\"[Mock OpenAI Response (No API Key) to: {prompt}]\"\n\n if not self.model:\n raise RuntimeError(\"OpenAILLM model is not configured.\")\n\n span_id = tracer.start_span(\"OpenAILLM.generate\", {\"model\": self.model, \"mode\": requested_mode})\n try:\n # Final user message if using memory\n if session_id and memory:\n current_messages.append({\"role\": \"user\", \"content\": prompt})\n\n resp = await self.client.chat.completions.create(\n model=self.model,\n messages=current_messages,\n max_tokens=max_tokens or config.SECURITY_MAX_OUTPUT_LENGTH\n )\n\n # Safe extraction\n if not resp or not resp.choices:\n tracer.end_span(span_id, status=\"error\", error=\"Empty response from OpenAI\")\n raise RuntimeError(\"Empty response from OpenAI API.\")\n\n usage = {\n \"prompt_tokens\": resp.usage.prompt_tokens,\n \"completion_tokens\": resp.usage.completion_tokens,\n \"total_tokens\": resp.usage.total_tokens\n }\n \n message = resp.choices[0].message\n content = message.content.strip() if message and message.content else \"\"\n \n tracer.end_span(span_id, status=\"success\", usage=usage)\n \n # Store interaction in memory\n if session_id and memory:\n await memory.add_interaction(session_id, prompt, content)\n \n return content\n\n except Exception as e:\n tracer.end_span(span_id, status=\"error\", error=str(e))\n raise RuntimeError(f\"OpenAILLM API call failed: {e}\")\n\n async def generate_structured(\n self,\n prompt: str,\n schema: Any,\n session_id: Optional[str] = None,\n max_tokens: Optional[int] = None\n ) -> Any:\n if not self.client:\n raise RuntimeError(\"OpenAILLM is not initialized.\")\n\n import json\n # Handle pydantic v1 vs v2 schema extraction\n schema_json = schema.model_json_schema() if hasattr(schema, \"model_json_schema\") else (schema.schema_json() if hasattr(schema, \"schema_json\") else str(schema))\n if isinstance(schema_json, dict):\n schema_json = json.dumps(schema_json)\n\n system_prompt = (\n f\"You are a strict data-generation assistant. \"\n f\"You MUST output raw JSON matching the following JSON schema:\\n{schema_json}\\n\"\n f\"Do not output markdown code blocks or any conversational text. Just the raw valid JSON object.\"\n )\n\n messages = [\n {\"role\": \"system\", \"content\": system_prompt},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n span_id = tracer.start_span(\"OpenAILLM.generate_structured\", {\"model\": self.model})\n try:\n # We try using response_format, if it fails due to API compatibility, we could fallback, \n # but LongCat supports json_object.\n resp = await self.client.chat.completions.create(\n model=self.model,\n messages=messages,\n max_tokens=max_tokens or config.SECURITY_MAX_OUTPUT_LENGTH,\n response_format={\"type\": \"json_object\"}\n )\n\n content = resp.choices[0].message.content.strip()\n tracer.end_span(span_id, status=\"success\")\n\n # Remove potential markdown formatting if the model ignored instructions\n if content.startswith(\"```json\"):\n content = content[7:]\n if content.startswith(\"```\"):\n content = content[3:]\n if content.endswith(\"```\"):\n content = content[:-3]\n\n parsed_data = json.loads(content.strip())\n return schema(**parsed_data)\n except Exception as e:\n tracer.end_span(span_id, status=\"error\", error=str(e))\n raise RuntimeError(f\"Structured generation failed: {e}\")\n\n"}}, "phoenix/framework/agent/cognition/planner/task_creator.py": {"c90cb83c79622ef024d92952f3f7f95b60745e72": {"t": 1785674302.4986558, "n": 10176, "h": "ff12c8ae53e4105b", "c": "\"\"\"\n Task creator is responsible for creating tasks based on the objective.\n It uses the LLM to generate tasks from the objective.\n\n\"\"\"\n\nimport json\nfrom typing import Any, Optional, List, Dict\nfrom uuid import uuid4\n\nfrom phoenix.framework.agent.cognition.schema import (\n Problem, Solution, Task, TaskType, TaskPriority, TaskStatus, \n SolutionType, ProblemComplexity\n)\n\nfrom phoenix.framework.agent.cognition.schema import BaseReflectorMeta\nfrom phoenix.framework.agent.cognition.utils import parse_llm_json\n\nclass TaskCreator:\n def __init__(self, thinker:Any, tools: Optional[Any] = None, cache: Optional[Any] = None ):\n self.thinker = thinker\n self.tools = tools\n self.cache = cache\n self.problems = []\n\n if not cache: \n self.memory = {}\n\n\n def _get_default_reflector(self) -> BaseReflectorMeta:\n return BaseReflectorMeta(\n rating=5, \n feedback=\"Initial generation, unreflected.\", \n confidence=0.5, \n reasoning=\"Created automatically by TaskCreator.\"\n )\n\n async def create_problem(self, objective: str) -> Problem:\n prompt = f\"\"\"\n Given the following objective, identify the core problem and propose potential solutions.\n Objective: {objective}\n\n Valid complexity values: \"low\", \"medium\", \"high\", \"extreme\"\n\n Respond ONLY in valid JSON matching this exact structure (no comments):\n {{\n \"description\": \"A very brief, 1-sentence description of the core problem\",\n \"complexity\": \"low\",\n \"best_solution_index\": 0\n }}\n \"\"\"\n response = await self.llm.generate(prompt, max_tokens=300)\n data = parse_llm_json(response) or {}\n \n desc = data.get(\"description\", f\"Problem for objective: {objective}\")\n comp_str = data.get(\"complexity\", \"medium\").lower()\n try:\n complexity = ProblemComplexity(comp_str)\n except ValueError:\n complexity = ProblemComplexity.MEDIUM\n\n solutions = []\n\n dummy_solution = Solution(\n id=uuid4(),\n description=\"Pending solution generation\",\n solution_type=SolutionType.PLAN,\n content=\"Pending content\",\n reflector_result=self._get_default_reflector()\n )\n\n problem = Problem(\n id=uuid4(),\n description=desc,\n solution=solutions,\n best_solution=dummy_solution,\n complexity=complexity,\n reflector_result=self._get_default_reflector()\n )\n self.problems.append(problem)\n return problem\n\n async def create_solution(self, problem: Problem, variant_index: int = 1) -> Solution:\n prompt = f\"\"\"\n Given the following problem, generate a concise strategy to solve it. DO NOT WRITE CODE. \n This is solution variant #{variant_index}. Provide a unique, high-level approach.\n \n Problem Description: {problem.description}\n Complexity: {problem.complexity.value}\n \n Valid solution_type values: \"plan\", \"code\", \"terminal\", \"network\", \"mission\", \"fastanswer\", \"other\"\n \n Respond ONLY in valid JSON matching this exact structure (no comments):\n {{\n \"description\": \"A very short, 1-sentence description of the solution\",\n \"solution_type\": \"plan\",\n \"content\": \"A high-level outline of the approach (max 3 bullet points, NO CODE).\"\n }}\n \"\"\"\n response = await self.llm.generate(prompt, max_tokens=500)\n data = parse_llm_json(response) or {}\n \n print(f\"DEBUG - LLM Raw Response for create_solution (Variant {variant_index}):\\n{response}\\n\")\n print(f\"DEBUG - Parsed Data:\\n{data}\\n\")\n\n stype_str = data.get(\"solution_type\", \"other\").lower()\n try:\n stype = SolutionType(stype_str)\n except ValueError:\n stype = SolutionType.OTHER\n\n content_val = data.get(\"content\", \"Detailed solution steps\")\n if isinstance(content_val, list):\n content_val = \"\\n\".join(f\"- {str(item)}\" for item in content_val)\n\n return Solution(\n id=uuid4(),\n description=data.get(\"description\", \"Generated solution for the problem\"),\n solution_type=stype,\n content=content_val,\n reflector_result=self._get_default_reflector()\n )\n\n async def solve_all_problems(self, solutions_count_for_each_problem=3): \n import asyncio\n for problem in self.problems:\n tasks = [self.create_solution(problem, variant_index=i+1) for i in range(solutions_count_for_each_problem)]\n solutions = await asyncio.gather(*tasks, return_exceptions=True)\n \n valid_solutions = []\n for s in solutions:\n if isinstance(s, Solution):\n valid_solutions.append(s)\n else:\n print(f\"DEBUG - Error generating solution: {s}\")\n \n problem.solution = valid_solutions\n if valid_solutions:\n problem.best_solution = valid_solutions[0]\n\n async def create_task(self, objective: str, user_prompt: str) -> Task:\n \"\"\"Creates a structured task based on objective and prompt.\"\"\"\n problem = await self.create_problem(objective)\n await self.solve_all_problems()\n \n registered_tools = []\n if self.tools:\n if hasattr(self.tools, \"tools\"):\n registered_tools = list(self.tools.tools.keys())\n elif hasattr(self.tools, \"get_all_tools_info\"):\n try:\n registered_tools = [t.get(\"name\") for t in self.tools.get_all_tools_info() if t and \"name\" in t]\n except Exception:\n pass\n\n prompt = f\"\"\"\n Given the user prompt, objective, and the identified problem, formulate a structured execution task.\n User Prompt: {user_prompt}\n Objective: {objective}\n Problem Description: {problem.description}\n Chosen Solution: {problem.best_solution.content}\n\n Available Tools: {json.dumps(registered_tools)}\n\n Valid task_type values: \"read\", \"write\", \"search\", \"update\", \"delete\", \"block_read\", \"block_write\", \"mmap_io\", \"net_send\", \"net_recv\", \"ipc_pipe\", \"ipc_share\", \"rpc_call\", \"batch_load\", \"tensor_stream\", \"vector_search\", \"vram_shuttle\", \"token_stream\", \"dma_transfer\", \"interrupt_req\", \"port_in\", \"port_out\", \"mem_mapped_in\", \"bus_broadcast\", \"bus_listen\", \"adc_sample\", \"dac_actuate\", \"pwm_output\", \"sensor_poll\", \"watchdog_ping\", \"other\"\n Valid priority values: \"critical\", \"high\", \"medium\", \"low\"\n\n Respond ONLY in valid JSON matching this exact structure (no comments):\n {{\n \"task_title\": \"Short descriptive title (max 5 words)\",\n \"description\": \"Brief, actionable description of the task (1-2 sentences)\",\n \"task_type\": \"other\",\n \"priority\": \"medium\",\n \"dependencies\": [],\n \"tools_required\": [\"tool_name1\", \"tool_name2\"]\n }}\n \"\"\"\n response = await self.llm.generate(prompt, max_tokens=500)\n data = parse_llm_json(response) or {}\n\n ttype_str = data.get(\"task_type\", \"other\").lower()\n try:\n task_type = TaskType(ttype_str)\n except ValueError:\n task_type = TaskType.OTHER\n\n tprio_str = data.get(\"priority\", \"medium\").lower()\n try:\n priority = TaskPriority(tprio_str)\n except ValueError:\n priority = TaskPriority.MEDIUM\n\n tools_required = [t for t in data.get(\"tools_required\", []) if t in registered_tools]\n payload = {\n \"tools_required\": tools_required,\n \"solution_context\": problem.best_solution.content\n }\n\n task = Task(\n prompt_id=uuid4(),\n task_id=str(uuid4()),\n dependencies=data.get(\"dependencies\", []),\n task_type=task_type,\n priority=priority,\n status=TaskStatus.PENDING,\n task_title=data.get(\"task_title\", \"Generated Task\"),\n description=data.get(\"description\", f\"Task for: {objective}\"),\n task_summary=None,\n complexity=problem.complexity,\n problems=[problem],\n repeat_count=1,\n payload=payload,\n created_by=\"TaskCreator\",\n reflector_result=self._get_default_reflector()\n )\n \n return task\n\n\n\n\nif __name__ == \"__main__\":\n import sys\n import os\n import asyncio\n \n # Add the project root to sys.path so 'phoenix' can be imported\n \n from phoenix.services.llm.openai import OpenAILLM\n\n llm = OpenAILLM(\n api_key=\"ak_2yp3Xw1Ny7ky2pF7er9x93ZO9jj6G\",\n model=\"LongCat-2.0-Preview\",\n base_url=\"https://api.longcat.chat/openai\"\n )\n\n class MockTools:\n def __init__(self):\n # Simulating a tool registry\n self.tools = {\n \"file_write\": \"Write code or text to a specific file path\",\n \"file_read\": \"Read content from a specific file path\",\n \"execute_terminal\": \"Run terminal commands like npm install or python execution\",\n \"web_search\": \"Search the web for up to date information\", \n \"folder_create\": \"Create a new folder\",\n \n }\n\n async def run_test():\n await llm.init()\n \n mock_tools = MockTools()\n test_task = TaskCreator(llm=llm, tools=mock_tools)\n \n objective = \"create a modular django project with modular folders structure and modular plugins with modular templates and static files for simple wallet system\"\n prompt = \"hello\"\n task = await test_task.create_task(objective, prompt)\n \n print(\"\\n\" + \"=\"*60)\n print(\"GENERATED TASK (Readable Format)\")\n print(\"=\"*60)\n # Pydantic V2 .model_dump_json() handles UUIDs and Enums\n print(task.model_dump_json(indent=5))\n print(\"=\"*60 + \"\\n\")\n\n asyncio.run(run_test())"}}}} |