Gabriel commited on
Commit
35a45a0
Β·
verified Β·
1 Parent(s): db4cfa5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -59
app.py CHANGED
@@ -32,8 +32,8 @@ class MCPClientWrapper:
32
 
33
  # Make a small test request to validate the key
34
  test_response = test_client.messages.create(
35
- model="claude-sonnet-4-20250514",
36
- max_tokens=1000,
37
  messages=[{"role": "user", "content": "Hi"}]
38
  )
39
 
@@ -55,7 +55,6 @@ class MCPClientWrapper:
55
  if not self.anthropic:
56
  return "❌ Please set your Anthropic API key first"
57
 
58
-
59
  if not server_url or not server_url.strip():
60
  return "❌ Please provide a valid server URL"
61
 
@@ -90,6 +89,63 @@ class MCPClientWrapper:
90
  self.tools = []
91
  self.current_server_url = None
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  def process_message(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]) -> tuple:
94
  if not self.anthropic:
95
  return history + [
@@ -104,7 +160,7 @@ class MCPClientWrapper:
104
  ], gr.Textbox(value="")
105
 
106
  try:
107
- # Convert history to Claude format
108
  claude_messages = []
109
  for msg in history:
110
  if isinstance(msg, ChatMessage):
@@ -112,31 +168,48 @@ class MCPClientWrapper:
112
  else:
113
  role, content = msg.get("role"), msg.get("content")
114
 
115
- if role in ["user", "assistant", "system"]:
116
- claude_messages.append({"role": role, "content": content})
 
 
 
117
 
 
118
  claude_messages.append({"role": "user", "content": message})
119
 
 
 
 
120
  # Convert tools to Claude format
121
  claude_tools = []
122
- for tool in self.tools:
123
- if hasattr(tool, 'name') and hasattr(tool, 'description'):
124
- claude_tools.append({
125
- "name": tool.name,
126
- "description": tool.description,
127
- "input_schema": getattr(tool, 'input_schema', {})
128
- })
 
 
 
 
 
129
 
130
  # Get response from Claude
131
- response = self.anthropic.messages.create(
132
- model="claude-3-5-sonnet-20241022",
133
- max_tokens=1000,
134
- messages=claude_messages,
135
- tools=claude_tools if claude_tools else None
136
- )
 
 
 
 
137
 
138
  result_messages = []
139
 
 
140
  for content in response.content:
141
  if content.type == 'text':
142
  result_messages.append({
@@ -147,6 +220,7 @@ class MCPClientWrapper:
147
  elif content.type == 'tool_use':
148
  tool_name = content.name
149
  tool_args = content.input
 
150
 
151
  result_messages.append({
152
  "role": "assistant",
@@ -159,16 +233,6 @@ class MCPClientWrapper:
159
  }
160
  })
161
 
162
- result_messages.append({
163
- "role": "assistant",
164
- "content": "```json\n" + json.dumps(tool_args, indent=2, ensure_ascii=True) + "\n```",
165
- "metadata": {
166
- "parent_id": f"tool_call_{tool_name}",
167
- "id": f"params_{tool_name}",
168
- "title": "Tool Parameters"
169
- }
170
- })
171
-
172
  # Execute tool using MCP client
173
  try:
174
  # Find the tool and execute it
@@ -183,12 +247,15 @@ class MCPClientWrapper:
183
  else:
184
  result_content = f"Tool {tool_name} not found"
185
 
186
- if result_messages and "metadata" in result_messages[-2]:
187
- result_messages[-2]["metadata"]["status"] = "done"
 
 
 
188
 
189
  result_messages.append({
190
  "role": "assistant",
191
- "content": "Here are the results from the tool:",
192
  "metadata": {
193
  "title": f"Tool Result for {tool_name}",
194
  "status": "done",
@@ -196,53 +263,76 @@ class MCPClientWrapper:
196
  }
197
  })
198
 
199
- # Format result content
200
- if isinstance(result_content, (dict, list)):
201
- result_content = json.dumps(result_content, indent=2)
202
- else:
203
- result_content = str(result_content)
 
 
 
 
 
 
 
204
 
205
- result_messages.append({
206
- "role": "assistant",
207
- "content": "```\n" + result_content + "\n```",
208
- "metadata": {
209
- "parent_id": f"result_{tool_name}",
210
- "id": f"raw_result_{tool_name}",
211
- "title": "Raw Output"
212
- }
 
213
  })
214
 
215
- # Get follow-up response from Claude
216
- claude_messages.append({"role": "user", "content": f"Tool result for {tool_name}: {result_content}"})
217
- next_response = self.anthropic.messages.create(
218
  model="claude-3-5-sonnet-20241022",
219
- max_tokens=1000,
220
  messages=claude_messages,
 
221
  )
222
 
223
- if next_response.content and next_response.content[0].type == 'text':
224
- result_messages.append({
225
- "role": "assistant",
226
- "content": next_response.content[0].text
227
- })
 
 
228
 
229
  except Exception as tool_error:
 
230
  result_messages.append({
231
  "role": "assistant",
232
- "content": f"❌ Error executing tool: {str(tool_error)}",
233
  "metadata": {
234
- "parent_id": f"result_{tool_name}",
235
- "id": f"error_{tool_name}",
236
- "title": "Tool Error"
237
  }
238
  })
239
 
240
  return history + [{"role": "user", "content": message}] + result_messages, gr.Textbox(value="")
241
 
242
  except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
243
  return history + [
244
  {"role": "user", "content": message},
245
- {"role": "assistant", "content": f"❌ Error processing message: {str(e)}"}
246
  ], gr.Textbox(value="")
247
 
248
  client = MCPClientWrapper()
 
32
 
33
  # Make a small test request to validate the key
34
  test_response = test_client.messages.create(
35
+ model="claude-3-5-sonnet-20241022", # Use consistent model name
36
+ max_tokens=10,
37
  messages=[{"role": "user", "content": "Hi"}]
38
  )
39
 
 
55
  if not self.anthropic:
56
  return "❌ Please set your Anthropic API key first"
57
 
 
58
  if not server_url or not server_url.strip():
59
  return "❌ Please provide a valid server URL"
60
 
 
89
  self.tools = []
90
  self.current_server_url = None
91
 
92
+ def _convert_mcp_tool_to_anthropic(self, tool) -> Dict[str, Any]:
93
+ """Convert MCP tool to Anthropic API format"""
94
+ tool_def = {
95
+ "name": tool.name,
96
+ "description": tool.description or f"Execute {tool.name} tool"
97
+ }
98
+
99
+ # Handle input schema - ensure it's proper JSON Schema
100
+ if hasattr(tool, 'input_schema') and tool.input_schema:
101
+ input_schema = tool.input_schema
102
+ # Ensure it has required JSON Schema fields
103
+ if isinstance(input_schema, dict):
104
+ if "type" not in input_schema:
105
+ input_schema["type"] = "object"
106
+ if "properties" not in input_schema and input_schema["type"] == "object":
107
+ input_schema["properties"] = {}
108
+ tool_def["input_schema"] = input_schema
109
+ else:
110
+ # Fallback schema
111
+ tool_def["input_schema"] = {
112
+ "type": "object",
113
+ "properties": {},
114
+ "additionalProperties": True
115
+ }
116
+ else:
117
+ # Default schema for tools without input schema
118
+ tool_def["input_schema"] = {
119
+ "type": "object",
120
+ "properties": {},
121
+ "additionalProperties": True
122
+ }
123
+
124
+ return tool_def
125
+
126
+ def _ensure_message_alternation(self, messages: List[Dict[str, str]]) -> List[Dict[str, str]]:
127
+ """Ensure proper user/assistant alternation in messages"""
128
+ if not messages:
129
+ return messages
130
+
131
+ fixed_messages = []
132
+ last_role = None
133
+
134
+ for msg in messages:
135
+ current_role = msg.get("role")
136
+ if current_role == last_role:
137
+ # Skip duplicate consecutive roles
138
+ continue
139
+ if current_role in ["user", "assistant"]:
140
+ fixed_messages.append(msg)
141
+ last_role = current_role
142
+
143
+ # Ensure it starts with user message
144
+ if fixed_messages and fixed_messages[0]["role"] != "user":
145
+ fixed_messages = fixed_messages[1:]
146
+
147
+ return fixed_messages
148
+
149
  def process_message(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]) -> tuple:
150
  if not self.anthropic:
151
  return history + [
 
160
  ], gr.Textbox(value="")
161
 
162
  try:
163
+ # Convert history to Claude format - only include text messages for API
164
  claude_messages = []
165
  for msg in history:
166
  if isinstance(msg, ChatMessage):
 
168
  else:
169
  role, content = msg.get("role"), msg.get("content")
170
 
171
+ # Only include user/assistant messages, skip metadata-heavy messages
172
+ if role in ["user", "assistant"] and content and not content.startswith("I'll use the"):
173
+ # Skip tool execution messages
174
+ if not (isinstance(msg, dict) and msg.get("metadata")):
175
+ claude_messages.append({"role": role, "content": content})
176
 
177
+ # Add current user message
178
  claude_messages.append({"role": "user", "content": message})
179
 
180
+ # Ensure proper message alternation
181
+ claude_messages = self._ensure_message_alternation(claude_messages)
182
+
183
  # Convert tools to Claude format
184
  claude_tools = []
185
+ if self.tools:
186
+ for tool in self.tools:
187
+ if hasattr(tool, 'name'):
188
+ try:
189
+ claude_tool = self._convert_mcp_tool_to_anthropic(tool)
190
+ claude_tools.append(claude_tool)
191
+ except Exception as e:
192
+ print(f"Warning: Failed to convert tool {tool.name}: {e}")
193
+
194
+ print(f"Debug - Sending to API:")
195
+ print(f"Messages: {json.dumps(claude_messages, indent=2)}")
196
+ print(f"Tools: {json.dumps(claude_tools, indent=2) if claude_tools else 'None'}")
197
 
198
  # Get response from Claude
199
+ api_params = {
200
+ "model": "claude-3-5-sonnet-20241022",
201
+ "max_tokens": 2000,
202
+ "messages": claude_messages
203
+ }
204
+
205
+ if claude_tools:
206
+ api_params["tools"] = claude_tools
207
+
208
+ response = self.anthropic.messages.create(**api_params)
209
 
210
  result_messages = []
211
 
212
+ # Process response content
213
  for content in response.content:
214
  if content.type == 'text':
215
  result_messages.append({
 
220
  elif content.type == 'tool_use':
221
  tool_name = content.name
222
  tool_args = content.input
223
+ tool_use_id = content.id
224
 
225
  result_messages.append({
226
  "role": "assistant",
 
233
  }
234
  })
235
 
 
 
 
 
 
 
 
 
 
 
236
  # Execute tool using MCP client
237
  try:
238
  # Find the tool and execute it
 
247
  else:
248
  result_content = f"Tool {tool_name} not found"
249
 
250
+ # Format result content
251
+ if isinstance(result_content, (dict, list)):
252
+ formatted_result = json.dumps(result_content, indent=2)
253
+ else:
254
+ formatted_result = str(result_content)
255
 
256
  result_messages.append({
257
  "role": "assistant",
258
+ "content": f"```json\n{formatted_result}\n```",
259
  "metadata": {
260
  "title": f"Tool Result for {tool_name}",
261
  "status": "done",
 
263
  }
264
  })
265
 
266
+ # Create proper tool result message for API
267
+ claude_messages.append({
268
+ "role": "assistant",
269
+ "content": [
270
+ {
271
+ "type": "tool_use",
272
+ "id": tool_use_id,
273
+ "name": tool_name,
274
+ "input": tool_args
275
+ }
276
+ ]
277
+ })
278
 
279
+ claude_messages.append({
280
+ "role": "user",
281
+ "content": [
282
+ {
283
+ "type": "tool_result",
284
+ "tool_use_id": tool_use_id,
285
+ "content": str(result_content)
286
+ }
287
+ ]
288
  })
289
 
290
+ # Get follow-up response from Claude with proper tool result format
291
+ follow_up_response = self.anthropic.messages.create(
 
292
  model="claude-3-5-sonnet-20241022",
293
+ max_tokens=2000,
294
  messages=claude_messages,
295
+ tools=claude_tools if claude_tools else None
296
  )
297
 
298
+ # Add follow-up response
299
+ for follow_content in follow_up_response.content:
300
+ if follow_content.type == 'text':
301
+ result_messages.append({
302
+ "role": "assistant",
303
+ "content": follow_content.text
304
+ })
305
 
306
  except Exception as tool_error:
307
+ print(f"Tool execution error: {tool_error}")
308
  result_messages.append({
309
  "role": "assistant",
310
+ "content": f"❌ Error executing tool {tool_name}: {str(tool_error)}",
311
  "metadata": {
312
+ "title": "Tool Error",
313
+ "status": "error",
314
+ "id": f"error_{tool_name}"
315
  }
316
  })
317
 
318
  return history + [{"role": "user", "content": message}] + result_messages, gr.Textbox(value="")
319
 
320
  except Exception as e:
321
+ print(f"API Error: {e}")
322
+ error_message = str(e)
323
+
324
+ # Provide more specific error messages
325
+ if "invalid_request_error" in error_message:
326
+ if "tools" in error_message:
327
+ error_message = f"❌ Tool schema error: {error_message}"
328
+ else:
329
+ error_message = f"❌ Invalid request format: {error_message}"
330
+ elif "authentication_error" in error_message:
331
+ error_message = "❌ Authentication failed. Please check your API key."
332
+
333
  return history + [
334
  {"role": "user", "content": message},
335
+ {"role": "assistant", "content": error_message}
336
  ], gr.Textbox(value="")
337
 
338
  client = MCPClientWrapper()