# agent.py import os from typing import Optional, List from crewai import Agent, Task, Crew, Process from crewai_tools import GithubSearchTool from google import genai # Gemini client (google-genai package) from dotenv import load_dotenv load_dotenv() # --------------------------- # CONFIG # --------------------------- GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") if not GOOGLE_API_KEY: raise RuntimeError("❌ Missing GOOGLE_API_KEY (get one from https://aistudio.google.com)") # Gemini Client client = genai.Client(api_key=GOOGLE_API_KEY) MODEL_NAME = os.getenv("GEMINI_MODEL", "gemini-1.5-flash") DEFAULT_CONTENT_TYPES = ["code", "pr", "issue", "repo"] # --------------------------- # Gemini Embedding Adapter (Free Embeddings) # --------------------------- class GeminiEmbedding: """Uses Google Gemini text-embedding-004 model (free tier)""" def __init__(self, model="text-embedding-004", api_key=None): self.model = model self.client = genai.Client(api_key=api_key or GOOGLE_API_KEY) def embed_documents(self, texts: List[str]) -> List[List[float]]: vectors = [] for text in texts: try: res = self.client.models.embed_content(model=self.model, contents=text) vectors.append(res.embedding.values) except Exception as e: print(f"⚠️ Embedding error: {e}") vectors.append([]) return vectors def embed_query(self, text: str) -> List[float]: try: res = self.client.models.embed_content(model=self.model, contents=text) return res.embedding.values except Exception as e: print(f"⚠️ Embedding query error: {e}") return [] # --------------------------- # Gemini LLM Wrapper # --------------------------- class GeminiLLM: def __init__(self, model: str): self.model = model def generate(self, prompt: str) -> str: """CrewAI-compatible LLM generate method.""" try: response = client.models.generate_content( model=self.model, contents=prompt, generation_config={"temperature": 0.7, "max_output_tokens": 1024} ) return response.text except Exception as e: return f"⚠️ Gemini API error: {e}" # Instantiate LLM + embedder gemini_llm = GeminiLLM(MODEL_NAME) embedder = GeminiEmbedding(api_key=GOOGLE_API_KEY) # --------------------------- # GitHub Tool using free embeddings # --------------------------- def github_tool(repo_url: Optional[str] = None) -> GithubSearchTool: """Create a GitHub Search Tool with free Gemini embeddings (no OpenAI key).""" if not GITHUB_TOKEN: raise RuntimeError("Missing GITHUB_TOKEN in environment.") if repo_url: return GithubSearchTool( github_repo=repo_url, gh_token=GITHUB_TOKEN, content_types=DEFAULT_CONTENT_TYPES, embedder=embedder, ) return GithubSearchTool( gh_token=GITHUB_TOKEN, content_types=DEFAULT_CONTENT_TYPES, embedder=embedder, ) # --------------------------- # AGENTS # --------------------------- def make_agents(repo_url: str): repo_search = github_tool(repo_url) repo_mapper = Agent( role="Repository Mapper", goal="Map repo structure, dependencies, and frameworks.", backstory="Understands directory trees, tech stacks, and configuration files.", tools=[repo_search], llm=gemini_llm, verbose=True, ) code_reviewer = Agent( role="Code Reviewer", goal="Perform code review for quality, structure, and clarity.", backstory="A senior engineer giving actionable review comments with examples.", tools=[repo_search], llm=gemini_llm, verbose=True, ) security_auditor = Agent( role="Security Auditor", goal="Identify secrets, vulnerabilities, unsafe APIs, and dependencies.", backstory="A white-hat hacker finding issues and giving fixes.", tools=[repo_search], llm=gemini_llm, verbose=True, ) doc_explainer = Agent( role="Documentation Explainer", goal="Summarize architecture, data flow, and how to run the project.", backstory="Explains tech systems simply and clearly with examples.", tools=[repo_search], llm=gemini_llm, verbose=True, ) manager = Agent( role="Engineering Manager", goal="Coordinate all agents and compile a clear, cohesive final report.", backstory="Ensures a professional, well-structured final document.", allow_delegation=True, llm=gemini_llm, verbose=True, ) return repo_mapper, code_reviewer, security_auditor, doc_explainer, manager # --------------------------- # TASKS # --------------------------- def make_tasks(repo_url: str, brief: str = ""): prefix = f"Target Repository: {repo_url}\nBrief: {brief}\nInclude file paths where relevant." t_map = Task( description=f"{prefix}\nMap the repository structure, dependencies, languages, and build tools.", expected_output="Markdown-formatted repository map with bullets and paths.", agent_role="Repository Mapper", ) t_review = Task( description=f"{prefix}\nPerform detailed code review (readability, refactors, testing, etc.).", expected_output="Actionable review bullets grouped by issue type.", agent_role="Code Reviewer", ) t_sec = Task( description=f"{prefix}\nPerform security audit (secrets, vulnerabilities, dependencies).", expected_output="Security findings table (Issue | Evidence | Risk | Fix).", agent_role="Security Auditor", ) t_doc = Task( description=f"{prefix}\nExplain architecture, modules, data flow, setup, and usage.", expected_output="Readable explanation with Quickstart and architecture overview.", agent_role="Documentation Explainer", ) t_merge = Task( description="Merge all outputs into a clean, single Markdown report with clear sections and TOC.", expected_output="Final cohesive Markdown report.", agent_role="Engineering Manager", ) return t_map, t_review, t_sec, t_doc, t_merge # --------------------------- # RUNNER # --------------------------- def run_repo_review(repo_url: str, brief: str = "") -> str: repo_mapper, reviewer, auditor, explainer, manager = make_agents(repo_url) t_map, t_review, t_sec, t_doc, t_merge = make_tasks(repo_url, brief) crew = Crew( agents=[repo_mapper, reviewer, auditor, explainer, manager], tasks=[t_map, t_review, t_sec, t_doc, t_merge], process=Process.hierarchical, manager_agent=manager, verbose=True, ) result = crew.kickoff() return str(result)