geomind-ai 1.0.3__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geomind-ai
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: AI agent for geospatial analysis with Sentinel-2 satellite imagery
5
5
  Author: Harsh Shinde
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@ GeoMind - Geospatial AI Agent
3
3
 
4
4
  """
5
5
 
6
- __version__ = "1.0.3"
6
+ __version__ = "1.0.4"
7
7
  __author__ = "Harsh Shinde"
8
8
 
9
9
  from .agent import GeoMindAgent
@@ -12,7 +12,14 @@ from datetime import datetime
12
12
 
13
13
  from openai import OpenAI
14
14
 
15
- from .config import OPENROUTER_API_KEY, OPENROUTER_API_URL, OPENROUTER_MODEL
15
+ import requests
16
+
17
+ from .config import (
18
+ OPENROUTER_API_KEY,
19
+ OPENROUTER_API_URL,
20
+ OPENROUTER_MODEL,
21
+ GEOMIND_PROXY_URL,
22
+ )
16
23
  from .tools import (
17
24
  geocode_location,
18
25
  get_bbox_from_location,
@@ -242,28 +249,25 @@ class GeoMindAgent:
242
249
 
243
250
  Args:
244
251
  model: Model name (default: xiaomi/mimo-v2-flash:free)
245
- api_key: OpenRouter API key. If not provided, looks for OPENROUTER_API_KEY env variable.
252
+ api_key: OpenRouter API key. If not provided, uses free proxy.
246
253
  """
247
254
  self.provider = "openrouter"
248
255
  self.api_key = api_key or OPENROUTER_API_KEY
249
256
  self.model_name = model or OPENROUTER_MODEL
250
- self.base_url = OPENROUTER_API_URL
251
-
252
- if not self.api_key:
253
- raise ValueError(
254
- "OpenRouter API key required.\n"
255
- "You can provide it in three ways:\n"
256
- "1. Pass it to the constructor: GeoMindAgent(api_key='your-key')\n"
257
- "2. Set OPENROUTER_API_KEY environment variable\n"
258
- "3. Create a .env file with OPENROUTER_API_KEY=your-key\n"
259
- "\nGet your API key at: https://openrouter.ai/settings/keys"
260
- )
261
-
262
- print(f"🚀 GeoMind Agent initialized with {self.model_name} (OpenRouter)")
263
- print(f" API URL: {self.base_url}")
264
-
265
- # Create OpenAI-compatible client
266
- self.client = OpenAI(base_url=self.base_url, api_key=self.api_key)
257
+ self.use_proxy = not self.api_key # Use proxy if no API key provided
258
+
259
+ if self.use_proxy:
260
+ self.base_url = GEOMIND_PROXY_URL
261
+ print(f"🚀 GeoMind Agent initialized with {self.model_name} (via proxy)")
262
+ print(f" Proxy URL: {self.base_url}")
263
+ # No OpenAI client needed for proxy
264
+ self.client = None
265
+ else:
266
+ self.base_url = OPENROUTER_API_URL
267
+ print(f"🚀 GeoMind Agent initialized with {self.model_name} (OpenRouter)")
268
+ print(f" API URL: {self.base_url}")
269
+ # Create OpenAI-compatible client
270
+ self.client = OpenAI(base_url=self.base_url, api_key=self.api_key)
267
271
 
268
272
  # Chat history
269
273
  self.history = []
@@ -296,6 +300,31 @@ When users ask for imagery:
296
300
 
297
301
  Always explain what you're doing and interpret results in a helpful way."""
298
302
 
303
+ def _call_llm(self, messages: list, tools: list) -> dict:
304
+ """Call LLM via proxy or direct OpenAI client."""
305
+ if self.use_proxy:
306
+ # Use proxy endpoint
307
+ payload = {
308
+ "model": self.model_name,
309
+ "messages": messages,
310
+ "tools": tools,
311
+ "tool_choice": "auto",
312
+ "max_tokens": 4096,
313
+ }
314
+ response = requests.post(self.base_url, json=payload, timeout=120)
315
+ response.raise_for_status()
316
+ return response.json()
317
+ else:
318
+ # Use OpenAI client directly
319
+ response = self.client.chat.completions.create(
320
+ model=self.model_name,
321
+ messages=messages,
322
+ tools=tools,
323
+ tool_choice="auto",
324
+ max_tokens=4096,
325
+ )
326
+ return response.model_dump()
327
+
299
328
  def _execute_function(self, name: str, args: dict) -> dict:
300
329
  """Execute a function call and return the result."""
301
330
  print(f" 🔧 Executing: {name}({args})")
@@ -329,42 +358,39 @@ Always explain what you're doing and interpret results in a helpful way."""
329
358
  while iteration < max_iterations:
330
359
  iteration += 1
331
360
 
332
- # Call the model
333
- response = self.client.chat.completions.create(
334
- model=self.model_name,
335
- messages=messages,
336
- tools=TOOLS,
337
- tool_choice="auto",
338
- max_tokens=4096,
339
- )
361
+ # Call the model (via proxy or direct)
362
+ response_data = self._call_llm(messages, TOOLS)
340
363
 
341
- assistant_message = response.choices[0].message
364
+ # Extract assistant message from response
365
+ choice = response_data["choices"][0]
366
+ assistant_message = choice["message"]
342
367
 
343
368
  # Check if there are tool calls
344
- if assistant_message.tool_calls:
369
+ tool_calls = assistant_message.get("tool_calls", [])
370
+ if tool_calls:
345
371
  # Add assistant message with tool calls to messages
346
372
  messages.append(
347
373
  {
348
374
  "role": "assistant",
349
- "content": assistant_message.content or "",
375
+ "content": assistant_message.get("content") or "",
350
376
  "tool_calls": [
351
377
  {
352
- "id": tc.id,
378
+ "id": tc["id"],
353
379
  "type": "function",
354
380
  "function": {
355
- "name": tc.function.name,
356
- "arguments": tc.function.arguments,
381
+ "name": tc["function"]["name"],
382
+ "arguments": tc["function"]["arguments"],
357
383
  },
358
384
  }
359
- for tc in assistant_message.tool_calls
385
+ for tc in tool_calls
360
386
  ],
361
387
  }
362
388
  )
363
389
 
364
390
  # Execute each tool call
365
- for tool_call in assistant_message.tool_calls:
366
- func_name = tool_call.function.name
367
- func_args = json.loads(tool_call.function.arguments)
391
+ for tool_call in tool_calls:
392
+ func_name = tool_call["function"]["name"]
393
+ func_args = json.loads(tool_call["function"]["arguments"])
368
394
 
369
395
  result = self._execute_function(func_name, func_args)
370
396
 
@@ -372,13 +398,13 @@ Always explain what you're doing and interpret results in a helpful way."""
372
398
  messages.append(
373
399
  {
374
400
  "role": "tool",
375
- "tool_call_id": tool_call.id,
401
+ "tool_call_id": tool_call["id"],
376
402
  "content": json.dumps(result, default=str),
377
403
  }
378
404
  )
379
405
  else:
380
406
  # No tool calls, we have a final response
381
- final_text = assistant_message.content or ""
407
+ final_text = assistant_message.get("content") or ""
382
408
 
383
409
  # Add to history
384
410
  self.history.append({"role": "assistant", "content": final_text})
@@ -49,7 +49,10 @@ OUTPUT_DIR.mkdir(exist_ok=True)
49
49
  GEOCODER_USER_AGENT = "geomind_agent_v0.1"
50
50
 
51
51
  # OpenRouter API Configuration
52
- # OpenRouter provides access to multiple AI models via API
53
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
52
+ # Uses Cloudflare Workers proxy to securely handle API key
53
+ GEOMIND_PROXY_URL = "https://geomind-proxy.harshinde.workers.dev"
54
+
55
+ # Users can optionally provide their own OpenRouter key to bypass proxy
56
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
54
57
  OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1")
55
58
  OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "xiaomi/mimo-v2-flash:free")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geomind-ai
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: AI agent for geospatial analysis with Sentinel-2 satellite imagery
5
5
  Author: Harsh Shinde
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "geomind-ai"
7
- version = "1.0.3"
7
+ version = "1.0.4"
8
8
  description = "AI agent for geospatial analysis with Sentinel-2 satellite imagery"
9
9
  readme = "README.md"
10
10
  authors = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes