geomind-ai 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl
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.
- geomind/__init__.py +1 -1
- geomind/agent.py +41 -30
- geomind/cli.py +68 -2
- geomind/config.py +3 -3
- {geomind_ai-1.0.3.dist-info → geomind_ai-1.0.5.dist-info}/METADATA +1 -1
- geomind_ai-1.0.5.dist-info/RECORD +14 -0
- geomind_ai-1.0.3.dist-info/RECORD +0 -14
- {geomind_ai-1.0.3.dist-info → geomind_ai-1.0.5.dist-info}/WHEEL +0 -0
- {geomind_ai-1.0.3.dist-info → geomind_ai-1.0.5.dist-info}/entry_points.txt +0 -0
- {geomind_ai-1.0.3.dist-info → geomind_ai-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {geomind_ai-1.0.3.dist-info → geomind_ai-1.0.5.dist-info}/top_level.txt +0 -0
geomind/__init__.py
CHANGED
geomind/agent.py
CHANGED
|
@@ -12,7 +12,11 @@ from datetime import datetime
|
|
|
12
12
|
|
|
13
13
|
from openai import OpenAI
|
|
14
14
|
|
|
15
|
-
from .config import
|
|
15
|
+
from .config import (
|
|
16
|
+
OPENROUTER_API_KEY,
|
|
17
|
+
OPENROUTER_API_URL,
|
|
18
|
+
OPENROUTER_MODEL,
|
|
19
|
+
)
|
|
16
20
|
from .tools import (
|
|
17
21
|
geocode_location,
|
|
18
22
|
get_bbox_from_location,
|
|
@@ -241,8 +245,8 @@ class GeoMindAgent:
|
|
|
241
245
|
Initialize the GeoMind agent.
|
|
242
246
|
|
|
243
247
|
Args:
|
|
244
|
-
model: Model name (default:
|
|
245
|
-
api_key: OpenRouter API key
|
|
248
|
+
model: Model name (default: google/gemini-2.0-flash-exp:free)
|
|
249
|
+
api_key: OpenRouter API key (required).
|
|
246
250
|
"""
|
|
247
251
|
self.provider = "openrouter"
|
|
248
252
|
self.api_key = api_key or OPENROUTER_API_KEY
|
|
@@ -252,15 +256,14 @@ class GeoMindAgent:
|
|
|
252
256
|
if not self.api_key:
|
|
253
257
|
raise ValueError(
|
|
254
258
|
"OpenRouter API key required.\n"
|
|
255
|
-
"
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
"
|
|
259
|
+
"Get your FREE API key at: https://openrouter.ai/settings/keys\n\n"
|
|
260
|
+
"Then provide it in one of these ways:\n"
|
|
261
|
+
"1. Run: geomind --api-key YOUR_KEY\n"
|
|
262
|
+
"2. Set environment variable: OPENROUTER_API_KEY=YOUR_KEY\n"
|
|
263
|
+
"3. Create .env file with: OPENROUTER_API_KEY=YOUR_KEY"
|
|
260
264
|
)
|
|
261
265
|
|
|
262
|
-
print(f"🚀 GeoMind Agent initialized with {self.model_name}
|
|
263
|
-
print(f" API URL: {self.base_url}")
|
|
266
|
+
print(f"🚀 GeoMind Agent initialized with {self.model_name}")
|
|
264
267
|
|
|
265
268
|
# Create OpenAI-compatible client
|
|
266
269
|
self.client = OpenAI(base_url=self.base_url, api_key=self.api_key)
|
|
@@ -296,6 +299,17 @@ When users ask for imagery:
|
|
|
296
299
|
|
|
297
300
|
Always explain what you're doing and interpret results in a helpful way."""
|
|
298
301
|
|
|
302
|
+
def _call_llm(self, messages: list, tools: list) -> dict:
|
|
303
|
+
"""Call LLM via OpenAI-compatible client."""
|
|
304
|
+
response = self.client.chat.completions.create(
|
|
305
|
+
model=self.model_name,
|
|
306
|
+
messages=messages,
|
|
307
|
+
tools=tools,
|
|
308
|
+
tool_choice="auto",
|
|
309
|
+
max_tokens=4096,
|
|
310
|
+
)
|
|
311
|
+
return response.model_dump()
|
|
312
|
+
|
|
299
313
|
def _execute_function(self, name: str, args: dict) -> dict:
|
|
300
314
|
"""Execute a function call and return the result."""
|
|
301
315
|
print(f" 🔧 Executing: {name}({args})")
|
|
@@ -329,42 +343,39 @@ Always explain what you're doing and interpret results in a helpful way."""
|
|
|
329
343
|
while iteration < max_iterations:
|
|
330
344
|
iteration += 1
|
|
331
345
|
|
|
332
|
-
# Call the model
|
|
333
|
-
|
|
334
|
-
model=self.model_name,
|
|
335
|
-
messages=messages,
|
|
336
|
-
tools=TOOLS,
|
|
337
|
-
tool_choice="auto",
|
|
338
|
-
max_tokens=4096,
|
|
339
|
-
)
|
|
346
|
+
# Call the model (via proxy or direct)
|
|
347
|
+
response_data = self._call_llm(messages, TOOLS)
|
|
340
348
|
|
|
341
|
-
|
|
349
|
+
# Extract assistant message from response
|
|
350
|
+
choice = response_data["choices"][0]
|
|
351
|
+
assistant_message = choice["message"]
|
|
342
352
|
|
|
343
353
|
# Check if there are tool calls
|
|
344
|
-
|
|
354
|
+
tool_calls = assistant_message.get("tool_calls", [])
|
|
355
|
+
if tool_calls:
|
|
345
356
|
# Add assistant message with tool calls to messages
|
|
346
357
|
messages.append(
|
|
347
358
|
{
|
|
348
359
|
"role": "assistant",
|
|
349
|
-
"content": assistant_message.content or "",
|
|
360
|
+
"content": assistant_message.get("content") or "",
|
|
350
361
|
"tool_calls": [
|
|
351
362
|
{
|
|
352
|
-
"id": tc
|
|
363
|
+
"id": tc["id"],
|
|
353
364
|
"type": "function",
|
|
354
365
|
"function": {
|
|
355
|
-
"name": tc
|
|
356
|
-
"arguments": tc
|
|
366
|
+
"name": tc["function"]["name"],
|
|
367
|
+
"arguments": tc["function"]["arguments"],
|
|
357
368
|
},
|
|
358
369
|
}
|
|
359
|
-
for tc in
|
|
370
|
+
for tc in tool_calls
|
|
360
371
|
],
|
|
361
372
|
}
|
|
362
373
|
)
|
|
363
374
|
|
|
364
375
|
# Execute each tool call
|
|
365
|
-
for tool_call in
|
|
366
|
-
func_name = tool_call
|
|
367
|
-
func_args = json.loads(tool_call
|
|
376
|
+
for tool_call in tool_calls:
|
|
377
|
+
func_name = tool_call["function"]["name"]
|
|
378
|
+
func_args = json.loads(tool_call["function"]["arguments"])
|
|
368
379
|
|
|
369
380
|
result = self._execute_function(func_name, func_args)
|
|
370
381
|
|
|
@@ -372,13 +383,13 @@ Always explain what you're doing and interpret results in a helpful way."""
|
|
|
372
383
|
messages.append(
|
|
373
384
|
{
|
|
374
385
|
"role": "tool",
|
|
375
|
-
"tool_call_id": tool_call
|
|
386
|
+
"tool_call_id": tool_call["id"],
|
|
376
387
|
"content": json.dumps(result, default=str),
|
|
377
388
|
}
|
|
378
389
|
)
|
|
379
390
|
else:
|
|
380
391
|
# No tool calls, we have a final response
|
|
381
|
-
final_text = assistant_message.content or ""
|
|
392
|
+
final_text = assistant_message.get("content") or ""
|
|
382
393
|
|
|
383
394
|
# Add to history
|
|
384
395
|
self.history.append({"role": "assistant", "content": final_text})
|
geomind/cli.py
CHANGED
|
@@ -3,12 +3,39 @@ Command-line interface for GeoMind.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
|
+
import os
|
|
6
7
|
import argparse
|
|
8
|
+
from pathlib import Path
|
|
7
9
|
from typing import Optional
|
|
8
10
|
|
|
9
11
|
from .agent import GeoMindAgent
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
# Config file path for storing API key
|
|
15
|
+
CONFIG_DIR = Path.home() / ".geomind"
|
|
16
|
+
CONFIG_FILE = CONFIG_DIR / "config"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_saved_api_key() -> Optional[str]:
|
|
20
|
+
"""Get API key saved on user's PC."""
|
|
21
|
+
if CONFIG_FILE.exists():
|
|
22
|
+
try:
|
|
23
|
+
return CONFIG_FILE.read_text().strip()
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def save_api_key(api_key: str) -> bool:
|
|
30
|
+
"""Save API key to user's PC."""
|
|
31
|
+
try:
|
|
32
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
CONFIG_FILE.write_text(api_key)
|
|
34
|
+
return True
|
|
35
|
+
except Exception:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
12
39
|
def main():
|
|
13
40
|
"""Main CLI entry point for the geomind package."""
|
|
14
41
|
parser = argparse.ArgumentParser(
|
|
@@ -56,9 +83,20 @@ Environment Variables:
|
|
|
56
83
|
parser.add_argument(
|
|
57
84
|
"--version", "-v", action="store_true", help="Show version and exit"
|
|
58
85
|
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--clear-key", action="store_true", help="Clear saved API key"
|
|
88
|
+
)
|
|
59
89
|
|
|
60
90
|
args = parser.parse_args()
|
|
61
91
|
|
|
92
|
+
if args.clear_key:
|
|
93
|
+
if CONFIG_FILE.exists():
|
|
94
|
+
CONFIG_FILE.unlink()
|
|
95
|
+
print("✅ Saved API key cleared.")
|
|
96
|
+
else:
|
|
97
|
+
print("ℹ️ No saved API key found.")
|
|
98
|
+
sys.exit(0)
|
|
99
|
+
|
|
62
100
|
if args.version:
|
|
63
101
|
from . import __version__
|
|
64
102
|
|
|
@@ -68,8 +106,12 @@ Environment Variables:
|
|
|
68
106
|
# Start interactive or single-query mode
|
|
69
107
|
try:
|
|
70
108
|
if args.query:
|
|
71
|
-
# Single query mode
|
|
72
|
-
|
|
109
|
+
# Single query mode - also use saved key
|
|
110
|
+
api_key = args.api_key or get_saved_api_key()
|
|
111
|
+
if not api_key:
|
|
112
|
+
print("❌ No API key found. Run 'geomind' first to set up.")
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
agent = GeoMindAgent(model=args.model, api_key=api_key)
|
|
73
115
|
agent.chat(args.query)
|
|
74
116
|
else:
|
|
75
117
|
# Interactive mode
|
|
@@ -95,6 +137,30 @@ def run_interactive(model: Optional[str] = None, api_key: Optional[str] = None):
|
|
|
95
137
|
print("Type 'reset' to start a new conversation")
|
|
96
138
|
print("=" * 60)
|
|
97
139
|
|
|
140
|
+
# Check for API key in order: argument > env > saved file
|
|
141
|
+
from .config import OPENROUTER_API_KEY
|
|
142
|
+
|
|
143
|
+
if not api_key:
|
|
144
|
+
api_key = OPENROUTER_API_KEY
|
|
145
|
+
|
|
146
|
+
if not api_key:
|
|
147
|
+
api_key = get_saved_api_key()
|
|
148
|
+
|
|
149
|
+
if not api_key:
|
|
150
|
+
print("\n🔑 OpenRouter API key required (FREE)")
|
|
151
|
+
print(" Get yours at: https://openrouter.ai/settings/keys\n")
|
|
152
|
+
api_key = input(" Enter your API key: ").strip()
|
|
153
|
+
|
|
154
|
+
if not api_key:
|
|
155
|
+
print("\n❌ No API key provided. Exiting.")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Save the key for future use
|
|
159
|
+
if save_api_key(api_key):
|
|
160
|
+
print(" ✅ API key saved! You won't need to enter it again.\n")
|
|
161
|
+
else:
|
|
162
|
+
print(" ⚠️ Could not save API key. You'll need to enter it next time.\n")
|
|
163
|
+
|
|
98
164
|
agent = GeoMindAgent(model=model, api_key=api_key)
|
|
99
165
|
|
|
100
166
|
while True:
|
geomind/config.py
CHANGED
|
@@ -49,7 +49,7 @@ OUTPUT_DIR.mkdir(exist_ok=True)
|
|
|
49
49
|
GEOCODER_USER_AGENT = "geomind_agent_v0.1"
|
|
50
50
|
|
|
51
51
|
# OpenRouter API Configuration
|
|
52
|
-
#
|
|
53
|
-
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
|
52
|
+
# Get your free API key at: https://openrouter.ai/settings/keys
|
|
53
|
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
|
54
54
|
OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1")
|
|
55
|
-
OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "
|
|
55
|
+
OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "google/gemini-2.0-flash-exp:free")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
geomind/__init__.py,sha256=xq7_AdhGGOgR1EMaBVysKaqbON5BJmSvg4msrYiO5Pg,151
|
|
2
|
+
geomind/agent.py,sha256=dpHH8q-x6v0CXbnTEPlJvhLb-2AqEiU4eZT__sjLl2U,15658
|
|
3
|
+
geomind/cli.py,sha256=VS5rASORRuWyXRcgUvUG2qCUX15qRG6KSvkj8EVIsbQ,5250
|
|
4
|
+
geomind/config.py,sha256=MQ9eP21kljXsQRIu6q9pyc7LCrLegEfVii5jYT6X23w,2027
|
|
5
|
+
geomind/tools/__init__.py,sha256=8iumGwIFHh8Bj1VJNgZtmKnEBqCy6_cRkzYENDUH7x4,720
|
|
6
|
+
geomind/tools/geocoding.py,sha256=hiLpzHpkJP6IgWAUtZMnHL6qpkWcYWVLpGe0yfYxXv8,3007
|
|
7
|
+
geomind/tools/processing.py,sha256=vMp8PMb8h8QiBRBFRvI_TGRqEDBTDQvSV0zvC1Ji5bc,9976
|
|
8
|
+
geomind/tools/stac_search.py,sha256=V6230l4aHjedPWXu-3Cjmfc6diSFh5zsycewUko0W8k,6452
|
|
9
|
+
geomind_ai-1.0.5.dist-info/licenses/LICENSE,sha256=aveu0ERm7I3NnIu8rtpKdvd0eyRpmktXKU0PBABtSN0,1069
|
|
10
|
+
geomind_ai-1.0.5.dist-info/METADATA,sha256=HriO8wTTwVxyMWY9aE9MzhQf2JrohOYFKAdmUfSDwvg,2233
|
|
11
|
+
geomind_ai-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
geomind_ai-1.0.5.dist-info/entry_points.txt,sha256=2nPR3faYKl0-1epccvzMJ2xdi-Q1Vt7aOSvA84oIWnw,45
|
|
13
|
+
geomind_ai-1.0.5.dist-info/top_level.txt,sha256=rjKWNSNRhq4R9xJoZGsG-eAaH7BmTVNvfrrbcaJMIIs,8
|
|
14
|
+
geomind_ai-1.0.5.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
geomind/__init__.py,sha256=n5p83zzIUSJlgGvqvyilFeU5u84if0DoTwsdj4gcLNE,151
|
|
2
|
-
geomind/agent.py,sha256=t5xQ5oldG5BrZfuk67RSKuDNl1TmIxwvPqbkvJqO_hE,15398
|
|
3
|
-
geomind/cli.py,sha256=tmMkvO9scyvzpRtiKbcb1jtWn3fSgFoywQmuusmjDuI,3283
|
|
4
|
-
geomind/config.py,sha256=hv3DNM7QbCuOSWuPmXeiVxEbGxH7fRLxOc8jexd7rrY,2011
|
|
5
|
-
geomind/tools/__init__.py,sha256=8iumGwIFHh8Bj1VJNgZtmKnEBqCy6_cRkzYENDUH7x4,720
|
|
6
|
-
geomind/tools/geocoding.py,sha256=hiLpzHpkJP6IgWAUtZMnHL6qpkWcYWVLpGe0yfYxXv8,3007
|
|
7
|
-
geomind/tools/processing.py,sha256=vMp8PMb8h8QiBRBFRvI_TGRqEDBTDQvSV0zvC1Ji5bc,9976
|
|
8
|
-
geomind/tools/stac_search.py,sha256=V6230l4aHjedPWXu-3Cjmfc6diSFh5zsycewUko0W8k,6452
|
|
9
|
-
geomind_ai-1.0.3.dist-info/licenses/LICENSE,sha256=aveu0ERm7I3NnIu8rtpKdvd0eyRpmktXKU0PBABtSN0,1069
|
|
10
|
-
geomind_ai-1.0.3.dist-info/METADATA,sha256=y6efOTHaisR2kWz4J4BpQLc6rLV7iMyTvKkuK2yuYM8,2233
|
|
11
|
-
geomind_ai-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
geomind_ai-1.0.3.dist-info/entry_points.txt,sha256=2nPR3faYKl0-1epccvzMJ2xdi-Q1Vt7aOSvA84oIWnw,45
|
|
13
|
-
geomind_ai-1.0.3.dist-info/top_level.txt,sha256=rjKWNSNRhq4R9xJoZGsG-eAaH7BmTVNvfrrbcaJMIIs,8
|
|
14
|
-
geomind_ai-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|