geomind-ai 1.0.2__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.2
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.2"
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,
@@ -236,29 +243,31 @@ class GeoMindAgent:
236
243
  Uses OpenRouter API for access to multiple AI models.
237
244
  """
238
245
 
239
- def __init__(self, model: Optional[str] = None):
246
+ def __init__(self, model: Optional[str] = None, api_key: Optional[str] = None):
240
247
  """
241
248
  Initialize the GeoMind agent.
242
249
 
243
250
  Args:
244
251
  model: Model name (default: xiaomi/mimo-v2-flash:free)
252
+ api_key: OpenRouter API key. If not provided, uses free proxy.
245
253
  """
246
254
  self.provider = "openrouter"
247
- self.api_key = OPENROUTER_API_KEY
255
+ self.api_key = api_key or OPENROUTER_API_KEY
248
256
  self.model_name = model or OPENROUTER_MODEL
249
- self.base_url = OPENROUTER_API_URL
250
-
251
- if not self.api_key:
252
- raise ValueError(
253
- "OpenRouter API key required. Set OPENROUTER_API_KEY in .env file.\n"
254
- "Get your API key at: https://openrouter.ai/settings/keys"
255
- )
256
-
257
- print(f"🚀 GeoMind Agent initialized with {self.model_name} (OpenRouter)")
258
- print(f" API URL: {self.base_url}")
259
-
260
- # Create OpenAI-compatible client
261
- 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)
262
271
 
263
272
  # Chat history
264
273
  self.history = []
@@ -291,6 +300,31 @@ When users ask for imagery:
291
300
 
292
301
  Always explain what you're doing and interpret results in a helpful way."""
293
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
+
294
328
  def _execute_function(self, name: str, args: dict) -> dict:
295
329
  """Execute a function call and return the result."""
296
330
  print(f" 🔧 Executing: {name}({args})")
@@ -324,42 +358,39 @@ Always explain what you're doing and interpret results in a helpful way."""
324
358
  while iteration < max_iterations:
325
359
  iteration += 1
326
360
 
327
- # Call the model
328
- response = self.client.chat.completions.create(
329
- model=self.model_name,
330
- messages=messages,
331
- tools=TOOLS,
332
- tool_choice="auto",
333
- max_tokens=4096,
334
- )
361
+ # Call the model (via proxy or direct)
362
+ response_data = self._call_llm(messages, TOOLS)
335
363
 
336
- assistant_message = response.choices[0].message
364
+ # Extract assistant message from response
365
+ choice = response_data["choices"][0]
366
+ assistant_message = choice["message"]
337
367
 
338
368
  # Check if there are tool calls
339
- if assistant_message.tool_calls:
369
+ tool_calls = assistant_message.get("tool_calls", [])
370
+ if tool_calls:
340
371
  # Add assistant message with tool calls to messages
341
372
  messages.append(
342
373
  {
343
374
  "role": "assistant",
344
- "content": assistant_message.content or "",
375
+ "content": assistant_message.get("content") or "",
345
376
  "tool_calls": [
346
377
  {
347
- "id": tc.id,
378
+ "id": tc["id"],
348
379
  "type": "function",
349
380
  "function": {
350
- "name": tc.function.name,
351
- "arguments": tc.function.arguments,
381
+ "name": tc["function"]["name"],
382
+ "arguments": tc["function"]["arguments"],
352
383
  },
353
384
  }
354
- for tc in assistant_message.tool_calls
385
+ for tc in tool_calls
355
386
  ],
356
387
  }
357
388
  )
358
389
 
359
390
  # Execute each tool call
360
- for tool_call in assistant_message.tool_calls:
361
- func_name = tool_call.function.name
362
- 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"])
363
394
 
364
395
  result = self._execute_function(func_name, func_args)
365
396
 
@@ -367,13 +398,13 @@ Always explain what you're doing and interpret results in a helpful way."""
367
398
  messages.append(
368
399
  {
369
400
  "role": "tool",
370
- "tool_call_id": tool_call.id,
401
+ "tool_call_id": tool_call["id"],
371
402
  "content": json.dumps(result, default=str),
372
403
  }
373
404
  )
374
405
  else:
375
406
  # No tool calls, we have a final response
376
- final_text = assistant_message.content or ""
407
+ final_text = assistant_message.get("content") or ""
377
408
 
378
409
  # Add to history
379
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.2
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.2"
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