aisberg 0.1.0__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.
- aisberg/__init__.py +7 -0
- aisberg/abstract/__init__.py +0 -0
- aisberg/abstract/modules.py +57 -0
- aisberg/api/__init__.py +0 -0
- aisberg/api/async_endpoints.py +333 -0
- aisberg/api/endpoints.py +328 -0
- aisberg/async_client.py +107 -0
- aisberg/client.py +108 -0
- aisberg/config.py +17 -0
- aisberg/exceptions.py +22 -0
- aisberg/models/__init__.py +0 -0
- aisberg/models/chat.py +143 -0
- aisberg/models/collections.py +36 -0
- aisberg/models/embeddings.py +92 -0
- aisberg/models/models.py +39 -0
- aisberg/models/requests.py +11 -0
- aisberg/models/token.py +11 -0
- aisberg/models/tools.py +73 -0
- aisberg/models/workflows.py +66 -0
- aisberg/modules/__init__.py +23 -0
- aisberg/modules/chat.py +403 -0
- aisberg/modules/collections.py +117 -0
- aisberg/modules/document.py +117 -0
- aisberg/modules/embeddings.py +309 -0
- aisberg/modules/me.py +77 -0
- aisberg/modules/models.py +108 -0
- aisberg/modules/tools.py +78 -0
- aisberg/modules/workflows.py +140 -0
- aisberg/requests/__init__.py +0 -0
- aisberg/requests/async_requests.py +85 -0
- aisberg/requests/sync_requests.py +85 -0
- aisberg/utils.py +111 -0
- aisberg-0.1.0.dist-info/METADATA +212 -0
- aisberg-0.1.0.dist-info/RECORD +43 -0
- aisberg-0.1.0.dist-info/WHEEL +5 -0
- aisberg-0.1.0.dist-info/licenses/LICENSE +9 -0
- aisberg-0.1.0.dist-info/top_level.txt +3 -0
- tests/integration/test_collections_integration.py +115 -0
- tests/unit/test_collections_sync.py +104 -0
- tmp/test.py +33 -0
- tmp/test_async.py +126 -0
- tmp/test_doc_parse.py +12 -0
- tmp/test_sync.py +146 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
from typing import List, Union, Generator, AsyncGenerator, Coroutine, Any
|
2
|
+
from abc import ABC
|
3
|
+
|
4
|
+
from ..models.workflows import (
|
5
|
+
Workflow,
|
6
|
+
WorkflowDetails,
|
7
|
+
WorkflowRunChunk,
|
8
|
+
WorkflowRunResult,
|
9
|
+
)
|
10
|
+
|
11
|
+
from abc import abstractmethod
|
12
|
+
from ..abstract.modules import SyncModule, AsyncModule
|
13
|
+
from ..api import endpoints, async_endpoints
|
14
|
+
|
15
|
+
|
16
|
+
class AbstractWorkflowsModule(ABC):
|
17
|
+
def __init__(self, parent, client):
|
18
|
+
self._parent = parent
|
19
|
+
self._client = client
|
20
|
+
|
21
|
+
@abstractmethod
|
22
|
+
def get(self) -> List[Workflow]:
|
23
|
+
"""Get a list of available workflows.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
List[dict]: A list of available workflows.
|
27
|
+
|
28
|
+
Raises:
|
29
|
+
Exception: If there is an error fetching the workflows.
|
30
|
+
"""
|
31
|
+
pass
|
32
|
+
|
33
|
+
@abstractmethod
|
34
|
+
def details(self, workflow_id: str) -> WorkflowDetails:
|
35
|
+
"""Get details of a specific workflow.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
workflow_id (str): The ID of the workflow to retrieve.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
WorkflowDetails: The details of the specified workflow.
|
42
|
+
|
43
|
+
Raises:
|
44
|
+
ValueError: If the workflow with the specified ID is not found.
|
45
|
+
Exception: If there is an error fetching the workflow details.
|
46
|
+
"""
|
47
|
+
pass
|
48
|
+
|
49
|
+
@abstractmethod
|
50
|
+
def run(self, workflow_id: str, data: dict = None, stream: bool = False):
|
51
|
+
"""
|
52
|
+
Run a workflow with the given ID and data.
|
53
|
+
If `stream` is True, it will yield chunks of the workflow run.
|
54
|
+
If `stream` is False, it will return the final result of the workflow run.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
workflow_id (str): The ID of the workflow to run.
|
58
|
+
data (dict, optional): The input data for the workflow. Defaults to None.
|
59
|
+
stream (bool, optional): If True, returns a generator yielding chunks of the workflow run. Defaults to False.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Union[Generator[WorkflowRunChunk, None, None], WorkflowRunResult]:
|
63
|
+
If `stream` is True, a generator yielding chunks of the workflow run.
|
64
|
+
If `stream` is False, the final result of the workflow run.
|
65
|
+
|
66
|
+
Raises:
|
67
|
+
ValueError: If the workflow with the specified ID is not found.
|
68
|
+
Exception: If there is an error running the workflow.
|
69
|
+
"""
|
70
|
+
pass
|
71
|
+
|
72
|
+
|
73
|
+
class SyncWorkflowsModule(SyncModule, AbstractWorkflowsModule):
|
74
|
+
def __init__(self, parent, client):
|
75
|
+
SyncModule.__init__(self, parent, client)
|
76
|
+
AbstractWorkflowsModule.__init__(self, parent, client)
|
77
|
+
|
78
|
+
def get(self) -> List[Workflow]:
|
79
|
+
return endpoints.workflows(self._client)
|
80
|
+
|
81
|
+
def details(self, workflow_id: str) -> WorkflowDetails:
|
82
|
+
return endpoints.workflow(self._client, workflow_id)
|
83
|
+
|
84
|
+
def run(
|
85
|
+
self, workflow_id: str, data: dict = None, stream: bool = False
|
86
|
+
) -> Union[Generator[WorkflowRunChunk, None, None], WorkflowRunResult]:
|
87
|
+
if stream:
|
88
|
+
return self._run_stream(workflow_id, data)
|
89
|
+
else:
|
90
|
+
return self._run_once(workflow_id, data)
|
91
|
+
|
92
|
+
def _run_stream(
|
93
|
+
self, workflow_id: str, data: dict = None
|
94
|
+
) -> Generator[WorkflowRunChunk, None, None]:
|
95
|
+
for chunk in endpoints.run_workflow(self._client, workflow_id, data):
|
96
|
+
yield WorkflowRunChunk.model_validate(chunk)
|
97
|
+
|
98
|
+
def _run_once(self, workflow_id: str, data: dict = None) -> WorkflowRunResult:
|
99
|
+
last_response = None
|
100
|
+
for chunk in endpoints.run_workflow(self._client, workflow_id, data):
|
101
|
+
last_response = chunk
|
102
|
+
return WorkflowRunResult.model_validate(last_response)
|
103
|
+
|
104
|
+
|
105
|
+
class AsyncWorkflowsModule(AsyncModule, AbstractWorkflowsModule):
|
106
|
+
def __init__(self, parent, client):
|
107
|
+
AsyncModule.__init__(self, parent, client)
|
108
|
+
AbstractWorkflowsModule.__init__(self, parent, client)
|
109
|
+
|
110
|
+
async def get(self) -> List[Workflow]:
|
111
|
+
return await async_endpoints.workflows(self._client)
|
112
|
+
|
113
|
+
async def details(self, workflow_id: str) -> WorkflowDetails:
|
114
|
+
return await async_endpoints.workflow(self._client, workflow_id)
|
115
|
+
|
116
|
+
def run(
|
117
|
+
self, workflow_id: str, data: dict = None, stream: bool = False
|
118
|
+
) -> Union[
|
119
|
+
AsyncGenerator[WorkflowRunChunk, None], Coroutine[Any, Any, WorkflowRunResult]
|
120
|
+
]:
|
121
|
+
if stream:
|
122
|
+
return self._run_stream(workflow_id, data)
|
123
|
+
else:
|
124
|
+
return self._run_once(workflow_id, data)
|
125
|
+
|
126
|
+
async def _run_stream(
|
127
|
+
self, workflow_id: str, data: dict = None
|
128
|
+
) -> AsyncGenerator[WorkflowRunChunk, None]:
|
129
|
+
async for chunk in async_endpoints.run_workflow(
|
130
|
+
self._client, workflow_id, data
|
131
|
+
):
|
132
|
+
yield WorkflowRunChunk.model_validate(chunk)
|
133
|
+
|
134
|
+
async def _run_once(self, workflow_id: str, data: dict = None) -> WorkflowRunResult:
|
135
|
+
last_response = None
|
136
|
+
async for chunk in async_endpoints.run_workflow(
|
137
|
+
self._client, workflow_id, data
|
138
|
+
):
|
139
|
+
last_response = chunk
|
140
|
+
return WorkflowRunResult.model_validate(last_response)
|
File without changes
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from typing import Type, TypeVar, Any, Dict, Optional, Callable
|
2
|
+
import httpx
|
3
|
+
from pydantic import ValidationError, BaseModel
|
4
|
+
|
5
|
+
T = TypeVar("T", bound=BaseModel)
|
6
|
+
|
7
|
+
|
8
|
+
async def areq(
|
9
|
+
client: httpx.AsyncClient,
|
10
|
+
method: str,
|
11
|
+
url: str,
|
12
|
+
response_model: Type[T],
|
13
|
+
*,
|
14
|
+
expected_status: int = 200,
|
15
|
+
handle_404_none: bool = False,
|
16
|
+
**kwargs,
|
17
|
+
) -> Optional[T]:
|
18
|
+
"""
|
19
|
+
Send an HTTP request asynchronously, parse the response.
|
20
|
+
Return None if 404 and handle_404_none=True, else raise.
|
21
|
+
"""
|
22
|
+
try:
|
23
|
+
resp = await client.request(method, url, **kwargs)
|
24
|
+
if handle_404_none and resp.status_code == 404:
|
25
|
+
return None
|
26
|
+
|
27
|
+
if 400 <= resp.status_code < 500:
|
28
|
+
resp.read()
|
29
|
+
if resp.text == "Please specify a group":
|
30
|
+
raise httpx.HTTPStatusError(
|
31
|
+
"Bad Request: Please specify a group. Use `AisbergClient.me.groups()` to know which groups you are in.",
|
32
|
+
request=resp.request,
|
33
|
+
response=resp,
|
34
|
+
)
|
35
|
+
raise httpx.HTTPStatusError(
|
36
|
+
f"Bad Request: {resp.text}", request=resp.request, response=resp
|
37
|
+
)
|
38
|
+
|
39
|
+
resp.raise_for_status()
|
40
|
+
return response_model.model_validate(resp.json())
|
41
|
+
except httpx.HTTPStatusError as e:
|
42
|
+
if handle_404_none and e.response.status_code == 404:
|
43
|
+
return None
|
44
|
+
raise
|
45
|
+
except ValidationError as ve:
|
46
|
+
raise RuntimeError(f"Invalid response: {ve}") from ve
|
47
|
+
|
48
|
+
|
49
|
+
async def areq_stream(
|
50
|
+
client: httpx.AsyncClient,
|
51
|
+
method: str,
|
52
|
+
url: str,
|
53
|
+
parse_line: Callable[[str], Any],
|
54
|
+
*,
|
55
|
+
handle_status: Optional[Dict[int, Any]] = None,
|
56
|
+
**kwargs,
|
57
|
+
) -> Any:
|
58
|
+
"""
|
59
|
+
Wrapper to handle HTTP streams.
|
60
|
+
- parse_line: Function to parse each line of the stream.
|
61
|
+
"""
|
62
|
+
async with client.stream(method, url, **kwargs) as resp:
|
63
|
+
if handle_status and resp.status_code in handle_status:
|
64
|
+
yield handle_status[resp.status_code]
|
65
|
+
return
|
66
|
+
|
67
|
+
if 400 <= resp.status_code < 500:
|
68
|
+
resp.read()
|
69
|
+
if resp.text == "Please specify a group":
|
70
|
+
raise httpx.HTTPStatusError(
|
71
|
+
"Bad Request: Please specify a group. Use `await AisbergAsyncClient.me.groups()` to know which groups you are in.",
|
72
|
+
request=resp.request,
|
73
|
+
response=resp,
|
74
|
+
)
|
75
|
+
raise httpx.HTTPStatusError(
|
76
|
+
f"Bad Request: {resp.text}", request=resp.request, response=resp
|
77
|
+
)
|
78
|
+
|
79
|
+
resp.raise_for_status()
|
80
|
+
async for raw_line in resp.aiter_lines():
|
81
|
+
if not raw_line:
|
82
|
+
continue
|
83
|
+
line = raw_line.decode() if isinstance(raw_line, bytes) else raw_line
|
84
|
+
for parsed in parse_line(line):
|
85
|
+
yield parsed
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from typing import Type, TypeVar, Any, Dict, Optional, Generator, Callable
|
2
|
+
import httpx
|
3
|
+
from pydantic import BaseModel, ValidationError
|
4
|
+
|
5
|
+
T = TypeVar("T", bound=BaseModel)
|
6
|
+
|
7
|
+
|
8
|
+
def req(
|
9
|
+
client: httpx.Client,
|
10
|
+
method: str,
|
11
|
+
url: str,
|
12
|
+
response_model: Type[T],
|
13
|
+
*,
|
14
|
+
expected_status: int = 200,
|
15
|
+
handle_404_none: bool = False,
|
16
|
+
**kwargs,
|
17
|
+
) -> Optional[T]:
|
18
|
+
"""
|
19
|
+
Send an HTTP request synchronously, parse the response.
|
20
|
+
Return None if 404 and handle_404_none=True, else raise.
|
21
|
+
"""
|
22
|
+
try:
|
23
|
+
resp = client.request(method, url, **kwargs)
|
24
|
+
if handle_404_none and resp.status_code == 404:
|
25
|
+
return None
|
26
|
+
|
27
|
+
if 400 <= resp.status_code < 500:
|
28
|
+
resp.read()
|
29
|
+
if resp.text == "Please specify a group":
|
30
|
+
raise httpx.HTTPStatusError(
|
31
|
+
"Bad Request: Please specify a group. Use `AisbergClient.me.groups()` to know which groups you are in.",
|
32
|
+
request=resp.request,
|
33
|
+
response=resp,
|
34
|
+
)
|
35
|
+
raise httpx.HTTPStatusError(
|
36
|
+
f"Bad Request: {resp.text}", request=resp.request, response=resp
|
37
|
+
)
|
38
|
+
|
39
|
+
resp.raise_for_status()
|
40
|
+
return response_model.model_validate(resp.json())
|
41
|
+
except httpx.HTTPStatusError as e:
|
42
|
+
if handle_404_none and e.response.status_code == 404:
|
43
|
+
return None
|
44
|
+
raise
|
45
|
+
except ValidationError as ve:
|
46
|
+
raise RuntimeError(f"Invalid response: {ve}") from ve
|
47
|
+
|
48
|
+
|
49
|
+
def req_stream(
|
50
|
+
client: httpx.Client,
|
51
|
+
method: str,
|
52
|
+
url: str,
|
53
|
+
parse_line: Callable[[str], Any],
|
54
|
+
*,
|
55
|
+
handle_status: Optional[Dict[int, Any]] = None,
|
56
|
+
**kwargs,
|
57
|
+
) -> Generator[Any, None, None]:
|
58
|
+
"""
|
59
|
+
Wrapper to handle HTTP streams.
|
60
|
+
- parse_line: Function to parse each line of the stream.
|
61
|
+
"""
|
62
|
+
with client.stream(method, url, **kwargs) as resp:
|
63
|
+
if handle_status and resp.status_code in handle_status:
|
64
|
+
yield handle_status[resp.status_code]
|
65
|
+
return
|
66
|
+
|
67
|
+
if 400 <= resp.status_code < 500:
|
68
|
+
resp.read()
|
69
|
+
if resp.text == "Please specify a group":
|
70
|
+
raise httpx.HTTPStatusError(
|
71
|
+
"Bad Request: Please specify a group. Use `await AisbergAsyncClient.me.groups()` to know which groups you are in.",
|
72
|
+
request=resp.request,
|
73
|
+
response=resp,
|
74
|
+
)
|
75
|
+
raise httpx.HTTPStatusError(
|
76
|
+
f"Bad Request: {resp.text}", request=resp.request, response=resp
|
77
|
+
)
|
78
|
+
|
79
|
+
resp.raise_for_status()
|
80
|
+
for raw_line in resp.iter_lines():
|
81
|
+
if not raw_line:
|
82
|
+
continue
|
83
|
+
line = raw_line.decode() if isinstance(raw_line, bytes) else raw_line
|
84
|
+
for parsed in parse_line(line):
|
85
|
+
yield parsed
|
aisberg/utils.py
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
from typing import Iterator, Union, Dict
|
2
|
+
import json
|
3
|
+
|
4
|
+
|
5
|
+
def parse_chat_line(
|
6
|
+
line: str, *, full_chunk: bool = True
|
7
|
+
) -> Iterator[Union[str, dict]]:
|
8
|
+
"""
|
9
|
+
Parse une ligne de stream JSON (commençant par `data:`) et yield un ou plusieurs éléments utilisables.
|
10
|
+
|
11
|
+
Args:
|
12
|
+
line (str): Ligne du flux à traiter.
|
13
|
+
full_chunk (bool): Contrôle le format de sortie (chunk brut ou contenu transformé).
|
14
|
+
|
15
|
+
Yields:
|
16
|
+
Union[str, dict]: Le chunk complet ou un morceau de texte/fonction/tool_call.
|
17
|
+
"""
|
18
|
+
if not line.startswith("data:"):
|
19
|
+
return
|
20
|
+
|
21
|
+
data = line[len("data:") :].strip()
|
22
|
+
if data == "[DONE]":
|
23
|
+
return
|
24
|
+
|
25
|
+
try:
|
26
|
+
chunk: dict = json.loads(data)
|
27
|
+
|
28
|
+
if chunk["object"] != "chat.completion.chunk":
|
29
|
+
raise ValueError(f"Unexpected object type: {chunk['object']}")
|
30
|
+
|
31
|
+
if chunk["choices"][0]["finish_reason"] == "stop":
|
32
|
+
return
|
33
|
+
|
34
|
+
if full_chunk:
|
35
|
+
yield chunk
|
36
|
+
return
|
37
|
+
|
38
|
+
delta = chunk.get("choices", [{}])[0].get("delta", {})
|
39
|
+
|
40
|
+
if "content" in delta and delta["content"]:
|
41
|
+
yield delta["content"]
|
42
|
+
|
43
|
+
elif "function_call" in delta and delta["function_call"]:
|
44
|
+
yield f"[FUNCTION_CALL]{delta['function_call']}"
|
45
|
+
|
46
|
+
elif "tool_calls" in delta and delta["tool_calls"]:
|
47
|
+
yield f"[TOOL_CALLS]{delta['tool_calls']}"
|
48
|
+
|
49
|
+
except json.JSONDecodeError:
|
50
|
+
return
|
51
|
+
|
52
|
+
|
53
|
+
class WorkflowLineParser:
|
54
|
+
def __init__(self, full_chunk: bool = True):
|
55
|
+
self.full_chunk = full_chunk
|
56
|
+
self._buckets: Dict[str, Dict] = {}
|
57
|
+
|
58
|
+
def __call__(self, line: str) -> Iterator[Union[str, dict]]:
|
59
|
+
if not line.startswith("data:"):
|
60
|
+
return
|
61
|
+
data = line[len("data:") :].strip()
|
62
|
+
if data == "[DONE]":
|
63
|
+
return
|
64
|
+
|
65
|
+
try:
|
66
|
+
payload = json.loads(data)
|
67
|
+
|
68
|
+
# Gestion des events chunk + slices
|
69
|
+
if payload.get("type") == "chunk":
|
70
|
+
self._buckets[payload["id"]] = {
|
71
|
+
"totalSlices": payload["totalSlices"],
|
72
|
+
"slices": [None] * payload["totalSlices"],
|
73
|
+
}
|
74
|
+
|
75
|
+
elif payload.get("type") == "chunk_slice":
|
76
|
+
bucket = self._buckets.get(payload["id"])
|
77
|
+
if bucket:
|
78
|
+
bucket["slices"][payload["index"]] = payload["slice"]
|
79
|
+
|
80
|
+
elif payload.get("type") == "chunk_end":
|
81
|
+
bucket = self._buckets.pop(payload["id"], None)
|
82
|
+
if bucket:
|
83
|
+
full_json = "".join(bucket["slices"])
|
84
|
+
try:
|
85
|
+
yield from self._yield_chunk(full_json)
|
86
|
+
except json.JSONDecodeError:
|
87
|
+
return
|
88
|
+
|
89
|
+
else:
|
90
|
+
# Message "normal" non splitté
|
91
|
+
yield from self._yield_chunk(data)
|
92
|
+
|
93
|
+
except json.JSONDecodeError:
|
94
|
+
return
|
95
|
+
|
96
|
+
def _yield_chunk(self, raw: str) -> Iterator[Union[str, dict]]:
|
97
|
+
chunk = json.loads(raw)
|
98
|
+
|
99
|
+
if "slice" in chunk:
|
100
|
+
try:
|
101
|
+
if isinstance(chunk["slice"], str):
|
102
|
+
# Si la slice est déjà un JSON string, on la parse
|
103
|
+
parsed_slice = json.loads(chunk["slice"])
|
104
|
+
yield parsed_slice
|
105
|
+
elif isinstance(chunk["slice"], dict):
|
106
|
+
# Si la slice est déjà un dict, on la yield directement
|
107
|
+
yield chunk["slice"]
|
108
|
+
except json.JSONDecodeError:
|
109
|
+
return
|
110
|
+
else:
|
111
|
+
return
|
@@ -0,0 +1,212 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: aisberg
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: Aisberg SDK for Python - A simple and powerful SDK to interact with the Aisberg API
|
5
|
+
Author: Free Pro
|
6
|
+
Author-email: Mathis Lambert <mathis.lambert@freepro.com>
|
7
|
+
License: Private License
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Classifier: License :: Other/Proprietary License
|
13
|
+
Requires-Python: >=3.10
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE
|
16
|
+
Requires-Dist: httpx>=0.28.1
|
17
|
+
Requires-Dist: pydantic>=2.11.7
|
18
|
+
Requires-Dist: pydantic-settings>=2.10.1
|
19
|
+
Provides-Extra: dev
|
20
|
+
Requires-Dist: pytest>=8.4.1; extra == "dev"
|
21
|
+
Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"
|
22
|
+
Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
|
23
|
+
Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
|
24
|
+
Requires-Dist: black>=25.1.0; extra == "dev"
|
25
|
+
Requires-Dist: isort>=6.0.1; extra == "dev"
|
26
|
+
Requires-Dist: mypy>=1.16.1; extra == "dev"
|
27
|
+
Requires-Dist: flake8>=7.3.0; extra == "dev"
|
28
|
+
Dynamic: license-file
|
29
|
+
|
30
|
+
# Aisberg Python SDK
|
31
|
+
|
32
|
+

|
33
|
+

|
34
|
+
|
35
|
+
Aisberg SDK for Python is a robust and easy-to-use library for interacting with the Aisberg API.
|
36
|
+
It provides **synchronous** and **asynchronous** clients, advanced module abstractions, and built-in support for
|
37
|
+
conversational LLM workflows, collections, embeddings, and more.
|
38
|
+
|
39
|
+
---
|
40
|
+
|
41
|
+
## Features
|
42
|
+
|
43
|
+
- **Sync & Async clients**: Use with regular scripts or async frameworks
|
44
|
+
- **Auto tool execution** for LLM flows (tool calls, results integration)
|
45
|
+
- **Modular architecture**: Collections, chat, models, workflows, embeddings, and more
|
46
|
+
- **Strong typing** via Pydantic models
|
47
|
+
- **Environment-based configuration** (supports `.env` files and system environment variables)
|
48
|
+
- **Context manager support** for easy resource management
|
49
|
+
- **Custom tool registration**: Easily extend LLM capabilities with your own functions
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
## Installation
|
54
|
+
|
55
|
+
```sh
|
56
|
+
pip install aisberg
|
57
|
+
````
|
58
|
+
|
59
|
+
Or, for local development:
|
60
|
+
|
61
|
+
```sh
|
62
|
+
git clone https://your.git.repo/aisberg.git
|
63
|
+
cd aisberg
|
64
|
+
pip install -e .
|
65
|
+
```
|
66
|
+
|
67
|
+
---
|
68
|
+
|
69
|
+
## Quickstart
|
70
|
+
|
71
|
+
### 1. **Configure your API key and base URL**
|
72
|
+
|
73
|
+
You can set them as environment variables, or in a `.env` file:
|
74
|
+
|
75
|
+
```env
|
76
|
+
AISBERG_API_KEY=...
|
77
|
+
AISBERG_BASE_URL=https://url
|
78
|
+
```
|
79
|
+
|
80
|
+
### 2. **Synchronous Usage**
|
81
|
+
|
82
|
+
```python
|
83
|
+
from aisberg import AisbergClient
|
84
|
+
|
85
|
+
with AisbergClient() as client:
|
86
|
+
me = client.me.info()
|
87
|
+
print(me)
|
88
|
+
|
89
|
+
chat_response = client.chat.complete(
|
90
|
+
input="Bonjour, qui es-tu ?",
|
91
|
+
model="llm-aisberg",
|
92
|
+
)
|
93
|
+
print(chat_response.choices[0].message.content)
|
94
|
+
```
|
95
|
+
|
96
|
+
### 3. **Asynchronous Usage**
|
97
|
+
|
98
|
+
```python
|
99
|
+
import asyncio
|
100
|
+
from aisberg import AisbergAsyncClient
|
101
|
+
|
102
|
+
|
103
|
+
async def main():
|
104
|
+
async with AisbergAsyncClient() as client:
|
105
|
+
await client.initialize()
|
106
|
+
me = await client.me.info()
|
107
|
+
print(me)
|
108
|
+
|
109
|
+
chat_response = await client.chat.complete(
|
110
|
+
input="Hello, who are you?",
|
111
|
+
model="llm-aisberg",
|
112
|
+
)
|
113
|
+
print(chat_response.choices[0].message.content)
|
114
|
+
|
115
|
+
|
116
|
+
asyncio.run(main())
|
117
|
+
```
|
118
|
+
|
119
|
+
---
|
120
|
+
|
121
|
+
## Modules
|
122
|
+
|
123
|
+
* `client.me` — User/account info
|
124
|
+
* `client.chat` — Conversational LLM completions and streaming
|
125
|
+
* `client.collections` — Manage data collections
|
126
|
+
* `client.embeddings` — Encode texts to embeddings
|
127
|
+
* `client.models` — Model discovery & info
|
128
|
+
* `client.workflows` — Workflow management & execution
|
129
|
+
* `client.tools` — Register and execute tools for LLM tool calls
|
130
|
+
|
131
|
+
Each module is available both in the sync and async clients with similar APIs.
|
132
|
+
|
133
|
+
---
|
134
|
+
|
135
|
+
## Tool Calls and Automatic Execution
|
136
|
+
|
137
|
+
The SDK supports **tool-augmented LLM workflows**.
|
138
|
+
Register your own functions for use in chat:
|
139
|
+
|
140
|
+
```python
|
141
|
+
def my_tool(args):
|
142
|
+
# Custom business logic
|
143
|
+
return {"result": "tool output"}
|
144
|
+
|
145
|
+
|
146
|
+
client.tools.register("my_tool", my_tool)
|
147
|
+
response = client.chat.complete(
|
148
|
+
input="Use the tool please.",
|
149
|
+
model="llm-aisberg",
|
150
|
+
tools=[{"name": "my_tool", ...}],
|
151
|
+
auto_execute_tools=True,
|
152
|
+
)
|
153
|
+
```
|
154
|
+
|
155
|
+
---
|
156
|
+
|
157
|
+
## Advanced Usage
|
158
|
+
|
159
|
+
### **Custom Configuration**
|
160
|
+
|
161
|
+
You can override configuration when instantiating the client:
|
162
|
+
|
163
|
+
```python
|
164
|
+
client = AisbergClient(
|
165
|
+
api_key="...",
|
166
|
+
base_url="https://url",
|
167
|
+
timeout=60,
|
168
|
+
)
|
169
|
+
```
|
170
|
+
|
171
|
+
### **Environment Variables Supported**
|
172
|
+
|
173
|
+
* `AISBERG_API_KEY`
|
174
|
+
* `AISBERG_BASE_URL`
|
175
|
+
* `AISBERG_TIMEOUT` (optional)
|
176
|
+
|
177
|
+
### **Using in a Context Manager**
|
178
|
+
|
179
|
+
Both clients are context manager compatible:
|
180
|
+
|
181
|
+
```python
|
182
|
+
with AisbergClient() as client:
|
183
|
+
# Sync usage
|
184
|
+
...
|
185
|
+
|
186
|
+
async with AisbergAsyncClient() as client:
|
187
|
+
# Async usage
|
188
|
+
...
|
189
|
+
```
|
190
|
+
|
191
|
+
---
|
192
|
+
|
193
|
+
## License
|
194
|
+
|
195
|
+
**Private License — Not for public distribution or resale.**
|
196
|
+
|
197
|
+
For enterprise/commercial use, please contact [Mathis Lambert](mailto:mathis.lambert@freepro.com) or Free Pro.
|
198
|
+
|
199
|
+
---
|
200
|
+
|
201
|
+
## Authors
|
202
|
+
|
203
|
+
* Mathis Lambert
|
204
|
+
* Free Pro
|
205
|
+
|
206
|
+
---
|
207
|
+
|
208
|
+
## Support
|
209
|
+
|
210
|
+
For support, bug reports, or feature requests, please contact your technical representative.
|
211
|
+
|
212
|
+
---
|
@@ -0,0 +1,43 @@
|
|
1
|
+
aisberg/__init__.py,sha256=jMX3F2Fh5JobAkRMKGAcOpmNdoxSthgR4v_oM-Yw1l0,141
|
2
|
+
aisberg/async_client.py,sha256=wxwkRUjxFbBlcj3D0aFspCVX5aACCksVhG9ZPVYrtx4,3547
|
3
|
+
aisberg/client.py,sha256=-UgM9pi-R_qeUmods8sWTxBVGM13pNUfLJv8Zq1vQto,3586
|
4
|
+
aisberg/config.py,sha256=tYuaE8WQFyyGhXEgF3kKLX7wM4PDIv6vDoezXaryOhk,441
|
5
|
+
aisberg/exceptions.py,sha256=T1ioitOczdkmW3-8XfPG0ta_O2WqzS1iQtzhDBucb5Y,515
|
6
|
+
aisberg/utils.py,sha256=4wkrpC5gz8PJWw3kZqaRbd3KekrI37zlDC__JiAVitw,3552
|
7
|
+
aisberg/abstract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
aisberg/abstract/modules.py,sha256=aRmB21oiL9NCZsHUx8zB0Q8VvZAZBucsZxYv5l7JygY,1698
|
9
|
+
aisberg/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
aisberg/api/async_endpoints.py,sha256=cYOmUGBnMrvj8zaiek_Cjut6tB3paEIlJRHjRbggQBc,8279
|
11
|
+
aisberg/api/endpoints.py,sha256=Ni-boFbHpB4ZiCbBwLCzoy8bxRTie-LHmBddqbwVn7Q,8003
|
12
|
+
aisberg/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
aisberg/models/chat.py,sha256=LcvOgQm9Aan7pS4S90h4lrdzENX-NebA9pAKYdcDLB8,3667
|
14
|
+
aisberg/models/collections.py,sha256=gtcpyMfUVkfZkWP82AlsfNDT0mM_wREdjnR32i3DP20,806
|
15
|
+
aisberg/models/embeddings.py,sha256=gBGXR3DgyBCWB9Vky-iowWh_2Md7wa57I3fWxIrl77I,2409
|
16
|
+
aisberg/models/models.py,sha256=NcECFpeVjt9nzQEN8lggDy-SesDSDFgSAYhqWz0dupA,1206
|
17
|
+
aisberg/models/requests.py,sha256=9QXt9COo3GfubLQR5AgajxaDEr-ZOHrdjp-j9nyZrlw,205
|
18
|
+
aisberg/models/token.py,sha256=p3-uxCZMHbe4yryQdH21iDnPh-etJWgvUDUpCXV2xhk,190
|
19
|
+
aisberg/models/tools.py,sha256=dO8-O-tV9usakviEvWzT7Z-dG5s7EkVV8cg5CyoxZHg,1830
|
20
|
+
aisberg/models/workflows.py,sha256=CiqJdHsSFrOo2DAYNvYFvFOmO0hyg0xUehktkn3maSg,1258
|
21
|
+
aisberg/modules/__init__.py,sha256=i499QPWq-JB596dRVuBFNleYoldJshYXGmC4oed0f58,725
|
22
|
+
aisberg/modules/chat.py,sha256=WrMvq0O5gdBggoGm_dJcJywsHJvlKKYBUQ5HgPipQuU,13822
|
23
|
+
aisberg/modules/collections.py,sha256=kgV7zFHme6d2SXPKITJknxnk1yc6SoWz2LGnGoifon0,3947
|
24
|
+
aisberg/modules/document.py,sha256=twloRFq1wRS4j6fzth77IdceAaSrdDULzu3hlTiIsrA,3902
|
25
|
+
aisberg/modules/embeddings.py,sha256=wq7DlOXVQBP9-V5f4ytY4nnD60DAJ49v6JFlBuJwmEs,10470
|
26
|
+
aisberg/modules/me.py,sha256=yN4-ZYVDLViwz1oXbDpF1_1gULWiBf5GL0wj9-Pp-H4,2145
|
27
|
+
aisberg/modules/models.py,sha256=EIyWOkO2FaOYV-fNB-42-AEY2bob6yA1_4bqlwKs6JY,3048
|
28
|
+
aisberg/modules/tools.py,sha256=TnTIfjQuPhPImtWTjEy3UCmXeroPrYX3BRzTvT9nIkk,2424
|
29
|
+
aisberg/modules/workflows.py,sha256=TGDriJYy__dRBwP-MK-8zl6mlzZs22-0_TUqYYTeVfs,5004
|
30
|
+
aisberg/requests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
+
aisberg/requests/async_requests.py,sha256=4J6Gj6vY5AD9lXhSTdYdAKQIqsqH9m1uaKc7XLDKWx8,2849
|
32
|
+
aisberg/requests/sync_requests.py,sha256=ov7GDgx4C-oQ9xEZ7VQsGVY1fnuqRpPwjS0H_yH1yb8,2839
|
33
|
+
aisberg-0.1.0.dist-info/licenses/LICENSE,sha256=dCGgiGa4f14s5mzTYnj1wIZNqGRVG2bUYyyk7nzKlEo,406
|
34
|
+
tests/integration/test_collections_integration.py,sha256=HSs-Mhh5JNx2V6EeKo--G3xZq7pMk3z3gLReX7FJidg,3883
|
35
|
+
tests/unit/test_collections_sync.py,sha256=DyizG4MIYNtBOC9TlsFdLlH5vz7USA12K9Uydlcydoo,3199
|
36
|
+
tmp/test.py,sha256=8H3GxLztspwyt3_wLPtQkwdiLl--qqAn8ZaEUsXbwDE,743
|
37
|
+
tmp/test_async.py,sha256=BaY3MkOIDXsersjYgnfaI43kZ42KDj5Andq5u-yO82s,4168
|
38
|
+
tmp/test_doc_parse.py,sha256=b41Tsa1VkgNwyKwKoDRLNd05sIFGVaJncJGlFGiwYWY,324
|
39
|
+
tmp/test_sync.py,sha256=87QSz728gvjTXz2JykjF0U_mNsunDYrWsXYDjkBybI8,4929
|
40
|
+
aisberg-0.1.0.dist-info/METADATA,sha256=ETmG5wRMX4W_Xr_L8ZyAXyS2BgtQcOLiIIE03vYEatA,5053
|
41
|
+
aisberg-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
42
|
+
aisberg-0.1.0.dist-info/top_level.txt,sha256=7_CasmLkyF_h1CVRncNuAjam-DMIeMu1Rev_LpVcWoA,18
|
43
|
+
aisberg-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright © 2025 Free Pro. All rights reserved.
|
2
|
+
|
3
|
+
This software and its source code are the exclusive property of Free Pro and are intended for internal use only.
|
4
|
+
|
5
|
+
Unauthorized copying, distribution, modification, or use of this software, in whole or in part, is strictly prohibited without prior written permission from Free Pro.
|
6
|
+
|
7
|
+
For questions or requests, contact:
|
8
|
+
Free Pro
|
9
|
+
[mathis.lambert@freepro.com]
|