agenthink 0.1.21__py3-none-any.whl → 0.1.25__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.
- agenthink/connection.py +110 -2
- agenthink/data_source.py +35 -21
- agenthink/models.py +15 -3
- {agenthink-0.1.21.dist-info → agenthink-0.1.25.dist-info}/METADATA +1 -1
- agenthink-0.1.25.dist-info/RECORD +9 -0
- agenthink-0.1.21.dist-info/RECORD +0 -9
- {agenthink-0.1.21.dist-info → agenthink-0.1.25.dist-info}/WHEEL +0 -0
- {agenthink-0.1.21.dist-info → agenthink-0.1.25.dist-info}/top_level.txt +0 -0
agenthink/connection.py
CHANGED
|
@@ -125,6 +125,17 @@ class DBConnector:
|
|
|
125
125
|
logger.info("Added MSSQL connection for database '%s' to connection_object_dict", db_name)
|
|
126
126
|
except Exception as e:
|
|
127
127
|
logger.exception("Unable to create connection object for MSSQL secret: %s", e)
|
|
128
|
+
elif datastore_type == "postgresql":
|
|
129
|
+
try:
|
|
130
|
+
connection_object = self.__connect_postgresql(secret)
|
|
131
|
+
if connection_object:
|
|
132
|
+
db_name = secret.get("database_name", "unknown")
|
|
133
|
+
connection_dict = {"database_type": "postgresql", "connection_object": connection_object}
|
|
134
|
+
self.connection_object_dict[f"{db_name}"] = connection_dict
|
|
135
|
+
logger.info("Added PostgreSQL connection for database '%s' to connection_object_dict", db_name)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.exception("Unable to create connection object for PostgreSQL secret: %s", e)
|
|
138
|
+
|
|
128
139
|
else:
|
|
129
140
|
logger.warning("Unsupported datastore_type '%s' for key '%s'", datastore_type, cleaned_key)
|
|
130
141
|
|
|
@@ -180,6 +191,37 @@ class DBConnector:
|
|
|
180
191
|
except Exception as e:
|
|
181
192
|
logger.exception("MS SQL Connection failed: %s", e)
|
|
182
193
|
return None
|
|
194
|
+
|
|
195
|
+
def __connect_postgresql(self, secret):
|
|
196
|
+
"""
|
|
197
|
+
Establish a connection to a PostgreSQL database.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
secret : dict
|
|
202
|
+
Dictionary containing PostgreSQL connection credentials.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
psycopg2.connection or None
|
|
207
|
+
PostgreSQL connection object if successful, otherwise None.
|
|
208
|
+
"""
|
|
209
|
+
config = {
|
|
210
|
+
"host": secret.get("cluster_ip"),
|
|
211
|
+
"port": secret.get("port"),
|
|
212
|
+
"user": secret.get("username"),
|
|
213
|
+
"password": secret.get("password"),
|
|
214
|
+
"database": secret.get("database_name")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
conn = psycopg2.connect(**config)
|
|
219
|
+
logger.info("PostgreSQL connection successfully established to database '%s' on host '%s'",
|
|
220
|
+
config["database"], config["host"])
|
|
221
|
+
return conn
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.exception("PostgreSQL connection failed: %s", e)
|
|
224
|
+
return None
|
|
183
225
|
|
|
184
226
|
def __sanitize_secret_name(self, secret_name: str) -> str:
|
|
185
227
|
"""
|
|
@@ -284,11 +326,27 @@ class DBConnector:
|
|
|
284
326
|
finally:
|
|
285
327
|
if cursor:
|
|
286
328
|
cursor.close()
|
|
329
|
+
|
|
330
|
+
elif database_type == "postgresql":
|
|
331
|
+
try:
|
|
332
|
+
cursor = database_connection.cursor()
|
|
333
|
+
cursor.execute(query)
|
|
334
|
+
results = cursor.fetchall()
|
|
335
|
+
logger.info("Executed query on PostgreSQL database '%s': %s", db_name, query)
|
|
336
|
+
return results
|
|
337
|
+
except Exception as e:
|
|
338
|
+
logger.exception("Failed to execute query on PostgreSQL database '%s': %s", db_name, e)
|
|
339
|
+
return None
|
|
340
|
+
finally:
|
|
341
|
+
if cursor:
|
|
342
|
+
cursor.close()
|
|
287
343
|
|
|
288
344
|
else:
|
|
289
345
|
logger.error("Unsupported database type '%s' for database '%s'", database_type, db_name)
|
|
290
346
|
return None
|
|
291
347
|
|
|
348
|
+
|
|
349
|
+
|
|
292
350
|
def display_tables(self,db_name:str):
|
|
293
351
|
"""
|
|
294
352
|
Retrieve a list of user tables from a MySQL or MSSQL database.
|
|
@@ -334,6 +392,22 @@ class DBConnector:
|
|
|
334
392
|
logger.exception("Failed to retrieve tables from MSSQL database '%s': %s", db_name, e)
|
|
335
393
|
return None
|
|
336
394
|
|
|
395
|
+
elif database_type == "postgresql":
|
|
396
|
+
try:
|
|
397
|
+
cursor = database_connection.cursor()
|
|
398
|
+
cursor.execute("""
|
|
399
|
+
SELECT table_name
|
|
400
|
+
FROM information_schema.tables
|
|
401
|
+
WHERE table_schema = 'public'
|
|
402
|
+
AND table_type = 'BASE TABLE'
|
|
403
|
+
""")
|
|
404
|
+
tables = cursor.fetchall()
|
|
405
|
+
table_list = [table[0] for table in tables]
|
|
406
|
+
return table_list
|
|
407
|
+
except Exception as e:
|
|
408
|
+
logger.exception("Failed to retrieve tables from PostgreSQL database '%s': %s", db_name, e)
|
|
409
|
+
return None
|
|
410
|
+
|
|
337
411
|
|
|
338
412
|
def insert_data(self,db_name:str,table_name:str,data:dict):
|
|
339
413
|
"""
|
|
@@ -370,7 +444,7 @@ class DBConnector:
|
|
|
370
444
|
logger.debug("Data inserted into MySQL database '%s', table '%s'", db_name, table_name)
|
|
371
445
|
except Exception as e:
|
|
372
446
|
logger.exception("Failed to insert data into MySQL database '%s', table '%s': %s", db_name, table_name, e)
|
|
373
|
-
|
|
447
|
+
elif database_type == "mssql":
|
|
374
448
|
try:
|
|
375
449
|
cursor = database_connection.cursor()
|
|
376
450
|
columns_sql = ", ".join(f"[{c}]" for c in data.keys())
|
|
@@ -391,6 +465,27 @@ class DBConnector:
|
|
|
391
465
|
except Exception as e:
|
|
392
466
|
logger.exception("Insert failed for %s database '%s', table '%s'", database_type, db_name, table_name)
|
|
393
467
|
|
|
468
|
+
elif database_type == "postgresql":
|
|
469
|
+
try:
|
|
470
|
+
cursor = database_connection.cursor()
|
|
471
|
+
columns_sql = ", ".join(f'"{c}"' for c in data.keys())
|
|
472
|
+
placeholders = ", ".join("%s" for _ in data)
|
|
473
|
+
values = tuple(data.values())
|
|
474
|
+
|
|
475
|
+
sql = f'INSERT INTO "{table_name}" ({columns_sql}) VALUES ({placeholders})'
|
|
476
|
+
|
|
477
|
+
cursor.execute(sql, values)
|
|
478
|
+
database_connection.commit()
|
|
479
|
+
|
|
480
|
+
logger.info(
|
|
481
|
+
"Data inserted into PostgreSQL database '%s', table '%s'",
|
|
482
|
+
db_name,
|
|
483
|
+
table_name
|
|
484
|
+
)
|
|
485
|
+
cursor.close()
|
|
486
|
+
except Exception as e:
|
|
487
|
+
logger.exception("Insert failed for PostgreSQL database '%s', table '%s'", db_name, table_name)
|
|
488
|
+
|
|
394
489
|
def get_data(self, db_name: str, table_name: str, num_rows: int = 5):
|
|
395
490
|
"""
|
|
396
491
|
Retrieve a limited number of rows from a table for display.
|
|
@@ -421,6 +516,9 @@ class DBConnector:
|
|
|
421
516
|
elif db_type == "mssql":
|
|
422
517
|
sql = f"SELECT TOP ({num_rows}) * FROM [{table_name}]"
|
|
423
518
|
cursor.execute(sql)
|
|
519
|
+
elif db_type == "postgresql":
|
|
520
|
+
sql = f'SELECT * FROM "{table_name}" LIMIT %s'
|
|
521
|
+
cursor.execute(sql, (num_rows,))
|
|
424
522
|
else:
|
|
425
523
|
return None
|
|
426
524
|
|
|
@@ -474,7 +572,7 @@ class DBConnector:
|
|
|
474
572
|
"""
|
|
475
573
|
cursor.execute(sql, (table_name,))
|
|
476
574
|
result = cursor.fetchall()
|
|
477
|
-
|
|
575
|
+
elif db_type == "mssql":
|
|
478
576
|
sql = """
|
|
479
577
|
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE
|
|
480
578
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
@@ -483,6 +581,16 @@ class DBConnector:
|
|
|
483
581
|
"""
|
|
484
582
|
cursor.execute(sql, (table_name,))
|
|
485
583
|
result = cursor.fetchall()
|
|
584
|
+
elif db_type == "postgresql":
|
|
585
|
+
sql = """
|
|
586
|
+
SELECT column_name, data_type, is_nullable
|
|
587
|
+
FROM information_schema.columns
|
|
588
|
+
WHERE table_schema = 'public'
|
|
589
|
+
AND table_name = %s
|
|
590
|
+
ORDER BY ordinal_position
|
|
591
|
+
"""
|
|
592
|
+
cursor.execute(sql, (table_name,))
|
|
593
|
+
result = cursor.fetchall()
|
|
486
594
|
except Exception:
|
|
487
595
|
logger.exception(
|
|
488
596
|
"Failed to retrieve schema from %s database '%s', table '%s'",
|
agenthink/data_source.py
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
from fastapi import FastAPI
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
import mysql.connector
|
|
6
|
-
from fastapi import APIRouter
|
|
7
|
-
import mysql.connector
|
|
8
|
-
from azure.identity import ClientSecretCredential
|
|
9
|
-
from azure.keyvault.secrets import SecretClient
|
|
10
|
-
from mysql.connector import pooling
|
|
11
|
-
import json
|
|
12
|
-
from azure.storage.blob import BlobServiceClient
|
|
13
|
-
from azure.storage.blob import BlobPrefix
|
|
14
|
-
import pyodbc
|
|
15
|
-
import agenthink.utils as utils
|
|
16
|
-
import os
|
|
17
|
-
import json
|
|
18
|
-
import dotenv
|
|
19
|
-
import logging
|
|
20
|
-
import re
|
|
21
|
-
dotenv.load_dotenv()
|
|
1
|
+
import uuid
|
|
2
|
+
from agenthink.models import ConnectionRequest
|
|
22
3
|
|
|
4
|
+
def build_d365_read_call(
|
|
5
|
+
data: ConnectionRequest,
|
|
6
|
+
primary_key: str = "Entry_No",
|
|
7
|
+
filter_query: str | None = None
|
|
8
|
+
) -> dict:
|
|
9
|
+
"""
|
|
10
|
+
Builds a d365_execute tool-call response from ConnectionRequest
|
|
11
|
+
"""
|
|
23
12
|
|
|
13
|
+
if not data.d365request or not isinstance(data.d365request, list):
|
|
14
|
+
raise ValueError("d365request must be a non-empty list")
|
|
24
15
|
|
|
16
|
+
target_url = data.d365request[0].get("url")
|
|
17
|
+
if not target_url:
|
|
18
|
+
raise ValueError("Missing url in d365request")
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
"status": "tool_calls_pending",
|
|
22
|
+
"session_id": data.session_id,
|
|
23
|
+
"tools_used": ["d365_execute"],
|
|
24
|
+
"tool_call_plan": [
|
|
25
|
+
{
|
|
26
|
+
"tool_call_id": f"call_{uuid.uuid4().hex}",
|
|
27
|
+
"params": {
|
|
28
|
+
"name": "d365_execute",
|
|
29
|
+
"arguments": {
|
|
30
|
+
"operation": "read",
|
|
31
|
+
"target_url": target_url,
|
|
32
|
+
"primary_key": primary_key,
|
|
33
|
+
"filter_query": filter_query,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
agenthink/models.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
from typing import List, Optional
|
|
3
|
+
from typing import Any, Dict
|
|
3
4
|
|
|
4
5
|
# class ConnectionRequest(BaseModel):
|
|
5
6
|
# query: str
|
|
@@ -9,11 +10,22 @@ from typing import List, Optional
|
|
|
9
10
|
# datastore: Optional[List] = []
|
|
10
11
|
# tools: Optional[List] = []
|
|
11
12
|
|
|
13
|
+
# class ConnectionRequest(BaseModel):
|
|
14
|
+
# query: Optional[str] = None
|
|
15
|
+
# user_id: Optional[str] = None
|
|
16
|
+
# session_id: Optional[str] = None
|
|
17
|
+
# workflow_id: Optional[str] = None
|
|
18
|
+
# datastore: Optional[List] = None
|
|
19
|
+
# tools: Optional[List] = None
|
|
20
|
+
# d365request: Optional[dict] = None
|
|
21
|
+
|
|
22
|
+
|
|
12
23
|
class ConnectionRequest(BaseModel):
|
|
13
24
|
query: Optional[str] = None
|
|
14
25
|
user_id: Optional[str] = None
|
|
15
26
|
session_id: Optional[str] = None
|
|
16
27
|
workflow_id: Optional[str] = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
datastore_id: Optional[str] = None
|
|
29
|
+
datastore: Optional[List[Dict[str, Any]]] = None
|
|
30
|
+
tools: Optional[List[Dict[str, Any]]] = None
|
|
31
|
+
d365request: Optional[List[Dict[str, Any]]] = None
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
agenthink/__init__.py,sha256=6oUldrZgE76j8OhwsQgVY5vdaPTDFyChOljske-so8U,78
|
|
2
|
+
agenthink/connection.py,sha256=sh4sbc7y-BVyCTMMjTSMMEJYeedB6_XBOKpdBb2jKEY,25477
|
|
3
|
+
agenthink/data_source.py,sha256=hSlpKcS_I6ws9vSKeMnt7EwSHcfO-fFKF8jfXNUE7DI,1196
|
|
4
|
+
agenthink/models.py,sha256=7jm3FPQS50h0t-oCi2SjYW_SmU0nH0d7Hxvqgh15DAw,991
|
|
5
|
+
agenthink/utils.py,sha256=r5o74RbenFhQ7E3N7naoLJ-fYEe9otz0nkcvwKHDTaU,911
|
|
6
|
+
agenthink-0.1.25.dist-info/METADATA,sha256=S9uDnN2gs-F8YlCBkRk2ivQjHezPYnpnm9P0JYEkMFY,1722
|
|
7
|
+
agenthink-0.1.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
agenthink-0.1.25.dist-info/top_level.txt,sha256=rYw4Lx2uqOzbGCSoJEaikme7vS9NvgbVMc26QUIZoZM,10
|
|
9
|
+
agenthink-0.1.25.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
agenthink/__init__.py,sha256=6oUldrZgE76j8OhwsQgVY5vdaPTDFyChOljske-so8U,78
|
|
2
|
-
agenthink/connection.py,sha256=5EIZkthtAf_bCICCcEHaLRxP_tEflpJNA1hPHMGDf44,20895
|
|
3
|
-
agenthink/data_source.py,sha256=HDi-UDAphTCKiFqWEB_-MfHwXpJKqfn_i05-LdID3M0,561
|
|
4
|
-
agenthink/models.py,sha256=O5lWUNP1soAxU1X0iuE_lxYoyuXiKI0LXI6S_6zmxiE,559
|
|
5
|
-
agenthink/utils.py,sha256=r5o74RbenFhQ7E3N7naoLJ-fYEe9otz0nkcvwKHDTaU,911
|
|
6
|
-
agenthink-0.1.21.dist-info/METADATA,sha256=KLsNFvne6GMA0zrxB77X7TRgsC02Gy26HRdaGPnKa70,1722
|
|
7
|
-
agenthink-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
agenthink-0.1.21.dist-info/top_level.txt,sha256=rYw4Lx2uqOzbGCSoJEaikme7vS9NvgbVMc26QUIZoZM,10
|
|
9
|
-
agenthink-0.1.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|