snowleopard 0.1.1__tar.gz → 0.3.0__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.
- {snowleopard-0.1.1 → snowleopard-0.3.0}/LICENSE +1 -1
- {snowleopard-0.1.1 → snowleopard-0.3.0}/PKG-INFO +41 -30
- snowleopard-0.3.0/README.md +96 -0
- snowleopard-0.3.0/src/snowleopard/__init__.py +13 -0
- {snowleopard-0.1.1 → snowleopard-0.3.0}/src/snowleopard/async_client.py +14 -7
- {snowleopard-0.1.1 → snowleopard-0.3.0}/src/snowleopard/cli.py +32 -9
- {snowleopard-0.1.1 → snowleopard-0.3.0}/src/snowleopard/client.py +14 -9
- snowleopard-0.3.0/src/snowleopard/client_base.py +116 -0
- snowleopard-0.3.0/src/snowleopard/error.py +10 -0
- {snowleopard-0.1.1 → snowleopard-0.3.0}/src/snowleopard/models.py +18 -11
- snowleopard-0.1.1/README.md +0 -85
- snowleopard-0.1.1/src/snowleopard/__init__.py +0 -13
- snowleopard-0.1.1/src/snowleopard/client_base.py +0 -66
- {snowleopard-0.1.1 → snowleopard-0.3.0}/.gitignore +0 -0
- {snowleopard-0.1.1 → snowleopard-0.3.0}/CONTRIBUTING.md +0 -0
- {snowleopard-0.1.1 → snowleopard-0.3.0}/pyproject.toml +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowleopard
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: snowleopard.ai client library
|
|
5
5
|
Author-email: Snowy <hello@snowleopard.ai>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
8
|
-
Copyright (c) 2025 Snow Leopard
|
|
8
|
+
Copyright (c) 2025 Snow Leopard, Inc
|
|
9
9
|
|
|
10
10
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
11
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -34,7 +34,9 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
|
|
35
35
|
# Snow Leopard SDK for Python
|
|
36
36
|
|
|
37
|
-
Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
37
|
+
This repo contains the Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
38
|
+
|
|
39
|
+
See our [API documentation](https://docs.snowleopard.ai/#api-documentation) for more details.
|
|
38
40
|
|
|
39
41
|
## Installation
|
|
40
42
|
|
|
@@ -45,16 +47,13 @@ pip install snowleopard
|
|
|
45
47
|
## Quick Start
|
|
46
48
|
|
|
47
49
|
```python
|
|
48
|
-
from snowleopard import
|
|
50
|
+
from snowleopard import SnowLeopardClient
|
|
49
51
|
|
|
50
|
-
# Initialize the client (or
|
|
51
|
-
client =
|
|
52
|
+
# Initialize the client (or AsyncSnowLeopardClient)
|
|
53
|
+
client = SnowLeopardClient(api_key="your-api-key")
|
|
52
54
|
|
|
53
55
|
# Query your data in natural language
|
|
54
|
-
response = client.retrieve(
|
|
55
|
-
datafile_id="your-datafile-id",
|
|
56
|
-
user_query="How many users signed up last month?"
|
|
57
|
-
)
|
|
56
|
+
response = client.retrieve(user_query="How many users signed up last month?", datafile_id="your-datafile-id")
|
|
58
57
|
```
|
|
59
58
|
|
|
60
59
|
## Getting Started
|
|
@@ -69,7 +68,7 @@ response = client.retrieve(
|
|
|
69
68
|
Or pass it directly to the client:
|
|
70
69
|
|
|
71
70
|
```python
|
|
72
|
-
|
|
71
|
+
SnowLeopardClient(api_key="your-api-key")
|
|
73
72
|
```
|
|
74
73
|
|
|
75
74
|
## Usage
|
|
@@ -77,31 +76,31 @@ response = client.retrieve(
|
|
|
77
76
|
### Synchronous Client
|
|
78
77
|
|
|
79
78
|
```python
|
|
80
|
-
from snowleopard import
|
|
79
|
+
from snowleopard import SnowLeopardClient
|
|
81
80
|
|
|
82
|
-
with
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
with SnowLeopardClient() as client:
|
|
82
|
+
# Get data directly from a natural language query
|
|
83
|
+
response = client.retrieve(user_query="How many superheroes are there?")
|
|
84
|
+
print(response.data)
|
|
85
|
+
|
|
86
|
+
# Stream natural language summary of live data
|
|
87
|
+
for chunk in client.response(user_query="How many superheroes are there?"):
|
|
88
|
+
print(chunk)
|
|
90
89
|
```
|
|
91
90
|
|
|
92
91
|
### Async Client
|
|
93
92
|
|
|
94
93
|
```python
|
|
95
|
-
from snowleopard import
|
|
94
|
+
from snowleopard import AsyncSnowLeopardClient
|
|
96
95
|
|
|
97
|
-
async with
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
async with AsyncSnowLeopardClient() as client:
|
|
97
|
+
# Get complete results
|
|
98
|
+
response = await client.retrieve(user_query="How many superheroes are there?")
|
|
99
|
+
print(response.data)
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
# Get streaming results
|
|
102
|
+
async for chunk in client.response(user_query="How many superheroes are there?"):
|
|
103
|
+
print(chunk)
|
|
105
104
|
```
|
|
106
105
|
|
|
107
106
|
### CLI
|
|
@@ -110,10 +109,22 @@ The SDK includes a command-line interface:
|
|
|
110
109
|
|
|
111
110
|
```bash
|
|
112
111
|
pip install snowleopard
|
|
113
|
-
snowy retrieve <datafile-id> "How many records are there?"
|
|
114
|
-
snowy response <datafile-id> "Summarize the data"
|
|
112
|
+
snowy retrieve --datafile <datafile-id> "How many records are there?"
|
|
113
|
+
snowy response --datafile <datafile-id> "Summarize the data"
|
|
115
114
|
```
|
|
116
115
|
|
|
116
|
+
### On Premise Customers
|
|
117
|
+
|
|
118
|
+
For our customers who have a separate deployment per dataset, you should declare <url> explicitly when creating a
|
|
119
|
+
client and omit <datafile id> when querying.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
```python
|
|
123
|
+
client = SnowLeopardClient(url="https://{your-vm-ip}:{port}", api_key="your-api-key")
|
|
124
|
+
response = client.retrieve(user_query="How many users signed up last month?")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
|
|
117
128
|
## Contributing
|
|
118
129
|
|
|
119
130
|
For SDK developer docs and how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Snow Leopard SDK for Python
|
|
2
|
+
|
|
3
|
+
This repo contains the Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
4
|
+
|
|
5
|
+
See our [API documentation](https://docs.snowleopard.ai/#api-documentation) for more details.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install snowleopard
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from snowleopard import SnowLeopardClient
|
|
17
|
+
|
|
18
|
+
# Initialize the client (or AsyncSnowLeopardClient)
|
|
19
|
+
client = SnowLeopardClient(api_key="your-api-key")
|
|
20
|
+
|
|
21
|
+
# Query your data in natural language
|
|
22
|
+
response = client.retrieve(user_query="How many users signed up last month?", datafile_id="your-datafile-id")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
1. **Get your API key** from [https://auth.snowleopard.ai/account/api_keys](https://auth.snowleopard.ai/account/api_keys)
|
|
28
|
+
2. **Upload your datafiles** at [https://try.snowleopard.ai](https://try.snowleopard.ai)
|
|
29
|
+
3. **Set your API key** via environment variable:
|
|
30
|
+
```bash
|
|
31
|
+
export SNOWLEOPARD_API_KEY="your-api-key"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or pass it directly to the client:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
SnowLeopardClient(api_key="your-api-key")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Synchronous Client
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from snowleopard import SnowLeopardClient
|
|
46
|
+
|
|
47
|
+
with SnowLeopardClient() as client:
|
|
48
|
+
# Get data directly from a natural language query
|
|
49
|
+
response = client.retrieve(user_query="How many superheroes are there?")
|
|
50
|
+
print(response.data)
|
|
51
|
+
|
|
52
|
+
# Stream natural language summary of live data
|
|
53
|
+
for chunk in client.response(user_query="How many superheroes are there?"):
|
|
54
|
+
print(chunk)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Async Client
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from snowleopard import AsyncSnowLeopardClient
|
|
61
|
+
|
|
62
|
+
async with AsyncSnowLeopardClient() as client:
|
|
63
|
+
# Get complete results
|
|
64
|
+
response = await client.retrieve(user_query="How many superheroes are there?")
|
|
65
|
+
print(response.data)
|
|
66
|
+
|
|
67
|
+
# Get streaming results
|
|
68
|
+
async for chunk in client.response(user_query="How many superheroes are there?"):
|
|
69
|
+
print(chunk)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### CLI
|
|
73
|
+
|
|
74
|
+
The SDK includes a command-line interface:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install snowleopard
|
|
78
|
+
snowy retrieve --datafile <datafile-id> "How many records are there?"
|
|
79
|
+
snowy response --datafile <datafile-id> "Summarize the data"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### On Premise Customers
|
|
83
|
+
|
|
84
|
+
For our customers who have a separate deployment per dataset, you should declare <url> explicitly when creating a
|
|
85
|
+
client and omit <datafile id> when querying.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
```python
|
|
89
|
+
client = SnowLeopardClient(url="https://{your-vm-ip}:{port}", api_key="your-api-key")
|
|
90
|
+
response = client.retrieve(user_query="How many users signed up last month?")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Contributing
|
|
95
|
+
|
|
96
|
+
For SDK developer docs and how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
from snowleopard.async_client import AsyncSnowLeopardClient
|
|
6
|
+
from snowleopard.client import SnowLeopardClient
|
|
7
|
+
|
|
8
|
+
__version__ = "0.3.0"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"SnowLeopardClient",
|
|
12
|
+
"AsyncSnowLeopardClient",
|
|
13
|
+
]
|
|
@@ -7,10 +7,11 @@ from typing import AsyncGenerator, Optional, Dict, Any
|
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
from snowleopard.client_base import SLClientBase
|
|
10
|
-
from snowleopard.
|
|
10
|
+
from snowleopard.error import APIBadRequest
|
|
11
|
+
from snowleopard.models import APIError, parse, ResponseDataObjects, RetrieveResponseObjects
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class
|
|
14
|
+
class AsyncSnowLeopardClient(SLClientBase):
|
|
14
15
|
client: httpx.AsyncClient
|
|
15
16
|
|
|
16
17
|
def __init__(
|
|
@@ -26,9 +27,10 @@ class AsyncSnowLeopardPlaygroundClient(SLClientBase):
|
|
|
26
27
|
|
|
27
28
|
async def retrieve(
|
|
28
29
|
self,
|
|
29
|
-
|
|
30
|
+
*,
|
|
30
31
|
user_query: str,
|
|
31
32
|
known_data: Optional[Dict[str, Any]] = None,
|
|
33
|
+
datafile_id: Optional[str] = None,
|
|
32
34
|
) -> RetrieveResponseObjects:
|
|
33
35
|
resp = await self.client.post(
|
|
34
36
|
url=self._build_path(datafile_id, "retrieve"),
|
|
@@ -38,18 +40,23 @@ class AsyncSnowLeopardPlaygroundClient(SLClientBase):
|
|
|
38
40
|
|
|
39
41
|
async def response(
|
|
40
42
|
self,
|
|
41
|
-
|
|
42
|
-
user_query: str,
|
|
43
|
+
*,
|
|
43
44
|
known_data: Optional[Dict[str, Any]] = None,
|
|
45
|
+
user_query: str,
|
|
46
|
+
datafile_id: Optional[str] = None,
|
|
44
47
|
) -> AsyncGenerator[ResponseDataObjects, None]:
|
|
45
48
|
async with self.client.stream(
|
|
46
49
|
"POST",
|
|
47
50
|
self._build_path(datafile_id, "response"),
|
|
48
51
|
json=self._build_request_body(user_query, known_data),
|
|
49
52
|
) as resp:
|
|
50
|
-
resp.
|
|
53
|
+
if resp.status_code not in (400,):
|
|
54
|
+
resp.raise_for_status()
|
|
51
55
|
async for line in resp.aiter_lines():
|
|
52
|
-
|
|
56
|
+
resultObj = parse(json.loads(line))
|
|
57
|
+
if isinstance(resultObj, APIError) and resp.status_code == 400:
|
|
58
|
+
raise APIBadRequest(resultObj.description)
|
|
59
|
+
yield resultObj
|
|
53
60
|
|
|
54
61
|
async def __aenter__(self):
|
|
55
62
|
await self.client.__aenter__()
|
|
@@ -9,8 +9,8 @@ import argparse
|
|
|
9
9
|
from typing import List, Optional, Dict, Any
|
|
10
10
|
|
|
11
11
|
from httpx import HTTPStatusError
|
|
12
|
-
from snowleopard import __version__,
|
|
13
|
-
from snowleopard.
|
|
12
|
+
from snowleopard import __version__, SnowLeopardClient
|
|
13
|
+
from snowleopard.error import APIBadRequest
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _create_parser() -> argparse.ArgumentParser:
|
|
@@ -41,13 +41,15 @@ def _create_parser() -> argparse.ArgumentParser:
|
|
|
41
41
|
response.set_defaults(command_func=_response)
|
|
42
42
|
|
|
43
43
|
for subparser in (retrieve, response):
|
|
44
|
+
subparser.add_argument(
|
|
45
|
+
"--datafile", "-df", type=str, help="ID for playground datafile to query"
|
|
46
|
+
)
|
|
44
47
|
subparser.add_argument(
|
|
45
48
|
"--knownData",
|
|
46
49
|
"-d",
|
|
47
50
|
action="append",
|
|
48
51
|
help="Known data in key=value format (can be specified multiple times)",
|
|
49
52
|
)
|
|
50
|
-
subparser.add_argument("datafile", type=str, help="ID for Datafile to query")
|
|
51
53
|
subparser.add_argument("question", type=str, help="Natural language query")
|
|
52
54
|
|
|
53
55
|
return parser
|
|
@@ -60,7 +62,10 @@ def _parse_known_data(known_data_list: Optional[List[str]]) -> Optional[Dict[str
|
|
|
60
62
|
result = {}
|
|
61
63
|
for item in known_data_list:
|
|
62
64
|
if "=" not in item:
|
|
63
|
-
print(
|
|
65
|
+
print(
|
|
66
|
+
f"Error: Invalid knownData format '{item}'. Expected key=value",
|
|
67
|
+
file=sys.stderr,
|
|
68
|
+
)
|
|
64
69
|
sys.exit(1)
|
|
65
70
|
key, value = item.split("=", 1)
|
|
66
71
|
result[key] = value
|
|
@@ -69,7 +74,7 @@ def _parse_known_data(known_data_list: Optional[List[str]]) -> Optional[Dict[str
|
|
|
69
74
|
|
|
70
75
|
def _get_client(parsed_args):
|
|
71
76
|
try:
|
|
72
|
-
client =
|
|
77
|
+
client = SnowLeopardClient(api_key=parsed_args.apikey, loc=parsed_args.loc)
|
|
73
78
|
except Exception as e:
|
|
74
79
|
print(str(e), file=sys.stderr)
|
|
75
80
|
sys.exit(1)
|
|
@@ -77,26 +82,44 @@ def _get_client(parsed_args):
|
|
|
77
82
|
|
|
78
83
|
|
|
79
84
|
def _retrieve(parsed_args):
|
|
85
|
+
hadErrors = False
|
|
80
86
|
try:
|
|
81
87
|
known_data = _parse_known_data(parsed_args.knownData)
|
|
82
88
|
with _get_client(parsed_args) as client:
|
|
83
|
-
resp = client.retrieve(
|
|
89
|
+
resp = client.retrieve(
|
|
90
|
+
user_query=parsed_args.question,
|
|
91
|
+
known_data=known_data,
|
|
92
|
+
datafile_id=parsed_args.datafile,
|
|
93
|
+
)
|
|
84
94
|
print(json.dumps(dataclasses.asdict(resp)))
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
except APIBadRequest as e:
|
|
96
|
+
print(f"error: {str(e)}", file=sys.stderr)
|
|
97
|
+
hadErrors = True
|
|
87
98
|
except HTTPStatusError as e:
|
|
88
99
|
print(str(e), file=sys.stderr)
|
|
100
|
+
hadErrors = True
|
|
101
|
+
if hadErrors:
|
|
89
102
|
sys.exit(1)
|
|
90
103
|
|
|
91
104
|
|
|
92
105
|
def _response(parsed_args):
|
|
106
|
+
hadErrors = False
|
|
93
107
|
try:
|
|
94
108
|
known_data = _parse_known_data(parsed_args.knownData)
|
|
95
109
|
with _get_client(parsed_args) as client:
|
|
96
|
-
for chunk in client.response(
|
|
110
|
+
for chunk in client.response(
|
|
111
|
+
known_data=known_data,
|
|
112
|
+
user_query=parsed_args.question,
|
|
113
|
+
datafile_id=parsed_args.datafile,
|
|
114
|
+
):
|
|
97
115
|
print(json.dumps(dataclasses.asdict(chunk)))
|
|
116
|
+
except APIBadRequest as e:
|
|
117
|
+
print(f"error: {str(e)}", file=sys.stderr)
|
|
118
|
+
hadErrors = True
|
|
98
119
|
except HTTPStatusError as e:
|
|
99
120
|
print(str(e), file=sys.stderr)
|
|
121
|
+
hadErrors = True
|
|
122
|
+
if hadErrors:
|
|
100
123
|
sys.exit(1)
|
|
101
124
|
|
|
102
125
|
|
|
@@ -7,10 +7,11 @@ from typing import Optional, Generator, Dict, Any
|
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
from snowleopard.client_base import SLClientBase
|
|
10
|
-
from snowleopard.
|
|
10
|
+
from snowleopard.error import APIBadRequest
|
|
11
|
+
from snowleopard.models import APIError, parse, RetrieveResponseObjects, ResponseDataObjects
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class
|
|
14
|
+
class SnowLeopardClient(SLClientBase):
|
|
14
15
|
client: httpx.Client
|
|
15
16
|
|
|
16
17
|
def __init__(
|
|
@@ -26,32 +27,36 @@ class SnowLeopardPlaygroundClient(SLClientBase):
|
|
|
26
27
|
|
|
27
28
|
def retrieve(
|
|
28
29
|
self,
|
|
29
|
-
|
|
30
|
+
*,
|
|
30
31
|
user_query: str,
|
|
31
32
|
known_data: Optional[Dict[str, Any]] = None,
|
|
33
|
+
datafile_id: Optional[str] = None,
|
|
32
34
|
) -> RetrieveResponseObjects:
|
|
33
35
|
resp = self.client.post(
|
|
34
36
|
url=self._build_path(datafile_id, "retrieve"),
|
|
35
37
|
json=self._build_request_body(user_query, known_data),
|
|
36
38
|
)
|
|
37
|
-
if resp.status_code not in (200, 409):
|
|
38
|
-
resp.raise_for_status()
|
|
39
39
|
return self._parse_retrieve(resp)
|
|
40
40
|
|
|
41
41
|
def response(
|
|
42
42
|
self,
|
|
43
|
-
|
|
44
|
-
user_query: str,
|
|
43
|
+
*,
|
|
45
44
|
known_data: Optional[Dict[str, Any]] = None,
|
|
45
|
+
user_query: str,
|
|
46
|
+
datafile_id: Optional[str] = None,
|
|
46
47
|
) -> Generator[ResponseDataObjects, None, None]:
|
|
47
48
|
with self.client.stream(
|
|
48
49
|
"POST",
|
|
49
50
|
url=self._build_path(datafile_id, "response"),
|
|
50
51
|
json=self._build_request_body(user_query, known_data),
|
|
51
52
|
) as resp:
|
|
52
|
-
resp.
|
|
53
|
+
if resp.status_code not in (400,):
|
|
54
|
+
resp.raise_for_status()
|
|
53
55
|
for line in resp.iter_lines():
|
|
54
|
-
|
|
56
|
+
resultObj = parse(json.loads(line))
|
|
57
|
+
if isinstance(resultObj, APIError) and resp.status_code == 400:
|
|
58
|
+
raise APIBadRequest(resultObj.description)
|
|
59
|
+
yield resultObj
|
|
55
60
|
|
|
56
61
|
def __enter__(self):
|
|
57
62
|
self.client.__enter__()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from abc import abstractmethod
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from snowleopard.error import APIBadRequest
|
|
13
|
+
from snowleopard.models import APIError, parse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SLConfig:
|
|
18
|
+
api_key: str
|
|
19
|
+
timeout: httpx.Timeout
|
|
20
|
+
loc: str
|
|
21
|
+
|
|
22
|
+
def headers(self):
|
|
23
|
+
headers = {}
|
|
24
|
+
if self.api_key:
|
|
25
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
26
|
+
return headers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SLClientBase:
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def retrieve(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
user_query: str,
|
|
35
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
36
|
+
datafile_id: Optional[str] = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
The primary for developers building AI agents that needs to retrieve data from a database directly.
|
|
40
|
+
|
|
41
|
+
Takes a natural language question (usually from the user or the agent) and returns the data required to answer
|
|
42
|
+
the query in an LLM-friendly object.
|
|
43
|
+
|
|
44
|
+
:param datafile_id: (optional) The playground datafile-id if hitting a Snow Leopard api directly
|
|
45
|
+
:param user_query: Natural language query to execute against the Playground datafile
|
|
46
|
+
:param known_data: Additional context about the user_query
|
|
47
|
+
"""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def response(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
55
|
+
user_query: str,
|
|
56
|
+
datafile_id: Optional[str] = None,
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Takes a natural language question (usually from the user or the agent) and returns the data required to answer
|
|
60
|
+
the query as well as a LLM summary of the returned data.
|
|
61
|
+
|
|
62
|
+
:param datafile_id: (optional) The playground datafile-id if hitting a Snow Leopard api directly
|
|
63
|
+
:param user_query: Natural language query to execute against the Playground datafile
|
|
64
|
+
:param known_data: (optional) Additional context about the user_query
|
|
65
|
+
"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _config(
|
|
70
|
+
api_key: Optional[str], timeout: Optional[httpx.Timeout], loc: Optional[str]
|
|
71
|
+
) -> SLConfig:
|
|
72
|
+
api_key = api_key or os.environ.get("SNOWLEOPARD_API_KEY")
|
|
73
|
+
if api_key is None:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
'Missing required argument "api_key" and environment variable "SNOWLEOPARD_API_KEY" not set'
|
|
76
|
+
)
|
|
77
|
+
timeout = timeout or httpx.Timeout(
|
|
78
|
+
connect=5.0, read=600.0, write=10.0, pool=5.0
|
|
79
|
+
)
|
|
80
|
+
loc = loc or os.environ.get("SNOWLEOPARD_LOC", "https://api.snowleopard.ai")
|
|
81
|
+
if not loc:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
'Missing required argument "loc" and environment variable "SNOWLEOPARD_LOC" not set'
|
|
84
|
+
)
|
|
85
|
+
return SLConfig(api_key, timeout, loc)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _build_path(datafile_id: str, endpoint: str) -> str:
|
|
89
|
+
if datafile_id is None:
|
|
90
|
+
return endpoint
|
|
91
|
+
else:
|
|
92
|
+
return f"datafiles/{datafile_id}/{endpoint}"
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _build_request_body(
|
|
96
|
+
user_query: str, known_data: Optional[Dict[str, Any]] = None
|
|
97
|
+
) -> Dict[str, Any]:
|
|
98
|
+
if not isinstance(user_query, str) or not user_query.strip():
|
|
99
|
+
raise APIBadRequest('userQuery field must not be empty/whitespace')
|
|
100
|
+
body = {"userQuery": user_query}
|
|
101
|
+
if known_data is not None:
|
|
102
|
+
body["knownData"] = known_data
|
|
103
|
+
return body
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _parse_retrieve(resp):
|
|
107
|
+
if resp.status_code not in (400, 409):
|
|
108
|
+
resp.raise_for_status()
|
|
109
|
+
try:
|
|
110
|
+
resultObj = parse(resp.json())
|
|
111
|
+
except Exception:
|
|
112
|
+
resp.raise_for_status()
|
|
113
|
+
raise
|
|
114
|
+
if isinstance(resultObj, APIError) and resp.status_code == 400:
|
|
115
|
+
raise APIBadRequest(resultObj.description)
|
|
116
|
+
return resultObj
|
|
@@ -11,25 +11,26 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
11
11
|
|
|
12
12
|
class StrEnum(str, Enum):
|
|
13
13
|
"""String enum compatible with Python 3.8"""
|
|
14
|
+
|
|
14
15
|
pass
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@dataclass
|
|
18
|
-
class
|
|
19
|
-
objType = "
|
|
19
|
+
class APIError:
|
|
20
|
+
objType = "apiError"
|
|
20
21
|
|
|
21
22
|
callId: str
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
responseStatus: str
|
|
24
|
+
description: str
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
27
|
-
class
|
|
28
|
-
objType = "
|
|
28
|
+
class RetrieveResponse:
|
|
29
|
+
objType = "retrieveResponse"
|
|
29
30
|
|
|
30
31
|
callId: str
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
data: List[Union[SchemaData, ErrorSchemaData]]
|
|
33
|
+
responseStatus: ResponseStatus
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@dataclass
|
|
@@ -96,19 +97,23 @@ class ResponseLLMResult:
|
|
|
96
97
|
|
|
97
98
|
class ResponseStatus(StrEnum):
|
|
98
99
|
SUCCESS = "SUCCESS"
|
|
100
|
+
BAD_REQUEST = "BAD_REQUEST"
|
|
99
101
|
NOT_FOUND_IN_SCHEMA = "NOT_FOUND_IN_SCHEMA"
|
|
100
102
|
UNKNOWN = "UNKNOWN"
|
|
101
103
|
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
|
|
102
104
|
AUTHORIZATION_FAILED = "AUTHORIZATION_FAILED"
|
|
103
105
|
LLM_ERROR = "LLM_ERROR"
|
|
104
106
|
LLM_TOKEN_LIMIT_REACHED = "LLM_TOKEN_LIMIT_REACHED"
|
|
107
|
+
DB_ERROR = "DB_ERROR"
|
|
108
|
+
DB_CONNECTION_ERROR = "DB_CONNECTION_ERROR"
|
|
109
|
+
DB_SYNTAX_ERROR = "DB_SYNTAX_ERROR"
|
|
105
110
|
|
|
106
111
|
|
|
107
112
|
_PARSE_OBJS = {
|
|
108
113
|
o.objType: o
|
|
109
114
|
for o in (
|
|
115
|
+
APIError,
|
|
110
116
|
RetrieveResponse,
|
|
111
|
-
RetrieveResponseError,
|
|
112
117
|
SchemaData,
|
|
113
118
|
ErrorSchemaData,
|
|
114
119
|
ResponseStart,
|
|
@@ -118,10 +123,12 @@ _PARSE_OBJS = {
|
|
|
118
123
|
)
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
|
|
126
|
+
|
|
127
|
+
RetrieveResponseObjects = Union[APIError, RetrieveResponse]
|
|
128
|
+
|
|
122
129
|
|
|
123
130
|
ResponseDataObjects = Union[
|
|
124
|
-
|
|
131
|
+
APIError,
|
|
125
132
|
ResponseStart,
|
|
126
133
|
ResponseData,
|
|
127
134
|
EarlyTermination,
|
snowleopard-0.1.1/README.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Snow Leopard SDK for Python
|
|
2
|
-
|
|
3
|
-
Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pip install snowleopard
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```python
|
|
14
|
-
from snowleopard import SnowLeopardPlaygroundClient
|
|
15
|
-
|
|
16
|
-
# Initialize the client (or AsyncSnowLeopardPlaygroundClient)
|
|
17
|
-
client = SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
18
|
-
|
|
19
|
-
# Query your data in natural language
|
|
20
|
-
response = client.retrieve(
|
|
21
|
-
datafile_id="your-datafile-id",
|
|
22
|
-
user_query="How many users signed up last month?"
|
|
23
|
-
)
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Getting Started
|
|
27
|
-
|
|
28
|
-
1. **Get your API key** from [https://auth.snowleopard.ai/account/api_keys](https://auth.snowleopard.ai/account/api_keys)
|
|
29
|
-
2. **Upload your datafiles** at [https://try.snowleopard.ai](https://try.snowleopard.ai)
|
|
30
|
-
3. **Set your API key** via environment variable:
|
|
31
|
-
```bash
|
|
32
|
-
export SNOWLEOPARD_API_KEY="your-api-key"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Or pass it directly to the client:
|
|
36
|
-
|
|
37
|
-
```python
|
|
38
|
-
SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Usage
|
|
42
|
-
|
|
43
|
-
### Synchronous Client
|
|
44
|
-
|
|
45
|
-
```python
|
|
46
|
-
from snowleopard import SnowLeopardPlaygroundClient
|
|
47
|
-
|
|
48
|
-
with SnowLeopardPlaygroundClient() as client:
|
|
49
|
-
# Get data directly from a natural language query
|
|
50
|
-
response = client.retrieve("datafile-id", "What's the total revenue?")
|
|
51
|
-
print(response.data)
|
|
52
|
-
|
|
53
|
-
# Stream natural language summary of live data
|
|
54
|
-
for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
55
|
-
print(chunk)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Async Client
|
|
59
|
-
|
|
60
|
-
```python
|
|
61
|
-
from snowleopard import AsyncSnowLeopardPlaygroundClient
|
|
62
|
-
|
|
63
|
-
async with AsyncSnowLeopardPlaygroundClient() as client:
|
|
64
|
-
# Get complete results
|
|
65
|
-
response = await client.retrieve("datafile-id", "What's the total revenue?")
|
|
66
|
-
print(response.data)
|
|
67
|
-
|
|
68
|
-
# Get streaming results
|
|
69
|
-
async for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
70
|
-
print(chunk)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### CLI
|
|
74
|
-
|
|
75
|
-
The SDK includes a command-line interface:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
pip install snowleopard
|
|
79
|
-
snowy retrieve <datafile-id> "How many records are there?"
|
|
80
|
-
snowy response <datafile-id> "Summarize the data"
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Contributing
|
|
84
|
-
|
|
85
|
-
For SDK developer docs and how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# copyright 2025 Snow Leopard, Inc
|
|
3
|
-
# released under the MIT license - see LICENSE file
|
|
4
|
-
|
|
5
|
-
from snowleopard.async_client import AsyncSnowLeopardPlaygroundClient
|
|
6
|
-
from snowleopard.client import SnowLeopardPlaygroundClient
|
|
7
|
-
|
|
8
|
-
__version__ = "0.1.1"
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"SnowLeopardPlaygroundClient",
|
|
12
|
-
"AsyncSnowLeopardPlaygroundClient",
|
|
13
|
-
]
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# copyright 2025 Snow Leopard, Inc
|
|
3
|
-
# released under the MIT license - see LICENSE file
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from typing import Optional, Dict, Any
|
|
8
|
-
|
|
9
|
-
import httpx
|
|
10
|
-
|
|
11
|
-
from snowleopard.models import parse
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass
|
|
15
|
-
class SLConfig:
|
|
16
|
-
api_key: str
|
|
17
|
-
timeout: httpx.Timeout
|
|
18
|
-
loc: str
|
|
19
|
-
|
|
20
|
-
def headers(self):
|
|
21
|
-
headers = {}
|
|
22
|
-
if self.api_key:
|
|
23
|
-
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
24
|
-
return headers
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class SLClientBase:
|
|
28
|
-
@staticmethod
|
|
29
|
-
def _config(
|
|
30
|
-
api_key: Optional[str], timeout: Optional[httpx.Timeout], loc: Optional[str]
|
|
31
|
-
) -> SLConfig:
|
|
32
|
-
api_key = api_key or os.environ.get("SNOWLEOPARD_API_KEY")
|
|
33
|
-
if api_key is None:
|
|
34
|
-
raise ValueError(
|
|
35
|
-
'Missing required argument "api_key" and environment variable "SNOWLEOPARD_API_KEY" not set'
|
|
36
|
-
)
|
|
37
|
-
timeout = timeout or httpx.Timeout(
|
|
38
|
-
connect=5.0, read=600.0, write=10.0, pool=5.0
|
|
39
|
-
)
|
|
40
|
-
loc = loc or os.environ.get("SNOWLEOPARD_LOC", "https://api.snowleopard.ai")
|
|
41
|
-
if not loc:
|
|
42
|
-
raise ValueError(
|
|
43
|
-
'Missing required argument "loc" and environment variable "SNOWLEOPARD_LOC" not set'
|
|
44
|
-
)
|
|
45
|
-
return SLConfig(api_key, timeout, loc)
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def _build_path(datafile_id: str, endpoint: str) -> str:
|
|
49
|
-
return f"datafiles/{datafile_id}/{endpoint}"
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def _build_request_body(
|
|
53
|
-
user_query: str, known_data: Optional[Dict[str, Any]] = None
|
|
54
|
-
) -> Dict[str, Any]:
|
|
55
|
-
body = {"userQuery": user_query}
|
|
56
|
-
if known_data is not None:
|
|
57
|
-
body["knownData"] = known_data
|
|
58
|
-
return body
|
|
59
|
-
|
|
60
|
-
@staticmethod
|
|
61
|
-
def _parse_retrieve(resp):
|
|
62
|
-
try:
|
|
63
|
-
return parse(resp.json())
|
|
64
|
-
except Exception:
|
|
65
|
-
resp.raise_for_status()
|
|
66
|
-
raise
|
|
File without changes
|
|
File without changes
|
|
File without changes
|