From d029d4045c029a0383385b27f2301075cf7c6279 Mon Sep 17 00:00:00 2001 From: dsk-dev-ai Date: Sun, 5 Apr 2026 22:33:53 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20DevOS=20AI=20v2.0=20-=20Full=20C?= =?UTF-8?q?LI=20system=20(Explain=20+=20Search=20+=20Debug=20+=20Multi-LLM?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents/__init__.py | 0 agents/debug_agent.py | 34 ++++++++++ agents/search_agent.py | 72 ++++++++++++++++++++ apps/ai-engine/agents/search_agent.py | 0 cli/__init__.py | 0 cli/devos.py | 11 ---- cli/main.py | 51 ++++++++++++++ core/__init__.py | 0 core/engine.py | 64 ++++++++++++++++++ core/parser.py | 38 +++++++++++ llm/__init__.py | 0 llm/provider.py | 95 +++++++++++++++++++++++++++ 12 files changed, 354 insertions(+), 11 deletions(-) create mode 100644 agents/__init__.py create mode 100644 agents/debug_agent.py create mode 100644 agents/search_agent.py create mode 100644 apps/ai-engine/agents/search_agent.py create mode 100644 cli/__init__.py delete mode 100644 cli/devos.py create mode 100755 cli/main.py create mode 100644 core/__init__.py create mode 100644 core/engine.py create mode 100644 core/parser.py create mode 100644 llm/__init__.py create mode 100644 llm/provider.py diff --git a/agents/__init__.py b/agents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/debug_agent.py b/agents/debug_agent.py new file mode 100644 index 0000000..f60789d --- /dev/null +++ b/agents/debug_agent.py @@ -0,0 +1,34 @@ +from llm.provider import ask_llm + + +def debug_error(file_path, model): + try: + with open(file_path, "r") as f: + error = f.read().strip() + except FileNotFoundError: + return f"❌ File not found: {file_path}" + + if not error: + return "❌ Error file is empty" + + prompt = f""" +You are a senior software engineer. + +Debug this error: + +{error} + +Explain clearly: + +1. Root cause +2. Why it happens +3. Step-by-step fix +4. Example fix +""" + + result = ask_llm(prompt, model) + + if not result: + return "❌ No response from LLM" + + return result \ No newline at end of file diff --git a/agents/search_agent.py b/agents/search_agent.py new file mode 100644 index 0000000..671be42 --- /dev/null +++ b/agents/search_agent.py @@ -0,0 +1,72 @@ +from core.parser import get_code_files +from llm.provider import ask_llm + + +def search_code(repo_path, query, model): + files = get_code_files(repo_path) + + matches = [] + + for file in files: + try: + with open(file, "r", encoding="utf-8") as f: + lines = f.readlines() + + for i, line in enumerate(lines): + if query.lower() in line.lower(): + snippet = "".join(lines[max(0, i-2):i+3]) + + matches.append(f""" +FILE: {file} +LINE: {i+1} +CODE: +{snippet} +""") + + if len(matches) >= 5: + break + + except: + continue + + if not matches: + return "❌ No matches found" + + context = "\n\n".join(matches) + + prompt = f""" +You are a senior software engineer analyzing search results from a codebase. + +User query: "{query}" + +Code snippets: +{context} + +IMPORTANT: +- Do NOT describe prompts or text +- Focus on actual code logic +- Ignore placeholder strings + +Give a professional answer: + +1. Relevant functionality found +- What real functionality matches the query + +2. Where it exists +- File names and modules + +3. How it works +- Explain logic of code (not text) + +4. Summary +- What developer should understand from this + +Be precise, technical, and useful. +""" + + result = ask_llm(prompt, model) + + if not result or "❌" in result: + return "❌ No meaningful response from LLM for search" + + return result \ No newline at end of file diff --git a/apps/ai-engine/agents/search_agent.py b/apps/ai-engine/agents/search_agent.py new file mode 100644 index 0000000..e69de29 diff --git a/cli/__init__.py b/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cli/devos.py b/cli/devos.py deleted file mode 100644 index 138cffb..0000000 --- a/cli/devos.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -import requests - -query = " ".join(sys.argv[1:]) - -res = requests.post( - "http://localhost:8000/chat", - params={"query": query} -) - -print(res.json()["response"]) \ No newline at end of file diff --git a/cli/main.py b/cli/main.py new file mode 100755 index 0000000..8e94ab0 --- /dev/null +++ b/cli/main.py @@ -0,0 +1,51 @@ +import sys +from core.engine import build_context, build_prompt +from llm.provider import ask_llm +from agents.debug_agent import debug_error +from agents.search_agent import search_code + + +def explain(path, model): + context = build_context(path) + prompt = build_prompt(context, "Explain this codebase") + return ask_llm(prompt, model) + + +def main(): + if len(sys.argv) < 3: + print(""" +Usage: + explain [--model openrouter|google|auto] + search "" [--model openrouter|google|auto] + debug [--model openrouter|google|auto] +""") + return + + command = sys.argv[1] + model = "auto" + + if "--model" in sys.argv: + idx = sys.argv.index("--model") + if idx + 1 < len(sys.argv): + model = sys.argv[idx + 1] + + print("🔍 Processing...\n") + + if command == "explain": + result = explain(sys.argv[2], model) + + elif command == "search": + result = search_code(sys.argv[2], sys.argv[3], model) + + elif command == "debug": + result = debug_error(sys.argv[2], model) + + else: + result = "❌ Unknown command" + + print("💡 Result:\n") + print(result) + + +if __name__ == "__main__": + main() diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/engine.py b/core/engine.py new file mode 100644 index 0000000..d1e29c1 --- /dev/null +++ b/core/engine.py @@ -0,0 +1,64 @@ +from core.parser import get_code_files, read_files + +PRIORITY = ["main", "app", "engine", "cli", "api"] + + +def rank_files(files): + scored = [] + + for f in files: + score = 0 + name = f.lower() + + for p in PRIORITY: + if p in name: + score += 5 + + if "core" in name or "llm" in name: + score += 3 + + scored.append((score, f)) + + scored.sort(reverse=True) + return [f for _, f in scored] + + +def build_context(repo_path): + files = get_code_files(repo_path) + ranked = rank_files(files) + + top_files = ranked[:5] + return read_files(top_files) + + +def build_prompt(context, question): + return f""" +You are a senior software engineer analyzing a real codebase. + +STRICT RULES: +- Use ONLY the given code context +- Do NOT assume missing features +- Do NOT mention technologies not present in context + +CONTEXT: +{context} + +TASK: +{question} + +Respond in this format: + +1. Purpose +- What this system actually does (based only on code) + +2. Architecture +- Real structure (modules, flow) + +3. Key Components +- Explain actual files/modules found + +4. Execution Flow +- Step-by-step how system works + +Keep answer precise, technical, and grounded in code. +""" diff --git a/core/parser.py b/core/parser.py new file mode 100644 index 0000000..c879fd6 --- /dev/null +++ b/core/parser.py @@ -0,0 +1,38 @@ +import os + +IGNORE_DIRS = { + ".venv", "__pycache__", ".git", "node_modules", + "dist", "build", + "apps", + "frontend", + "infrastructure" +} + +EXTENSIONS = (".py", ".js", ".ts", ".tsx") + + +def get_code_files(repo_path): + code_files = [] + + for root, dirs, files in os.walk(repo_path): + dirs[:] = [d for d in dirs if d not in IGNORE_DIRS] + + for file in files: + if file.endswith(EXTENSIONS): + code_files.append(os.path.join(root, file)) + + return code_files + + +def read_files(files, max_chars=800): + contents = [] + + for file in files: + try: + with open(file, "r", encoding="utf-8") as f: + text = f.read()[:max_chars] + contents.append(f"\n# FILE: {file}\n{text}") + except: + continue + + return "\n\n".join(contents) \ No newline at end of file diff --git a/llm/__init__.py b/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/llm/provider.py b/llm/provider.py new file mode 100644 index 0000000..26c8ed8 --- /dev/null +++ b/llm/provider.py @@ -0,0 +1,95 @@ +import os +import requests +from dotenv import load_dotenv + +load_dotenv() + +OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") +GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") + + +def ask_openrouter(prompt): + if not OPENROUTER_API_KEY: + return None + + try: + res = requests.post( + "https://openrouter.ai/api/v1/chat/completions", + headers={ + "Authorization": f"Bearer {OPENROUTER_API_KEY}", + "Content-Type": "application/json" + }, + json={ + "model": "openai/gpt-3.5-turbo", + "messages": [{"role": "user", "content": prompt}] + }, + timeout=20 + ) + + data = res.json() + + if "choices" in data: + return data["choices"][0]["message"]["content"] + + return None + + except: + return None + + +def ask_google(prompt): + if not GOOGLE_API_KEY: + return None + + try: + res = requests.post( + f"https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent?key={GOOGLE_API_KEY}", + headers={"Content-Type": "application/json"}, + json={ + "contents": [{"parts": [{"text": prompt}]}] + }, + timeout=30 + ) + + data = res.json() + + if "candidates" in data: + return data["candidates"][0]["content"]["parts"][0]["text"] + + return None + + except: + return None + + +def ask_llm(prompt, model="auto"): + + def clean(res): + if not res: + return None + res = str(res).strip() + return res if len(res) > 10 else None # IMPORTANT + + # -------- OPENROUTER -------- + if model == "openrouter": + res = clean(ask_openrouter(prompt)) + return res if res else "❌ OpenRouter returned empty response" + + # -------- GOOGLE -------- + if model == "google": + res = clean(ask_google(prompt)) + return res if res else "❌ Google returned empty response" + + # -------- AUTO MODE -------- + if model == "auto": + res = clean(ask_openrouter(prompt)) + if res: + return res + + res = clean(ask_google(prompt)) + if res: + return res + + return "❌ All LLM providers failed or returned empty" + + return "❌ Invalid model"