elismasilva commited on
Commit
7b4c3d2
·
1 Parent(s): 204c089

change code agent to tool calling agent

Browse files
Files changed (3) hide show
  1. app.py +14 -8
  2. config/gitrepo_agent_prompt.yaml +29 -182
  3. services/agent_chat.py +166 -278
app.py CHANGED
@@ -714,7 +714,7 @@ with gr.Blocks(title="GitRepo Inspector", theme=theme, css=css_code, head=APP_HE
714
  )
715
  )
716
  with gr.Row():
717
- msg = gr.Textbox(label="Message", max_lines=2, scale=4, placeholder="Ask: 'What's up with issue #123?'")
718
  send_btn = gr.Button("Send", variant="primary", scale=1)
719
  with gr.Row():
720
  clear_chat_btn = gr.Button("🧹 Clear History")
@@ -731,7 +731,12 @@ with gr.Blocks(title="GitRepo Inspector", theme=theme, css=css_code, head=APP_HE
731
  ["Please analyze issue #12264 again right now"],
732
  ["Check issue #12044 using the 'openai' provider and model 'gpt-4o'"],
733
  ["Analyze issue #12432 using 'gpt-oss-120b'"],
734
- ["Is issue #11910 a duplicate? Check now."],
 
 
 
 
 
735
  ],
736
  inputs=[msg],
737
  label="Message Examples",
@@ -793,12 +798,13 @@ with gr.Blocks(title="GitRepo Inspector", theme=theme, css=css_code, head=APP_HE
793
  yield history, "", gr.skip(), agent_state
794
 
795
  # Check Trigger
796
- triggers = re.findall(r"\[VIEW:#(\d+)\]", full_response)
797
- detected_issue = None
798
- if triggers:
799
- # Get the last mentioned (or first, your choice)
 
800
  # Usually the last is the conclusion focus
801
- detected_issue = int(triggers[-1])
802
 
803
  # Remove ALL [VIEW:#...] tags from final message
804
  last_msg = history[-1]
@@ -898,7 +904,7 @@ with gr.Blocks(title="GitRepo Inspector", theme=theme, css=css_code, head=APP_HE
898
  prio_out = gr.HTML(label="AI Strategy Report", elem_id="prio-report")
899
 
900
  # BOTTOM BAR
901
- with BottomBar(label="🤖 AI Assistant", bring_to_front=False, height=320, open=False, rounded_borders=True):
902
  gr.Markdown("### 📊 Workflow Agent Activity Logs", elem_id="logs-header")
903
  with gr.Row():
904
  auto_refresh_timer = gr.Timer(value=30, active=True)
 
714
  )
715
  )
716
  with gr.Row():
717
+ msg = gr.Textbox(label="Message", max_lines=4, scale=4, placeholder="Ask: 'What's up with issue #123?'")
718
  send_btn = gr.Button("Send", variant="primary", scale=1)
719
  with gr.Row():
720
  clear_chat_btn = gr.Button("🧹 Clear History")
 
731
  ["Please analyze issue #12264 again right now"],
732
  ["Check issue #12044 using the 'openai' provider and model 'gpt-4o'"],
733
  ["Analyze issue #12432 using 'gpt-oss-120b'"],
734
+ ["Is issue #11910 a duplicate? Check now."],
735
+ ["Find the most recent open issue about 'bug' and tell me if it's already resolved."],
736
+ ["Do we have any duplicates related to 'audio'? If yes, which is the original issue?"],
737
+ ["I'm seeing an error '422 Unprocessable Entity'. Is this a known issue in the repo?"],
738
+ ["What is the latest version of Gradio library?"],
739
+ ["This issue mentions 'Pydantic v2'. What is the migration guide for that?"],
740
  ],
741
  inputs=[msg],
742
  label="Message Examples",
 
798
  yield history, "", gr.skip(), agent_state
799
 
800
  # Check Trigger
801
+ #triggers = re.findall(r"\[VIEW:#(\d+)\]", full_response)
802
+ match = re.search(r"\[VIEW:#(\d+)\]\s*$", full_response, re.MULTILINE)
803
+ detected_issue = int(match.group(1)) if match else None
804
+ if match:
805
+ ## Get the last mentioned (or first, your choice)
806
  # Usually the last is the conclusion focus
807
+ #detected_issue = int(match[-1])
808
 
809
  # Remove ALL [VIEW:#...] tags from final message
810
  last_msg = history[-1]
 
904
  prio_out = gr.HTML(label="AI Strategy Report", elem_id="prio-report")
905
 
906
  # BOTTOM BAR
907
+ with BottomBar(label="🤖 AI Assistant", bring_to_front=False, height=320, open=True, rounded_borders=True):
908
  gr.Markdown("### 📊 Workflow Agent Activity Logs", elem_id="logs-header")
909
  with gr.Row():
910
  auto_refresh_timer = gr.Timer(value=30, active=True)
config/gitrepo_agent_prompt.yaml CHANGED
@@ -1,192 +1,39 @@
1
- system_prompt: |-
2
- You are the GitRepo Inspector Agent. Your goal is to answer questions about GitHub issues using the provided tools.
3
-
4
- **CONTEXT & OVERRIDES:**
5
- - The user has a default repository and LLM selected in the UI. This is your primary context.
6
- - HOWEVER, if the user's prompt explicitly asks to use a DIFFERENT provider or model (e.g., "using OpenAI"), you MUST override the default from the context and use the one specified in the prompt.
7
- - **Priority: Prompt > Context**.
8
-
9
- **RULES:**
10
- - You MUST start every step with `Thought:`.
11
- - Then, write Python code in a ` ```python ... ``` ` block.
12
- - Call one tool at a time.
13
- - Finish your work by calling `final_answer("your text")`.
14
-
15
- **TOOLS:**
16
- - `get_issue_status(issue_number, repo_url)`: Gets a pre-formatted AI report for a single issue.
17
- - `search_issues(repo_url, author, verdict, query)`: Finds a LIST of issues.
18
- - `trigger_live_analysis(issue_number, repo_url, github_token)`: Generate AI analysis report for a single issue.
19
- - `web_search(query)`: Searches the web.
20
 
21
- **DEFAULT REPO:**
22
- If not specified, assume repo_url is `https://github.com/gradio-app/gradio`.
23
-
24
- **UI CONTROL:**
25
- - If your final answer is about ONE issue, append `[VIEW:#<issue_number>]` at the end of the text.
26
-
27
- **MODEL-TO-PROVIDER MAPPING:**
28
- - Gemini models: 'gemini-2.0-flash'
29
- - OpenAI models: 'gpt-4o', 'gpt-4o-mini'
30
- - Nebius models: 'deepseek-ai/DeepSeek-R1-0528', 'meta-llama/Llama-3.3-70B-Instruct', 'nvidia/Llama-3_1-Nemotron-Ultra-253B-v1", "meta-llama/Meta-Llama-3.1-8B-Instruct-fast'
31
- - SambaNova models: 'DeepSeek-R1', 'DeepSeek-V3-0324', 'DeepSeek-V3.1', 'Meta-Llama-3.1-8B-Instruct', 'Meta-Llama-3.3-70B-Instruct', 'gpt-oss-120b'
32
-
33
- If a user specifies a model name, you MUST infer the correct provider from this map
34
-
35
- **EXAMPLES:**
36
-
37
- ---
38
- Task: "Which repository are we analyzing?"
39
- Thought: I know the context. I will answer directly using `final_answer`.
40
- ```python
41
- final_answer("We are currently analyzing the Gradio repository: https://github.com/gradio-app/gradio")
42
- ```
43
- ---
44
-
45
- Task: "whats up with issue 123?"
46
- Thought: The user wants to see everything about issue 123. I will get the report and trigger the UI to show the full details.
47
- ```python
48
- import re
49
- report_text = get_issue_status(issue_number=123, repo_url="https://github.com/gradio-app/gradio")
50
-
51
- if "ERROR" in report_text:
52
- final_answer(f"No report found for #123. Should I analyze it now?")
53
- else:
54
- verdict_match = re.search(r"VERDICT: (\w+)", report_text)
55
- verdict = verdict_match.group(1) if verdict_match else "unknown"
56
- final_answer(f"The verdict for issue #123 is **{verdict}**. I've opened the full report for you. [VIEW:#123]")
57
- ```
58
-
59
- ---
60
- Task: "What is the status of issue 456?"
61
- Thought: The user just wants the status. I will get the report, extract only the verdict, and give a short answer without triggering the UI.
62
- ```python
63
- import re
64
- report_text = get_issue_status(issue_number=456, repo_url="https://github.com/gradio-app/gradio")
65
-
66
- if "ERROR" in report_text:
67
- final_answer("No analysis found in the database for issue #123")
68
- else:
69
- verdict_match = re.search(r"VERDICT: (\w+)", report_text)
70
- verdict = verdict_match.group(1) if verdict_match else "unknown"
71
- final_answer(f"The current AI-analyzed status for issue #456 is **{verdict}**.")
72
- ```
73
-
74
- ---
75
- Task: "Show me the full report for issue 123"
76
- Thought: The user wants to see the full report. I will use `get_issue_status` to get the data, but I only need to confirm it exists. The UI trigger will handle the display.
77
- ```python
78
- report_text = get_issue_status(issue_number=123, repo_url="https://github.com/gradio-app/gradio")
79
-
80
- if "ERROR" in report_text:
81
- final_answer("I couldn't find a report for that issue.")
82
- else:
83
- final_answer(f"Opening full report for issue #123... [VIEW:#123]")
84
- ```
85
-
86
- ---
87
- Task: "Why was issue #123 marked as duplicate?"
88
- Thought: The user wants the justification for a duplicate. I need to get the report with `get_issue_status` and then use Python's `re` module to parse the "Justification:" text from the report.
89
- ```python
90
- import re
91
- report_text = get_issue_status(issue_number=123, repo_url="https://github.com/gradio-app/gradio")
92
-
93
- if "duplicate" in report_text.lower():
94
- justification_match = re.search(r"Justification:</strong>\s*(.*?)(</li>|<br>|\n)", report_text, re.DOTALL)
95
-
96
- if justification_match:
97
- reason = justification_match.group(1).strip()
98
- final_answer(f"It was marked as duplicate because: '{reason}'. [VIEW:#123]")
99
- else:
100
- final_answer("The report confirms it's a duplicate, but the specific reason was not found. [VIEW:#123]")
101
- ```
102
-
103
- ---
104
- Task: "Re-analyze issue #123 with OpenAI"
105
- Thought: The user wants to re-analyze with a specific provider. The context says the user selected 'openai' and 'gpt-4o-mini' in the UI. I will use the `trigger_live_analysis` tool and pass these parameters.
106
- ```python
107
- result = trigger_live_analysis(
108
- issue_number=123,
109
- repo_url="https://github.com/gradio-app/gradio",
110
- provider="openai",
111
- model="gpt-4o-mini"
112
- )
113
- final_answer(f"Re-analysis with OpenAI is complete. [VIEW:#123]")
114
- ```
115
-
116
- ---
117
- Task: "Analyze/Check issue #456 with Nebius"
118
- Context: "Selected provider is 'gemini'"
119
- Thought: The user's prompt explicitly asks to use 'Nebius', which overrides the default 'gemini' from the context. I will call `trigger_live_analysis` with `provider='nebius'`.
120
- ```python
121
- result = trigger_live_analysis(
122
- issue_number=456,
123
- repo_url="https://github.com/gradio-app/gradio",
124
- provider="nebius" # Override
125
- )
126
- final_answer(f"Analysis with Nebius is complete. [VIEW:#456]")
127
- ```
128
-
129
- ---
130
- Task: "Analyze/Check issue #456 with Nebius using the DeepSeek-R1 model"
131
- Context: "Selected provider is 'gemini'"
132
- Thought: The user is overriding both the provider and the model. I must call `trigger_live_analysis` with `provider='nebius'` and `model='deepseek-ai/DeepSeek-R1'`.
133
- ```python
134
- result = trigger_live_analysis(
135
- issue_number=456,
136
- repo_url="https://github.com/gradio-app/gradio",
137
- provider="nebius",
138
- model="deepseek-ai/DeepSeek-R1-0528" # Override completo
139
- )
140
- final_answer(f"Analysis with Nebius (DeepSeek) is complete. [VIEW:#456]")
141
- ```
142
-
143
- ---
144
- Task: "Analyze/Check issue #789 with gpt-4o-mini"
145
- Thought: The user specified the model 'gpt-4o-mini'. According to my mapping, this model belongs to the 'openai' provider. I will call the tool with both parameters.
146
- ```python
147
- result = trigger_live_analysis(
148
- issue_number=789,
149
- repo_url="https://github.com/gradio-app/gradio",
150
- provider="openai", # I inferred this
151
- model="gpt-4o-mini"
152
- )
153
- final_answer(f"Analysis with gpt-4o-mini is complete. [VIEW:#789]")
154
- ```
155
-
156
- ---
157
- Task: "Is issue #123 a duplicate? Check now."
158
- Thought: The user is asking for the status of a specific issue. I should first check the database using `get_issue_status` to see if we already have an analysis.
159
- ```python
160
- import re
161
- report_text = get_issue_status(issue_number=123, repo_url="https://github.com/gradio-app/gradio")
162
-
163
- if "duplicate" in report_text.lower():
164
- final_answer(f"Yes, my last analysis shows issue #123 is a duplicate. I've opened the report. [VIEW:#123]")
165
- elif "ERROR" in report_text:
166
- final_answer("I don't have a report for #123. Should I run a full analysis now?")
167
- else:
168
- verdict_match = re.search(r"VERDICT: (\w+)", report_text)
169
- verdict = verdict_match.group(1) if verdict_match else "unknown"
170
- final_answer(f"My last analysis for issue #123 shows a verdict of **{verdict}**, not duplicate. [VIEW:#123]")
171
- ```
172
-
173
- ---
174
- Task: "Find issues by abidlabs"
175
- Thought: I need to search for multiple issues. I will use `search_issues`. It returns a formatted string list.
176
- ```python
177
- list_of_issues = search_issues(
178
- repo_url="https://github.com/gradio-app/gradio",
179
- author="abidlabs"
180
- )
181
- final_answer(list_of_issues)
182
- ```
183
 
184
  planning:
185
  initial_plan: |-
186
  You are a planner. Given a task, create a simple plan.
187
  Task: {{task}}
188
 
189
- 1. Facts: What do I know? (Issue #, Repo)
190
  2. Plan: Which tools to call?
191
 
192
  Write the plan and end with <end_plan>.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ system_prompt: |-
3
+ You are GitRepo Inspector elite autonomous agent for GitHub issue triage.
4
+
5
+ YOU HAVE THESE TOOLS:
6
+ 1. get_issue_status(issue_number: int, repo_url: str) returns JSON report
7
+ 2. search_issues(repo_url: str, query: str="", verdict: str=None, author: str=None, limit: int=8) → list of issues
8
+ 3. trigger_live_analysis(issue_number: int, repo_url: str, provider: str=None, model: str=None) → forces fresh analysis
9
+
10
+ STRICT RULES:
11
+ Be concise, confident, professional
12
+ Use **bold** for issue numbers and verdicts
13
+ • When referring to ONE specific issue → end answer with exactly: [VIEW:#12345]
14
+ Only call trigger_live_analysis when user explicitly says "now", "again", "re-analyze", "check again"
15
+ • Never write Thought:, code blocks, or apologize
16
+
17
+ PROVIDER OVERRIDES (user always wins):
18
+ • "OpenAI", "gpt-4o" → provider="openai"
19
+ "DeepSeek", "R1" provider="nebius", model="deepseek-ai/DeepSeek-R1-0528"
20
+ "Llama 70B" provider="sambanova", model="Meta-Llama-3.3-70B-Instruct"
21
+
22
+ EXAMPLES:
23
+ User: status of #12345
24
+ → **#12345** is **Duplicate** of #10000. Full report opened.
25
+ [VIEW:#12345]
26
+
27
+ User: re-analyze #456 with DeepSeek
28
+ → Re-analysis with DeepSeek-R1 completed!
29
+ [VIEW:#456]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  planning:
32
  initial_plan: |-
33
  You are a planner. Given a task, create a simple plan.
34
  Task: {{task}}
35
 
36
+ 1. Facts: What do I know?
37
  2. Plan: Which tools to call?
38
 
39
  Write the plan and end with <end_plan>.
services/agent_chat.py CHANGED
@@ -1,325 +1,213 @@
 
1
  import os
2
  import re
3
- import markdown
4
- import yaml
5
- from dotenv import load_dotenv
6
- from smolagents import CodeAgent, LiteLLMModel, tool
7
  from smolagents.mcp_client import MCPClient as SmolMCPClient
8
  from gradio_client import Client as GradioClient
9
  from config.constants import AVAILABLE_MODELS_BY_PROVIDER
10
  from services.db import save_analysis_report
11
 
12
- load_dotenv()
13
-
14
  MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "https://mcp-1st-birthday-gitrepo-inspector-mcp.hf.space/")
15
- GEMINI_API_KEY = os.getenv("GOOGLE_API_KEY")
16
-
17
- class ModelFixerWrapper(LiteLLMModel):
18
- """
19
- Inherits from LiteLLMModel and overrides the `generate` method
20
- to fix Gemini's common output formatting mistakes.
21
- """
22
- def generate(self, messages, **kwargs):
23
- raw_chat_message = super().generate(messages, **kwargs)
24
- raw_output = raw_chat_message.content if raw_chat_message.content else ""
25
-
26
- # 1. Try to find the code block
27
- # Regex that searches for ```python ... ``` OR ``` ... ```
28
- code_match = re.search(r"```(python)?\n(.*?)\n```", raw_output, re.DOTALL)
29
-
30
- if code_match:
31
- # If found, ensure it's formatted correctly as ```python
32
- code_content = code_match.group(2).strip()
33
- fixed_code_block = f"```python\n{code_content}\n```"
34
-
35
- # Replace the original block with the corrected one
36
- fixed_output = raw_output[:code_match.start()] + fixed_code_block + raw_output[code_match.end():]
37
- else:
38
- # If not found, don't modify
39
- fixed_output = raw_output
40
 
41
- # Ensure "Thought:"
42
- if "```" in fixed_output and "Thought:" not in fixed_output:
43
- fixed_output = "Thought: Executing.\n" + fixed_output
44
-
45
- raw_chat_message.content = fixed_output
46
- return raw_chat_message
47
 
48
-
49
  @tool
50
- def trigger_live_analysis(issue_number: int, repo_url: str, provider: str = "gemini", model: str = None, github_token: str = None) -> str:
51
  """
52
- FORCES a NEW, LIVE analysis of an issue, ignoring any cached data.
53
- Use this ONLY when the user explicitly asks to "re-analyze", "check again", "analyze now", or "run again".
54
- Do NOT use this for simple status checks.
55
-
56
  Args:
57
- issue_number: The issue number (e.g. 123).
58
- repo_url: The full repository URL.
59
- provider (str, optional): The AI provider name. Defaults to "gemini".
60
- model (str, optional): The model identifier to use for analysis.
61
- Defaults to "gemini-2.0-flash".
62
- github_token: Optional GitHub token for authentication.
 
 
 
 
 
 
 
63
  """
64
  try:
65
-
66
- if not model:
67
- models_for_provider = AVAILABLE_MODELS_BY_PROVIDER.get(provider.lower(), [])
68
- if models_for_provider:
69
- model = models_for_provider[0]
70
- else:
71
- model = "gemini-2.0-flash"
72
-
73
- # Use GradioClient directly for control over output format
74
  client = GradioClient(MCP_SERVER_URL)
75
-
76
- # Call MCP Tool
77
- result = client.predict(
78
- repo_url,
79
- int(issue_number),
80
- provider,
81
- model,
82
- github_token,
83
- None,
84
- api_name="/analyze_github_issue"
85
- )
86
-
87
- html_report = result[0]
88
- thought = result[1]
89
-
90
- verdict = "unresolved" # Default
91
- if "✅ Resolved" in html_report: verdict = "resolved"
92
- elif "⚠️ Possibly Resolved" in html_report: verdict = "possibly_resolved"
93
- elif "🔥 Duplicate" in html_report: verdict = "duplicate"
94
-
95
- try:
96
- save_analysis_report(
97
- repo_url=repo_url,
98
- issue_number=int(issue_number),
99
- provider=provider,
100
- model=model,
101
- verdict=verdict,
102
- body=html_report,
103
- thought=thought,
104
- action=None,
105
- priority=None
106
- )
107
- print(f"💾 Saved analysis for #{issue_number} to DB.")
108
-
109
-
110
- except Exception as e:
111
- print(f"⚠️ Failed to save report: {e}")
112
-
113
- # Clean HTML tags for LLM readability
114
- clean_report = re.sub(r'<[^>]+>', '', html_report)
115
-
116
- return f"""
117
- ANALYSIS COMPLETED.
118
-
119
- --- THINKING PROCESS ---
120
- {thought}
121
-
122
- --- FINAL REPORT ---
123
- {clean_report}
124
- """
125
  except Exception as e:
126
- return f"Analysis failed: {e}"
 
127
 
128
  @tool
129
- def get_issue_status(issue_number: int, repo_url: str) -> str:
 
 
 
 
 
 
130
  """
131
- Retrieves the detailed AI analysis report for a SINGLE, SPECIFIC issue.
132
- Use this when the user asks for "details", "status of", "what's up with" a specific issue number.
133
-
134
  Args:
135
- issue_number: The specific issue number (e.g., 123).
136
- repo_url: The full repository URL.
 
 
 
 
 
 
 
 
 
 
 
137
  """
138
  try:
139
  client = GradioClient(MCP_SERVER_URL)
140
-
141
  result = client.predict(
142
- repo_url,
143
- issue_number,
144
- api_name="/get_issue_report"
145
  )
146
-
147
- import json
148
- if isinstance(result, str):
149
- try:
150
- data = json.loads(result)
151
- except:
152
- return str(result)
153
- elif isinstance(result, dict):
154
- data = result
155
- else:
156
- return f"Unexpected format: {type(result)}"
157
 
158
- if "error" in data and "No AI report found" in data['error']:
159
- return f"ERROR: No analysis found in the database for issue #{issue_number}."
160
-
161
- if "error" in data:
162
- return f"Status: Not Found\nDetails: {data['error']}"
163
-
164
- body = data.get('report', '')
165
- if "<div" in body and "Analyzed by" in body:
166
- final_html = body.replace("```html", "").replace("```", "")
167
  else:
168
- final_html = markdown.markdown(body, extensions=['tables', 'fenced_code'])
169
- final_html = f"<h3 style='color:#4B5563; border-bottom:1px solid #eee; padding-bottom:5px;'>Analysis Report</h3>{final_html}"
170
-
171
- return f"""
172
- --- ISSUE REPORT #{data.get('issue')} ---
173
- VERDICT: {data.get('verdict')}
174
-
175
- --- ANALYSIS BODY ---
176
- {data.get('report')}
177
-
178
- --- PROPOSED ACTION ---
179
- {data.get('action')}
180
- """
181
-
182
  except Exception as e:
183
- return f"Failed to fetch status: {e}"
 
184
 
185
  @tool
186
- def search_issues(
 
187
  repo_url: str,
188
- query: str = "",
189
- issue_number: int = None,
190
- status: str = "open",
191
- verdict: str = None,
192
- author: str = None,
193
- limit: int = 5,
194
- state: str = None,
195
- max_results: int = None,
196
- per_page: int = None
197
  ) -> str:
198
  """
199
- Searches a LIST of issues based on KEYWORDS, author, or verdict.
200
- Use this for BROAD questions like "Are there issues about...?" or "Find all issues by...".
201
- DO NOT use this to get details of a single known issue number.
202
-
203
  Args:
204
- repo_url: The full repository URL.
205
- query: Text to search in title or body.
206
- issue_number: Specific issue ID to find.
207
- status: GitHub state ('open', 'closed'). Default: 'open'.
208
- verdict: AI Analysis Verdict ('resolved', 'duplicate', 'unresolved').
209
- author: Filter by issue author.
210
- limit: Max results to return. Default: 5.
211
- state: Alias for status parameter.
212
- max_results: Alias for limit parameter.
213
- per_page: Alias for limit parameter.
214
  """
215
-
216
- final_status = state if state else status
217
- final_limit = max_results if max_results else (per_page if per_page else limit)
218
-
219
  try:
 
 
 
220
  client = GradioClient(MCP_SERVER_URL)
221
  result = client.predict(
222
- repo_url,
223
- query,
224
- issue_number,
225
- final_status,
226
- verdict,
227
- author,
228
- final_limit,
229
- api_name="/search_issues"
230
  )
231
-
232
- import json
233
- if isinstance(result, str):
234
- try:
235
- data = json.loads(result)
236
- except:
237
- return f"Error parsing JSON from MCP: {result}"
238
- else:
239
- data = result
240
-
241
- if not data:
242
- return "No issues found matching criteria."
243
- if isinstance(data, dict) and "error" in data:
244
- return f"Search Error: {data['error']}"
245
- if isinstance(data, str):
246
- return data
247
-
248
- output = f"Found {len(data)} issues:\n"
249
- for item in data:
250
- if not isinstance(item, dict): continue
251
-
252
- i_id = item.get('id', '?')
253
- i_title = item.get('title', 'No Title')
254
- i_author = item.get('author', 'Unknown')
255
- i_verdict = item.get('verdict', 'pending')
256
- i_snippet = item.get('snippet', '')[:100].replace('\n', ' ')
257
-
258
- output += f"- #{i_id} '{i_title}' (by @{i_author}) [Verdict: {i_verdict}]\n"
259
- output += f" Snippet: {i_snippet}...\n"
260
-
261
- return output
262
 
263
- except Exception as e:
264
- return f"Search tool failed: {str(e)}"
265
-
266
- def create_dashboard_agent():
267
- if not GEMINI_API_KEY:
268
- print("⚠️ Warning: GOOGLE_API_KEY not found.")
269
 
270
- try:
271
- print(f"🚀 Initializing Agent...")
272
- yaml_path = os.path.join(os.path.dirname(__file__), "../config/gitrepo_agent_prompt.yaml")
273
-
274
- with open(yaml_path, 'r', encoding='utf-8') as f:
275
- prompt_templates = yaml.safe_load(f)
276
-
277
- model = ModelFixerWrapper(
278
- model_id="gemini/gemini-2.5-flash",
279
- api_key=GEMINI_API_KEY,
280
- temperature=0.2
281
- )
282
-
283
- # 1. Import Automatic Tools from MCP (Search, WebSearch, etc.)
284
- # Ensure URL ends with /gradio_api/mcp/sse for smolagents discovery
285
- sse_url = f"{MCP_SERVER_URL.rstrip('/')}/gradio_api/mcp/sse"
286
- mcp_client = SmolMCPClient({"url":sse_url, "transport":"sse"})
287
- auto_tools = mcp_client.get_tools()
288
-
289
- # 2. Filter out tools we want to override
290
- # Remove 'analyze_github_issue' to use our 'trigger_live_analysis' wrapper
291
- # Remove 'get_issue_report' if we want to force usage of 'get_issue_status' wrapper
292
- filtered_tools = [
293
- t for t in auto_tools
294
- if t.name not in ['analyze_github_issue',
295
- 'search_issues',
296
- 'get_issue_report',
297
- 'generate_sketch_to_ui',
298
- 'generate_theme'
299
- ]
300
- ]
301
-
302
- # 3. Combine: Auto Tools + Custom Wrappers
303
- final_tools = filtered_tools + [trigger_live_analysis, get_issue_status, search_issues]
304
 
305
- # 4. Create Agent
306
- agent = CodeAgent(
307
- tools=final_tools,
 
308
  model=model,
309
- prompt_templates=prompt_templates,
310
- additional_authorized_imports=["json", "re", "time", "datetime", "ast"],
311
- add_base_tools=False,
312
- max_steps=8,
313
- planning_interval=3
314
  )
315
- if agent:
316
- print(f"Agent initialized successfully!")
317
- else:
318
- print(f"Agent not initialized!")
319
- return agent
320
 
 
321
  except Exception as e:
322
- print(f" Error creating agent: {e}")
323
- import traceback
324
- traceback.print_exc()
325
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/agent_chat.py
2
  import os
3
  import re
4
+ import json
5
+ from typing import Dict, List, Any, Optional
6
+ from smolagents import CodeAgent, LiteLLMModel, tool, ToolCallingAgent
 
7
  from smolagents.mcp_client import MCPClient as SmolMCPClient
8
  from gradio_client import Client as GradioClient
9
  from config.constants import AVAILABLE_MODELS_BY_PROVIDER
10
  from services.db import save_analysis_report
11
 
 
 
12
  MCP_SERVER_URL = os.getenv("MCP_SERVER_URL", "https://mcp-1st-birthday-gitrepo-inspector-mcp.hf.space/")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # ===================================================================
15
+ # TOOLS WITH CORRECT DOCSTRINGS
16
+ # ===================================================================
 
 
 
17
 
 
18
  @tool
19
+ def get_issue_status(issue_number: int, repo_url: str) -> Dict[str, Any]:
20
  """
21
+ Retrieves the latest AI analysis report for a specific GitHub issue from the database.
22
+
 
 
23
  Args:
24
+ issue_number (int): The GitHub issue number (e.g. 12345).
25
+ repo_url (str): Full repository URL (e.g. "https://github.com/gradio-app/gradio").
26
+
27
+ Returns:
28
+ dict: Structured report with keys:
29
+ - issue_number (int)
30
+ - title (str)
31
+ - verdict (str)
32
+ - report (str) – clean text without HTML
33
+ - action (dict or None)
34
+ - model_used (str)
35
+ - has_report (bool)
36
+ - error (str, optional)
37
  """
38
  try:
 
 
 
 
 
 
 
 
 
39
  client = GradioClient(MCP_SERVER_URL)
40
+ result = client.predict(repo_url, issue_number, api_name="/get_issue_report")
41
+
42
+ # Normalize MCP response
43
+ if isinstance(result, str):
44
+ data = json.loads(result) if "{" in result else {"error": result}
45
+ else:
46
+ data = result if isinstance(result, dict) else {"error": str(result)}
47
+
48
+ if data.get("error"):
49
+ return {"error": data["error"], "has_report": False}
50
+
51
+ # Remove HTML tags for easy LLM reading
52
+ raw_body = data.get("report", "")
53
+ clean_body = re.sub(r'<[^>]+>', '', raw_body).strip()
54
+
55
+ return {
56
+ "issue_number": int(data.get("issue", issue_number)),
57
+ "title": data.get("title", "No title"),
58
+ "verdict": data.get("verdict", "Pending Analysis"),
59
+ "report": clean_body,
60
+ "action": data.get("action"),
61
+ "model_used": data.get("llm_model", "unknown"),
62
+ "has_report": True
63
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  except Exception as e:
65
+ return {"error": str(e), "has_report": False}
66
+
67
 
68
  @tool
69
+ def search_issues(
70
+ repo_url: str,
71
+ query: str = "",
72
+ verdict: Optional[str] = None,
73
+ author: Optional[str] = None,
74
+ limit: int = 8
75
+ ) -> List[Dict[str, Any]]:
76
  """
77
+ Searches for GitHub issues matching the given criteria.
78
+
 
79
  Args:
80
+ repo_url (str): Full repository URL.
81
+ query (str, optional): Text to search in title/body.
82
+ verdict (str, optional): AI verdict filter ("resolved", "duplicate", "unresolved", etc.).
83
+ author (str, optional): GitHub username of the issue author.
84
+ limit (int, optional): Maximum number of results (default: 8).
85
+
86
+ Returns:
87
+ list[dict]: List of issues, each containing:
88
+ - issue_number (int)
89
+ - title (str)
90
+ - author (str)
91
+ - verdict (str)
92
+ - snippet (str)
93
  """
94
  try:
95
  client = GradioClient(MCP_SERVER_URL)
 
96
  result = client.predict(
97
+ repo_url, query, None, "open", verdict, author, limit,
98
+ api_name="/search_issues"
 
99
  )
 
 
 
 
 
 
 
 
 
 
 
100
 
101
+ if isinstance(result, str):
102
+ data = json.loads(result) if ("[" in result or "{" in result) else []
 
 
 
 
 
 
 
103
  else:
104
+ data = result if isinstance(result, list) else []
105
+
106
+ return [
107
+ {
108
+ "issue_number": item.get("id"),
109
+ "title": item.get("title", "No title"),
110
+ "author": item.get("author", "unknown"),
111
+ "verdict": item.get("verdict", "pending"),
112
+ "snippet": (item.get("snippet") or "")[:150].replace("\n", " ")
113
+ }
114
+ for item in data
115
+ ]
 
 
116
  except Exception as e:
117
+ return [{"error": str(e)}]
118
+
119
 
120
  @tool
121
+ def trigger_live_analysis(
122
+ issue_number: int,
123
  repo_url: str,
124
+ provider: str = "gemini",
125
+ model: Optional[str] = None,
126
+ github_token: Optional[str] = None
 
 
 
 
 
 
127
  ) -> str:
128
  """
129
+ Forces a brand-new AI analysis of an issue, ignoring any cached data.
130
+ Use ONLY when the user explicitly asks to re-analyze or "now", "again", etc.
131
+
 
132
  Args:
133
+ issue_number (int): The GitHub issue number.
134
+ repo_url (str): Full repository URL.
135
+ provider (str, optional): AI provider ("gemini", "openai", "nebius", "sambanova").
136
+ model (str, optional): Specific model name. If None, uses first model of the provider.
137
+ github_token (str, optional): GitHub token for private repos.
138
+
139
+ Returns:
140
+ str: Confirmation message or error.
 
 
141
  """
 
 
 
 
142
  try:
143
+ if not model:
144
+ model = AVAILABLE_MODELS_BY_PROVIDER.get(provider.lower(), ["gemini-2.0-flash"])[0]
145
+
146
  client = GradioClient(MCP_SERVER_URL)
147
  result = client.predict(
148
+ repo_url, int(issue_number), provider, model, github_token, None,
149
+ api_name="/analyze_github_issue"
 
 
 
 
 
 
150
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
+ html_report, thought = result[0], result[1]
 
 
 
 
 
153
 
154
+ # Detects verdict to save in database
155
+ verdict = "unresolved"
156
+ if "Resolved" in html_report: verdict = "resolved"
157
+ elif "Possibly Resolved" in html_report: verdict = "possibly_resolved"
158
+ elif "Duplicate" in html_report: verdict = "duplicate"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ save_analysis_report(
161
+ repo_url=repo_url,
162
+ issue_number=issue_number,
163
+ provider=provider,
164
  model=model,
165
+ verdict=verdict,
166
+ body=html_report,
167
+ thought=thought
 
 
168
  )
 
 
 
 
 
169
 
170
+ return f"Live analysis completed using {provider} ({model}). Report updated!"
171
  except Exception as e:
172
+ return f"Live analysis failed: {str(e)}"
173
+
174
+
175
+ # ===================================================================
176
+ # AGENT FACTORY
177
+ # ===================================================================
178
+
179
+ def create_dashboard_agent():
180
+ print("Initializing GitRepo Inspector Agent...")
181
+
182
+ model = LiteLLMModel(
183
+ model_id="gemini/gemini-2.5-flash",
184
+ temperature=0.1,
185
+ max_tokens=2048
186
+ )
187
+
188
+ # Loads the clean prompt from YAML config
189
+ import yaml
190
+ yaml_path = os.path.join(os.path.dirname(__file__), "../config/gitrepo_agent_prompt.yaml")
191
+ with open(yaml_path, "r", encoding="utf-8") as f:
192
+ prompt_templates = yaml.safe_load(f)
193
+
194
+ # Automatic MCP tools (keep web_search, etc.)
195
+ sse_url = f"{MCP_SERVER_URL.rstrip('/')}/gradio_api/mcp/sse"
196
+ mcp_client = SmolMCPClient({"url": sse_url, "transport": "sse"})
197
+ auto_tools = [
198
+ t for t in mcp_client.get_tools()
199
+ if t.name not in {"analyze_github_issue", "get_issue_report", "search_issues"}
200
+ ]
201
+
202
+ my_tools = [get_issue_status, search_issues, trigger_live_analysis]
203
+
204
+ agent = ToolCallingAgent(
205
+ tools=auto_tools + my_tools,
206
+ model=model,
207
+ prompt_templates=prompt_templates,
208
+ max_steps=12,
209
+ planning_interval=4
210
+ )
211
+
212
+ print("GitRepo Inspector Agent ready!")
213
+ return agent