geomind-ai 1.0.0__tar.gz → 1.0.1__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.
- {geomind_ai-1.0.0/geomind_ai.egg-info → geomind_ai-1.0.1}/PKG-INFO +10 -17
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/README.md +7 -14
- geomind_ai-1.0.1/geomind/__init__.py +11 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/agent.py +127 -127
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/cli.py +12 -14
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/tools/geocoding.py +21 -18
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/tools/processing.py +80 -82
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/tools/stac_search.py +35 -33
- {geomind_ai-1.0.0 → geomind_ai-1.0.1/geomind_ai.egg-info}/PKG-INFO +10 -17
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/pyproject.toml +3 -3
- geomind_ai-1.0.0/geomind/__init__.py +0 -11
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/LICENSE +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/MANIFEST.in +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/config.py +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind/tools/__init__.py +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind_ai.egg-info/SOURCES.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind_ai.egg-info/dependency_links.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind_ai.egg-info/entry_points.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind_ai.egg-info/requires.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/geomind_ai.egg-info/top_level.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/requirements.txt +0 -0
- {geomind_ai-1.0.0 → geomind_ai-1.0.1}/setup.cfg +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geomind-ai
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary: AI agent for geospatial analysis
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: AI agent for geospatial analysis with Sentinel-2 satellite imagery
|
|
5
5
|
Author: Harsh Shinde
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
7
|
+
Project-URL: Homepage, https://github.com/HarshShinde0/GeoMind
|
|
8
8
|
Project-URL: Repository, https://github.com/HarshShinde0/GeoMind
|
|
9
9
|
Project-URL: Documentation, https://github.com/HarshShinde0/GeoMind#readme
|
|
10
10
|
Project-URL: Issues, https://github.com/HarshShinde0/GeoMind/issues
|
|
@@ -36,27 +36,20 @@ Requires-Dist: numpy>=1.26.0
|
|
|
36
36
|
Requires-Dist: python-dotenv>=1.0.0
|
|
37
37
|
Dynamic: license-file
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### Install Dependencies
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
pip install -r requirements.txt
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
###
|
|
46
|
-
|
|
47
|
-
Set your HuggingFace API key in the environment or update `config.py`:
|
|
48
|
-
|
|
49
|
-
```python
|
|
50
|
-
# In geomind/config.py
|
|
51
|
-
HF_API_KEY = "your_huggingface_api_key"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Get a free API key from [HuggingFace](https://huggingface.co/settings/tokens).
|
|
55
|
-
|
|
56
|
-
### 3. Run the Agent
|
|
45
|
+
### Run the Agent
|
|
57
46
|
|
|
58
47
|
```bash
|
|
59
|
-
|
|
48
|
+
# Interactive mode
|
|
49
|
+
geomind
|
|
50
|
+
|
|
51
|
+
# Single query
|
|
52
|
+
geomind --query "Find recent imagery of Paris"
|
|
60
53
|
```
|
|
61
54
|
|
|
62
55
|
## Example Queries
|
|
@@ -1,24 +1,17 @@
|
|
|
1
|
-
###
|
|
1
|
+
### Install Dependencies
|
|
2
2
|
|
|
3
3
|
```bash
|
|
4
4
|
pip install -r requirements.txt
|
|
5
5
|
```
|
|
6
6
|
|
|
7
|
-
###
|
|
8
|
-
|
|
9
|
-
Set your HuggingFace API key in the environment or update `config.py`:
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
# In geomind/config.py
|
|
13
|
-
HF_API_KEY = "your_huggingface_api_key"
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Get a free API key from [HuggingFace](https://huggingface.co/settings/tokens).
|
|
17
|
-
|
|
18
|
-
### 3. Run the Agent
|
|
7
|
+
### Run the Agent
|
|
19
8
|
|
|
20
9
|
```bash
|
|
21
|
-
|
|
10
|
+
# Interactive mode
|
|
11
|
+
geomind
|
|
12
|
+
|
|
13
|
+
# Single query
|
|
14
|
+
geomind --query "Find recent imagery of Paris"
|
|
22
15
|
```
|
|
23
16
|
|
|
24
17
|
## Example Queries
|
|
@@ -6,12 +6,15 @@ queries about satellite imagery and execute the appropriate tools.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
-
|
|
9
|
+
import re
|
|
10
|
+
from typing import Optional, Callable, Any
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
|
|
12
13
|
from openai import OpenAI
|
|
13
14
|
|
|
14
|
-
from .config import
|
|
15
|
+
from .config import (
|
|
16
|
+
OPENROUTER_API_KEY, OPENROUTER_API_URL, OPENROUTER_MODEL
|
|
17
|
+
)
|
|
15
18
|
from .tools import (
|
|
16
19
|
geocode_location,
|
|
17
20
|
get_bbox_from_location,
|
|
@@ -48,12 +51,12 @@ TOOLS = [
|
|
|
48
51
|
"properties": {
|
|
49
52
|
"place_name": {
|
|
50
53
|
"type": "string",
|
|
51
|
-
"description": "The name of the place to geocode (e.g., 'New York City', 'Paris, France')"
|
|
54
|
+
"description": "The name of the place to geocode (e.g., 'New York City', 'Paris, France')"
|
|
52
55
|
}
|
|
53
56
|
},
|
|
54
|
-
"required": ["place_name"]
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
+
"required": ["place_name"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
57
60
|
},
|
|
58
61
|
{
|
|
59
62
|
"type": "function",
|
|
@@ -65,16 +68,16 @@ TOOLS = [
|
|
|
65
68
|
"properties": {
|
|
66
69
|
"place_name": {
|
|
67
70
|
"type": "string",
|
|
68
|
-
"description": "The name of the place"
|
|
71
|
+
"description": "The name of the place"
|
|
69
72
|
},
|
|
70
73
|
"buffer_km": {
|
|
71
74
|
"type": "number",
|
|
72
|
-
"description": "Buffer distance in kilometers (default: 10)"
|
|
73
|
-
}
|
|
75
|
+
"description": "Buffer distance in kilometers (default: 10)"
|
|
76
|
+
}
|
|
74
77
|
},
|
|
75
|
-
"required": ["place_name"]
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
+
"required": ["place_name"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
78
81
|
},
|
|
79
82
|
{
|
|
80
83
|
"type": "function",
|
|
@@ -87,28 +90,28 @@ TOOLS = [
|
|
|
87
90
|
"bbox": {
|
|
88
91
|
"type": "array",
|
|
89
92
|
"items": {"type": "number"},
|
|
90
|
-
"description": "Bounding box as [min_lon, min_lat, max_lon, max_lat]"
|
|
93
|
+
"description": "Bounding box as [min_lon, min_lat, max_lon, max_lat]"
|
|
91
94
|
},
|
|
92
95
|
"start_date": {
|
|
93
96
|
"type": "string",
|
|
94
|
-
"description": "Start date in YYYY-MM-DD format"
|
|
97
|
+
"description": "Start date in YYYY-MM-DD format"
|
|
95
98
|
},
|
|
96
99
|
"end_date": {
|
|
97
100
|
"type": "string",
|
|
98
|
-
"description": "End date in YYYY-MM-DD format"
|
|
101
|
+
"description": "End date in YYYY-MM-DD format"
|
|
99
102
|
},
|
|
100
103
|
"max_cloud_cover": {
|
|
101
104
|
"type": "number",
|
|
102
|
-
"description": "Maximum cloud cover percentage (0-100)"
|
|
105
|
+
"description": "Maximum cloud cover percentage (0-100)"
|
|
103
106
|
},
|
|
104
107
|
"max_items": {
|
|
105
108
|
"type": "integer",
|
|
106
|
-
"description": "Maximum number of results"
|
|
107
|
-
}
|
|
109
|
+
"description": "Maximum number of results"
|
|
110
|
+
}
|
|
108
111
|
},
|
|
109
|
-
"required": []
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
+
"required": []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
112
115
|
},
|
|
113
116
|
{
|
|
114
117
|
"type": "function",
|
|
@@ -120,24 +123,24 @@ TOOLS = [
|
|
|
120
123
|
"properties": {
|
|
121
124
|
"location_name": {
|
|
122
125
|
"type": "string",
|
|
123
|
-
"description": "Name of the location to search"
|
|
126
|
+
"description": "Name of the location to search"
|
|
124
127
|
},
|
|
125
128
|
"days": {
|
|
126
129
|
"type": "integer",
|
|
127
|
-
"description": "Number of days to look back (default: 7)"
|
|
130
|
+
"description": "Number of days to look back (default: 7)"
|
|
128
131
|
},
|
|
129
132
|
"max_cloud_cover": {
|
|
130
133
|
"type": "number",
|
|
131
|
-
"description": "Maximum cloud cover percentage"
|
|
134
|
+
"description": "Maximum cloud cover percentage"
|
|
132
135
|
},
|
|
133
136
|
"max_items": {
|
|
134
137
|
"type": "integer",
|
|
135
|
-
"description": "Maximum number of results"
|
|
136
|
-
}
|
|
138
|
+
"description": "Maximum number of results"
|
|
139
|
+
}
|
|
137
140
|
},
|
|
138
|
-
"required": []
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
+
"required": []
|
|
142
|
+
}
|
|
143
|
+
}
|
|
141
144
|
},
|
|
142
145
|
{
|
|
143
146
|
"type": "function",
|
|
@@ -147,11 +150,14 @@ TOOLS = [
|
|
|
147
150
|
"parameters": {
|
|
148
151
|
"type": "object",
|
|
149
152
|
"properties": {
|
|
150
|
-
"item_id": {
|
|
153
|
+
"item_id": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"description": "The STAC item ID"
|
|
156
|
+
}
|
|
151
157
|
},
|
|
152
|
-
"required": ["item_id"]
|
|
153
|
-
}
|
|
154
|
-
}
|
|
158
|
+
"required": ["item_id"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
155
161
|
},
|
|
156
162
|
{
|
|
157
163
|
"type": "function",
|
|
@@ -163,20 +169,20 @@ TOOLS = [
|
|
|
163
169
|
"properties": {
|
|
164
170
|
"zarr_url": {
|
|
165
171
|
"type": "string",
|
|
166
|
-
"description": "URL to the SR_10m Zarr asset from a STAC item"
|
|
172
|
+
"description": "URL to the SR_10m Zarr asset from a STAC item"
|
|
167
173
|
},
|
|
168
174
|
"output_path": {
|
|
169
175
|
"type": "string",
|
|
170
|
-
"description": "Optional path to save the output image"
|
|
176
|
+
"description": "Optional path to save the output image"
|
|
171
177
|
},
|
|
172
178
|
"subset_size": {
|
|
173
179
|
"type": "integer",
|
|
174
|
-
"description": "Size to subset the image (default: 1000 pixels)"
|
|
175
|
-
}
|
|
180
|
+
"description": "Size to subset the image (default: 1000 pixels)"
|
|
181
|
+
}
|
|
176
182
|
},
|
|
177
|
-
"required": ["zarr_url"]
|
|
178
|
-
}
|
|
179
|
-
}
|
|
183
|
+
"required": ["zarr_url"]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
180
186
|
},
|
|
181
187
|
{
|
|
182
188
|
"type": "function",
|
|
@@ -188,20 +194,20 @@ TOOLS = [
|
|
|
188
194
|
"properties": {
|
|
189
195
|
"zarr_url": {
|
|
190
196
|
"type": "string",
|
|
191
|
-
"description": "URL to the SR_10m Zarr asset"
|
|
197
|
+
"description": "URL to the SR_10m Zarr asset"
|
|
192
198
|
},
|
|
193
199
|
"output_path": {
|
|
194
200
|
"type": "string",
|
|
195
|
-
"description": "Optional path to save the NDVI image"
|
|
201
|
+
"description": "Optional path to save the NDVI image"
|
|
196
202
|
},
|
|
197
203
|
"subset_size": {
|
|
198
204
|
"type": "integer",
|
|
199
|
-
"description": "Size to subset the image"
|
|
200
|
-
}
|
|
205
|
+
"description": "Size to subset the image"
|
|
206
|
+
}
|
|
201
207
|
},
|
|
202
|
-
"required": ["zarr_url"]
|
|
203
|
-
}
|
|
204
|
-
}
|
|
208
|
+
"required": ["zarr_url"]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
205
211
|
},
|
|
206
212
|
{
|
|
207
213
|
"type": "function",
|
|
@@ -213,67 +219,65 @@ TOOLS = [
|
|
|
213
219
|
"properties": {
|
|
214
220
|
"zarr_url": {
|
|
215
221
|
"type": "string",
|
|
216
|
-
"description": "URL to the Zarr asset"
|
|
222
|
+
"description": "URL to the Zarr asset"
|
|
217
223
|
},
|
|
218
224
|
"bands": {
|
|
219
225
|
"type": "array",
|
|
220
226
|
"items": {"type": "string"},
|
|
221
|
-
"description": "List of band names to analyze"
|
|
222
|
-
}
|
|
227
|
+
"description": "List of band names to analyze"
|
|
228
|
+
}
|
|
223
229
|
},
|
|
224
|
-
"required": ["zarr_url"]
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
230
|
+
"required": ["zarr_url"]
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
228
234
|
]
|
|
229
235
|
|
|
230
236
|
|
|
231
237
|
class GeoMindAgent:
|
|
232
238
|
"""
|
|
233
239
|
GeoMind - An AI agent for geospatial analysis with Sentinel-2 imagery.
|
|
234
|
-
|
|
240
|
+
|
|
235
241
|
Uses OpenRouter API for access to multiple AI models.
|
|
236
242
|
"""
|
|
237
|
-
|
|
238
|
-
def __init__(self, model: Optional[str] = None
|
|
243
|
+
|
|
244
|
+
def __init__(self, model: Optional[str] = None):
|
|
239
245
|
"""
|
|
240
246
|
Initialize the GeoMind agent.
|
|
241
|
-
|
|
247
|
+
|
|
242
248
|
Args:
|
|
243
249
|
model: Model name (default: xiaomi/mimo-v2-flash:free)
|
|
244
|
-
api_key: OpenRouter API key. If not provided, looks for OPENROUTER_API_KEY env variable.
|
|
245
250
|
"""
|
|
246
251
|
self.provider = "openrouter"
|
|
247
|
-
self.api_key =
|
|
252
|
+
self.api_key = OPENROUTER_API_KEY
|
|
248
253
|
self.model_name = model or OPENROUTER_MODEL
|
|
249
254
|
self.base_url = OPENROUTER_API_URL
|
|
250
|
-
|
|
255
|
+
|
|
251
256
|
if not self.api_key:
|
|
252
257
|
raise ValueError(
|
|
253
|
-
"OpenRouter API key required.\n"
|
|
254
|
-
"
|
|
255
|
-
"1. Pass it to the constructor: GeoMindAgent(api_key='your-key')\n"
|
|
256
|
-
"2. Set OPENROUTER_API_KEY environment variable\n"
|
|
257
|
-
"3. Create a .env file with OPENROUTER_API_KEY=your-key\n"
|
|
258
|
-
"\nGet your API key at: https://openrouter.ai/settings/keys"
|
|
258
|
+
"OpenRouter API key required. Set OPENROUTER_API_KEY in .env file.\n"
|
|
259
|
+
"Get your API key at: https://openrouter.ai/settings/keys"
|
|
259
260
|
)
|
|
260
|
-
|
|
261
|
+
|
|
261
262
|
print(f"🚀 GeoMind Agent initialized with {self.model_name} (OpenRouter)")
|
|
262
263
|
print(f" API URL: {self.base_url}")
|
|
263
|
-
|
|
264
|
+
|
|
264
265
|
# Create OpenAI-compatible client
|
|
265
|
-
self.client = OpenAI(
|
|
266
|
-
|
|
266
|
+
self.client = OpenAI(
|
|
267
|
+
base_url=self.base_url,
|
|
268
|
+
api_key=self.api_key
|
|
269
|
+
)
|
|
270
|
+
|
|
267
271
|
# Chat history
|
|
268
272
|
self.history = []
|
|
269
|
-
|
|
273
|
+
|
|
270
274
|
# Add system message
|
|
271
275
|
self.system_prompt = self._get_system_prompt()
|
|
272
|
-
|
|
276
|
+
|
|
273
277
|
def _get_system_prompt(self) -> str:
|
|
274
278
|
"""Get the system prompt for the agent."""
|
|
275
|
-
return f"""You are GeoMind, an expert AI assistant specialized in geospatial analysis
|
|
276
|
-
and satellite imagery. You help users find, analyze, and visualize Sentinel-2 satellite data
|
|
279
|
+
return f"""You are GeoMind, an expert AI assistant specialized in geospatial analysis
|
|
280
|
+
and satellite imagery. You help users find, analyze, and visualize Sentinel-2 satellite data
|
|
277
281
|
from the EOPF (ESA Earth Observation Processing Framework) catalog.
|
|
278
282
|
|
|
279
283
|
Your capabilities include:
|
|
@@ -294,20 +298,20 @@ When users ask for imagery:
|
|
|
294
298
|
3. Offer to create visualizations if data is found
|
|
295
299
|
|
|
296
300
|
Always explain what you're doing and interpret results in a helpful way."""
|
|
297
|
-
|
|
301
|
+
|
|
298
302
|
def _execute_function(self, name: str, args: dict) -> dict:
|
|
299
303
|
"""Execute a function call and return the result."""
|
|
300
304
|
print(f" 🔧 Executing: {name}({args})")
|
|
301
|
-
|
|
305
|
+
|
|
302
306
|
if name not in TOOL_FUNCTIONS:
|
|
303
307
|
return {"error": f"Unknown function: {name}"}
|
|
304
|
-
|
|
308
|
+
|
|
305
309
|
try:
|
|
306
310
|
result = TOOL_FUNCTIONS[name](**args)
|
|
307
311
|
return result
|
|
308
312
|
except Exception as e:
|
|
309
313
|
return {"error": str(e)}
|
|
310
|
-
|
|
314
|
+
|
|
311
315
|
def chat(self, message: str, verbose: bool = True) -> str:
|
|
312
316
|
"""
|
|
313
317
|
Send a message to the agent and get a response.
|
|
@@ -315,19 +319,19 @@ Always explain what you're doing and interpret results in a helpful way."""
|
|
|
315
319
|
if verbose:
|
|
316
320
|
print(f"\n💬 User: {message}")
|
|
317
321
|
print("🤔 Processing...")
|
|
318
|
-
|
|
322
|
+
|
|
319
323
|
# Add user message to history
|
|
320
324
|
self.history.append({"role": "user", "content": message})
|
|
321
|
-
|
|
325
|
+
|
|
322
326
|
# Build messages with system prompt
|
|
323
327
|
messages = [{"role": "system", "content": self.system_prompt}] + self.history
|
|
324
|
-
|
|
328
|
+
|
|
325
329
|
max_iterations = 10
|
|
326
330
|
iteration = 0
|
|
327
|
-
|
|
331
|
+
|
|
328
332
|
while iteration < max_iterations:
|
|
329
333
|
iteration += 1
|
|
330
|
-
|
|
334
|
+
|
|
331
335
|
# Call the model
|
|
332
336
|
response = self.client.chat.completions.create(
|
|
333
337
|
model=self.model_name,
|
|
@@ -336,59 +340,55 @@ Always explain what you're doing and interpret results in a helpful way."""
|
|
|
336
340
|
tool_choice="auto",
|
|
337
341
|
max_tokens=4096,
|
|
338
342
|
)
|
|
339
|
-
|
|
343
|
+
|
|
340
344
|
assistant_message = response.choices[0].message
|
|
341
|
-
|
|
345
|
+
|
|
342
346
|
# Check if there are tool calls
|
|
343
347
|
if assistant_message.tool_calls:
|
|
344
348
|
# Add assistant message with tool calls to messages
|
|
345
|
-
messages.append(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"
|
|
354
|
-
|
|
355
|
-
"arguments": tc.function.arguments,
|
|
356
|
-
},
|
|
349
|
+
messages.append({
|
|
350
|
+
"role": "assistant",
|
|
351
|
+
"content": assistant_message.content or "",
|
|
352
|
+
"tool_calls": [
|
|
353
|
+
{
|
|
354
|
+
"id": tc.id,
|
|
355
|
+
"type": "function",
|
|
356
|
+
"function": {
|
|
357
|
+
"name": tc.function.name,
|
|
358
|
+
"arguments": tc.function.arguments
|
|
357
359
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
)
|
|
362
|
-
|
|
360
|
+
}
|
|
361
|
+
for tc in assistant_message.tool_calls
|
|
362
|
+
]
|
|
363
|
+
})
|
|
364
|
+
|
|
363
365
|
# Execute each tool call
|
|
364
366
|
for tool_call in assistant_message.tool_calls:
|
|
365
367
|
func_name = tool_call.function.name
|
|
366
368
|
func_args = json.loads(tool_call.function.arguments)
|
|
367
|
-
|
|
369
|
+
|
|
368
370
|
result = self._execute_function(func_name, func_args)
|
|
369
|
-
|
|
371
|
+
|
|
370
372
|
# Add tool result to messages
|
|
371
|
-
messages.append(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
)
|
|
373
|
+
messages.append({
|
|
374
|
+
"role": "tool",
|
|
375
|
+
"tool_call_id": tool_call.id,
|
|
376
|
+
"content": json.dumps(result, default=str)
|
|
377
|
+
})
|
|
378
378
|
else:
|
|
379
379
|
# No tool calls, we have a final response
|
|
380
380
|
final_text = assistant_message.content or ""
|
|
381
|
-
|
|
381
|
+
|
|
382
382
|
# Add to history
|
|
383
383
|
self.history.append({"role": "assistant", "content": final_text})
|
|
384
|
-
|
|
384
|
+
|
|
385
385
|
if verbose:
|
|
386
386
|
print(f"\n🌍 GeoMind: {final_text}")
|
|
387
|
-
|
|
387
|
+
|
|
388
388
|
return final_text
|
|
389
|
-
|
|
389
|
+
|
|
390
390
|
return "Max iterations reached."
|
|
391
|
-
|
|
391
|
+
|
|
392
392
|
def reset(self):
|
|
393
393
|
"""Reset the chat session."""
|
|
394
394
|
self.history = []
|
|
@@ -398,7 +398,7 @@ Always explain what you're doing and interpret results in a helpful way."""
|
|
|
398
398
|
def main(model: Optional[str] = None):
|
|
399
399
|
"""Main entry point for CLI usage."""
|
|
400
400
|
import sys
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
print("=" * 60)
|
|
403
403
|
print("🌍 GeoMind - Geospatial AI Agent")
|
|
404
404
|
print("=" * 60)
|
|
@@ -406,7 +406,7 @@ def main(model: Optional[str] = None):
|
|
|
406
406
|
print("Type 'quit' or 'exit' to end the session")
|
|
407
407
|
print("Type 'reset' to start a new conversation")
|
|
408
408
|
print("=" * 60)
|
|
409
|
-
|
|
409
|
+
|
|
410
410
|
try:
|
|
411
411
|
agent = GeoMindAgent(model=model)
|
|
412
412
|
except ValueError as e:
|
|
@@ -416,24 +416,24 @@ def main(model: Optional[str] = None):
|
|
|
416
416
|
print(f"\n❌ Error: {e}")
|
|
417
417
|
print("\nPlease check your API key and internet connection.")
|
|
418
418
|
sys.exit(1)
|
|
419
|
-
|
|
419
|
+
|
|
420
420
|
while True:
|
|
421
421
|
try:
|
|
422
422
|
user_input = input("\n💬 You: ").strip()
|
|
423
|
-
|
|
423
|
+
|
|
424
424
|
if not user_input:
|
|
425
425
|
continue
|
|
426
|
-
|
|
427
|
-
if user_input.lower() in [
|
|
426
|
+
|
|
427
|
+
if user_input.lower() in ['quit', 'exit', 'q']:
|
|
428
428
|
print("\n👋 Goodbye!")
|
|
429
429
|
break
|
|
430
|
-
|
|
431
|
-
if user_input.lower() ==
|
|
430
|
+
|
|
431
|
+
if user_input.lower() == 'reset':
|
|
432
432
|
agent.reset()
|
|
433
433
|
continue
|
|
434
|
-
|
|
434
|
+
|
|
435
435
|
agent.chat(user_input)
|
|
436
|
-
|
|
436
|
+
|
|
437
437
|
except KeyboardInterrupt:
|
|
438
438
|
print("\n\n👋 Goodbye!")
|
|
439
439
|
break
|
|
@@ -32,36 +32,34 @@ Environment Variables:
|
|
|
32
32
|
OPENROUTER_API_KEY Your OpenRouter API key
|
|
33
33
|
OPENROUTER_MODEL Model to use (default: xiaomi/mimo-v2-flash:free)
|
|
34
34
|
OPENROUTER_API_URL API endpoint (default: https://openrouter.ai/api/v1)
|
|
35
|
-
"""
|
|
35
|
+
"""
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
parser.add_argument(
|
|
39
|
-
"--query",
|
|
40
|
-
"-q",
|
|
39
|
+
"--query", "-q",
|
|
41
40
|
type=str,
|
|
42
|
-
help="Single query to run (if not provided, starts interactive mode)"
|
|
41
|
+
help="Single query to run (if not provided, starts interactive mode)"
|
|
43
42
|
)
|
|
44
43
|
parser.add_argument(
|
|
45
|
-
"--model",
|
|
46
|
-
"-m",
|
|
44
|
+
"--model", "-m",
|
|
47
45
|
type=str,
|
|
48
|
-
help="Model name to use (e.g., 'anthropic/claude-3.5-sonnet')"
|
|
46
|
+
help="Model name to use (e.g., 'anthropic/claude-3.5-sonnet')"
|
|
49
47
|
)
|
|
50
48
|
parser.add_argument(
|
|
51
|
-
"--api-key",
|
|
52
|
-
"-k",
|
|
49
|
+
"--api-key", "-k",
|
|
53
50
|
type=str,
|
|
54
|
-
help="OpenRouter API key (or set OPENROUTER_API_KEY env variable)"
|
|
51
|
+
help="OpenRouter API key (or set OPENROUTER_API_KEY env variable)"
|
|
55
52
|
)
|
|
56
53
|
parser.add_argument(
|
|
57
|
-
"--version", "-v",
|
|
54
|
+
"--version", "-v",
|
|
55
|
+
action="store_true",
|
|
56
|
+
help="Show version and exit"
|
|
58
57
|
)
|
|
59
58
|
|
|
60
59
|
args = parser.parse_args()
|
|
61
60
|
|
|
62
61
|
if args.version:
|
|
63
62
|
from . import __version__
|
|
64
|
-
|
|
65
63
|
print(f"GeoMind version {__version__}")
|
|
66
64
|
sys.exit(0)
|
|
67
65
|
|
|
@@ -104,11 +102,11 @@ def run_interactive(model: Optional[str] = None, api_key: Optional[str] = None):
|
|
|
104
102
|
if not user_input:
|
|
105
103
|
continue
|
|
106
104
|
|
|
107
|
-
if user_input.lower() in [
|
|
105
|
+
if user_input.lower() in ['quit', 'exit', 'q']:
|
|
108
106
|
print("\n👋 Goodbye!")
|
|
109
107
|
break
|
|
110
108
|
|
|
111
|
-
if user_input.lower() ==
|
|
109
|
+
if user_input.lower() == 'reset':
|
|
112
110
|
agent.reset()
|
|
113
111
|
continue
|
|
114
112
|
|