weco 0.1.1__py3-none-any.whl → 0.1.4__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.
- weco/__init__.py +2 -6
- weco/client.py +442 -49
- weco/constants.py +4 -0
- weco/functional.py +94 -7
- weco/utils.py +180 -0
- weco-0.1.4.dist-info/METADATA +121 -0
- weco-0.1.4.dist-info/RECORD +10 -0
- {weco-0.1.1.dist-info → weco-0.1.4.dist-info}/WHEEL +1 -1
- weco-0.1.1.dist-info/METADATA +0 -69
- weco-0.1.1.dist-info/RECORD +0 -8
- {weco-0.1.1.dist-info → weco-0.1.4.dist-info}/LICENSE +0 -0
- {weco-0.1.1.dist-info → weco-0.1.4.dist-info}/top_level.txt +0 -0
weco/__init__.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
from .client import WecoAI
|
|
2
|
-
from .functional import build, query
|
|
2
|
+
from .functional import abuild, aquery, batch_query, build, query
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
5
|
-
"WecoAI",
|
|
6
|
-
"build",
|
|
7
|
-
"query",
|
|
8
|
-
]
|
|
4
|
+
__all__ = ["WecoAI", "build", "abuild", "query", "aquery", "batch_query"]
|
weco/client.py
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
1
3
|
import os
|
|
2
|
-
|
|
4
|
+
import warnings
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from typing import Any, Callable, Coroutine, Dict, List, Optional, Tuple, Union
|
|
3
7
|
|
|
8
|
+
import httpx
|
|
4
9
|
import requests
|
|
10
|
+
from httpx import HTTPStatusError
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
from .constants import MAX_IMAGE_SIZE_MB, MAX_IMAGE_UPLOADS, MAX_TEXT_LENGTH, SUPPORTED_IMAGE_EXTENSIONS
|
|
14
|
+
from .utils import (
|
|
15
|
+
generate_random_base16_code,
|
|
16
|
+
get_image_size,
|
|
17
|
+
is_base64_image,
|
|
18
|
+
is_local_image,
|
|
19
|
+
is_public_url_image,
|
|
20
|
+
preprocess_image,
|
|
21
|
+
)
|
|
5
22
|
|
|
6
23
|
|
|
7
24
|
class WecoAI:
|
|
8
|
-
|
|
25
|
+
"""A client for the WecoAI function builder API that allows users to build and query specialized functions built by LLMs.
|
|
26
|
+
The user must simply provide a task description to build a function, and then query the function with an input to get the result they need.
|
|
27
|
+
Our client supports both synchronous and asynchronous request paradigms and uses HTTP/2 for faster communication with the API.
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
api_key : str
|
|
32
|
+
The API key used for authentication.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, api_key: str = None, timeout: float = 120.0, http2: bool = True) -> None:
|
|
9
36
|
"""Initializes the WecoAI client with the provided API key and base URL.
|
|
10
37
|
|
|
11
38
|
Parameters
|
|
12
39
|
----------
|
|
13
|
-
api_key : str
|
|
40
|
+
api_key : str, optional
|
|
14
41
|
The API key used for authentication. If not provided, the client will attempt to read it from the environment variable - WECO_API_KEY.
|
|
15
|
-
|
|
16
|
-
|
|
42
|
+
|
|
43
|
+
timeout : float, optional
|
|
44
|
+
The timeout for the HTTP requests in seconds (default is 30.0).
|
|
45
|
+
|
|
46
|
+
http2 : bool, optional
|
|
47
|
+
Whether to use HTTP/2 protocol for the HTTP requests (default is True).
|
|
17
48
|
|
|
18
49
|
Raises
|
|
19
50
|
------
|
|
@@ -25,29 +56,29 @@ class WecoAI:
|
|
|
25
56
|
try:
|
|
26
57
|
api_key = os.environ["WECO_API_KEY"]
|
|
27
58
|
except KeyError:
|
|
28
|
-
raise ValueError(
|
|
29
|
-
"WECO_API_KEY must be passed to client or set as an environment variable"
|
|
30
|
-
)
|
|
59
|
+
raise ValueError("WECO_API_KEY must be passed to client or set as an environment variable")
|
|
31
60
|
self.api_key = api_key
|
|
61
|
+
self.http2 = http2
|
|
62
|
+
self.timeout = timeout
|
|
63
|
+
self.base_url = "https://function.api.weco.ai"
|
|
64
|
+
# Setup clients
|
|
65
|
+
self.client = httpx.Client(http2=http2, timeout=timeout)
|
|
66
|
+
self.async_client = httpx.AsyncClient(http2=http2, timeout=timeout)
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
def close(self):
|
|
69
|
+
"""Close both synchronous and asynchronous clients."""
|
|
70
|
+
self.client.close()
|
|
71
|
+
asyncio.run(self.async_client.aclose())
|
|
35
72
|
|
|
36
73
|
def _headers(self) -> Dict[str, str]:
|
|
37
|
-
"""Constructs the headers for the API requests.
|
|
38
|
-
|
|
39
|
-
Returns
|
|
40
|
-
-------
|
|
41
|
-
dict
|
|
42
|
-
A dictionary containing the headers.
|
|
43
|
-
"""
|
|
74
|
+
"""Constructs the headers for the API requests."""
|
|
44
75
|
return {
|
|
45
76
|
"Authorization": f"Bearer {self.api_key}",
|
|
46
77
|
"Content-Type": "application/json",
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
def
|
|
50
|
-
"""
|
|
80
|
+
def _make_request(self, endpoint: str, data: Dict[str, Any], is_async: bool = False) -> Callable:
|
|
81
|
+
"""Creates a callable for making either synchronous or asynchronous requests.
|
|
51
82
|
|
|
52
83
|
Parameters
|
|
53
84
|
----------
|
|
@@ -55,49 +86,359 @@ class WecoAI:
|
|
|
55
86
|
The API endpoint to which the request will be made.
|
|
56
87
|
data : dict
|
|
57
88
|
The data to be sent in the request body.
|
|
89
|
+
is_async : bool, optional
|
|
90
|
+
Whether to create an asynchronous request (default is False).
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
Callable
|
|
95
|
+
A callable that performs the HTTP request.
|
|
96
|
+
"""
|
|
97
|
+
url = f"{self.base_url}/{endpoint}"
|
|
98
|
+
headers = self._headers()
|
|
99
|
+
|
|
100
|
+
if is_async:
|
|
101
|
+
|
|
102
|
+
async def _request():
|
|
103
|
+
try:
|
|
104
|
+
response = await self.async_client.post(url, json=data, headers=headers)
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
return response.json()
|
|
107
|
+
except HTTPStatusError as e:
|
|
108
|
+
# Handle HTTP errors (4xx and 5xx status codes)
|
|
109
|
+
error_message = f"HTTP error occurred: {e.response.status_code} - {e.response.text}"
|
|
110
|
+
raise ValueError(error_message) from e
|
|
111
|
+
except Exception as e:
|
|
112
|
+
# Handle other exceptions
|
|
113
|
+
raise ValueError(f"An error occurred: {str(e)}") from e
|
|
114
|
+
|
|
115
|
+
return _request()
|
|
116
|
+
else:
|
|
117
|
+
|
|
118
|
+
def _request():
|
|
119
|
+
try:
|
|
120
|
+
response = self.client.post(url, json=data, headers=headers)
|
|
121
|
+
response.raise_for_status()
|
|
122
|
+
return response.json()
|
|
123
|
+
except HTTPStatusError as e:
|
|
124
|
+
# Handle HTTP errors (4xx and 5xx status codes)
|
|
125
|
+
error_message = f"HTTP error occurred: {e.response.status_code} - {e.response.text}"
|
|
126
|
+
raise ValueError(error_message) from e
|
|
127
|
+
except Exception as e:
|
|
128
|
+
# Handle other exceptions
|
|
129
|
+
raise ValueError(f"An error occurred: {str(e)}") from e
|
|
130
|
+
|
|
131
|
+
return _request()
|
|
132
|
+
|
|
133
|
+
def _process_query_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
|
|
134
|
+
"""Processes the query response and handles warnings.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
response : dict
|
|
139
|
+
The raw API response.
|
|
58
140
|
|
|
59
141
|
Returns
|
|
60
142
|
-------
|
|
61
143
|
dict
|
|
62
|
-
|
|
144
|
+
A processed dictionary containing the output, token counts, and latency.
|
|
63
145
|
|
|
64
146
|
Raises
|
|
65
147
|
------
|
|
66
|
-
|
|
67
|
-
If the
|
|
148
|
+
UserWarning
|
|
149
|
+
If there are any warnings in the API response.
|
|
68
150
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
response.raise_for_status()
|
|
72
|
-
return response.json()
|
|
151
|
+
for _warning in response.get("warnings", []):
|
|
152
|
+
warnings.warn(_warning)
|
|
73
153
|
|
|
74
|
-
|
|
75
|
-
|
|
154
|
+
return {
|
|
155
|
+
"output": response["response"],
|
|
156
|
+
"in_tokens": response["num_input_tokens"],
|
|
157
|
+
"out_tokens": response["num_output_tokens"],
|
|
158
|
+
"latency_ms": response["latency_ms"],
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def _build(self, task_description: str, is_async: bool) -> Union[Tuple[str, str], Coroutine[Any, Any, Tuple[str, str]]]:
|
|
162
|
+
"""Internal method to handle both synchronous and asynchronous build requests.
|
|
76
163
|
|
|
77
164
|
Parameters
|
|
78
165
|
----------
|
|
79
166
|
task_description : str
|
|
80
167
|
A description of the task for which the function is being built.
|
|
168
|
+
is_async : bool
|
|
169
|
+
Whether to perform an asynchronous request.
|
|
81
170
|
|
|
82
171
|
Returns
|
|
83
172
|
-------
|
|
84
|
-
tuple[str, str]
|
|
85
|
-
A tuple containing the name and description of the function.
|
|
173
|
+
Union[tuple[str, str], Coroutine[Any, Any, tuple[str, str]]]
|
|
174
|
+
A tuple containing the name and description of the function, or a coroutine that returns such a tuple.
|
|
175
|
+
|
|
176
|
+
Raises
|
|
177
|
+
------
|
|
178
|
+
ValueError
|
|
179
|
+
If the task description is empty or exceeds the maximum length.
|
|
86
180
|
"""
|
|
181
|
+
# Validate the input
|
|
182
|
+
if len(task_description) == 0:
|
|
183
|
+
raise ValueError("Task description must be provided.")
|
|
184
|
+
if len(task_description) > MAX_TEXT_LENGTH:
|
|
185
|
+
raise ValueError(f"Task description must be less than {MAX_TEXT_LENGTH} characters.")
|
|
186
|
+
|
|
87
187
|
endpoint = "build"
|
|
88
188
|
data = {"request": task_description}
|
|
89
|
-
|
|
90
|
-
|
|
189
|
+
request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
|
|
190
|
+
|
|
191
|
+
if is_async:
|
|
91
192
|
|
|
92
|
-
|
|
93
|
-
|
|
193
|
+
async def _async_build():
|
|
194
|
+
response = await request
|
|
195
|
+
return response["name"], response["description"]
|
|
196
|
+
|
|
197
|
+
return _async_build()
|
|
198
|
+
else:
|
|
199
|
+
response = request # the request has already been made and the response is available
|
|
200
|
+
return response["name"], response["description"]
|
|
201
|
+
|
|
202
|
+
async def abuild(self, task_description: str) -> Tuple[str, str]:
|
|
203
|
+
"""Asynchronously builds a specialized function given a task description.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
task_description : str
|
|
208
|
+
A description of the task for which the function is being built.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
tuple[str, str]
|
|
213
|
+
A tuple containing the name and description of the function.
|
|
214
|
+
"""
|
|
215
|
+
return await self._build(task_description=task_description, is_async=True)
|
|
216
|
+
|
|
217
|
+
def build(self, task_description: str) -> Tuple[str, str]:
|
|
218
|
+
"""Synchronously builds a specialized function given a task description.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
task_description : str
|
|
223
|
+
A description of the task for which the function is being built.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
tuple[str, str]
|
|
228
|
+
A tuple containing the name and description of the function.
|
|
229
|
+
"""
|
|
230
|
+
return self._build(task_description=task_description, is_async=False)
|
|
231
|
+
|
|
232
|
+
def _upload_image(self, fn_name: str, upload_id: str, image_info: Dict[str, Any]) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Uploads an image to an S3 bucket and returns the URL of the uploaded image.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
fn_name : str
|
|
239
|
+
The name of the function for which the image is being uploaded.
|
|
240
|
+
upload_id: str
|
|
241
|
+
A unique identifier for the image upload.
|
|
242
|
+
image_info : Dict[str, Any]
|
|
243
|
+
A dictionary containing the image metadata.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
str
|
|
248
|
+
The URL of the uploaded image.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
if image_info["source"] == "base64":
|
|
252
|
+
_, base64_info = is_base64_image(maybe_base64=image_info["image"])
|
|
253
|
+
img_data = base64.b64decode(base64_info["encoding"])
|
|
254
|
+
elif image_info["source"] == "url":
|
|
255
|
+
response = requests.get(image_info["image"])
|
|
256
|
+
response.raise_for_status()
|
|
257
|
+
img_data = response.content
|
|
258
|
+
elif image_info["source"] == "local":
|
|
259
|
+
with open(image_info["image"], "rb") as f:
|
|
260
|
+
img_data = f.read()
|
|
261
|
+
else:
|
|
262
|
+
raise ValueError("Invalid image input")
|
|
263
|
+
|
|
264
|
+
# Preprocess the image
|
|
265
|
+
img = Image.open(BytesIO(img_data))
|
|
266
|
+
file_type = image_info["file_type"]
|
|
267
|
+
processed_img, file_type = preprocess_image(image=img, file_type=file_type)
|
|
268
|
+
upload_data = BytesIO()
|
|
269
|
+
processed_img.save(upload_data, format=file_type)
|
|
270
|
+
upload_data = upload_data.getvalue()
|
|
271
|
+
|
|
272
|
+
# Request a presigned URL from the server
|
|
273
|
+
endpoint = "upload_link"
|
|
274
|
+
request_data = {"fn_name": fn_name, "upload_id": upload_id, "file_type": file_type}
|
|
275
|
+
# This needs to be a synchronous request since we need the presigned URL to upload the image
|
|
276
|
+
response = self._make_request(endpoint=endpoint, data=request_data, is_async=False)
|
|
277
|
+
|
|
278
|
+
# Upload the image to the S3 bucket
|
|
279
|
+
image_name = generate_random_base16_code()
|
|
280
|
+
files = {"file": (f"{image_name}.{file_type}", upload_data)}
|
|
281
|
+
http_response = requests.post(response["url"], data=response["fields"], files=files)
|
|
282
|
+
if http_response.status_code == 204:
|
|
283
|
+
pass
|
|
284
|
+
else:
|
|
285
|
+
raise ValueError("Image upload failed")
|
|
286
|
+
|
|
287
|
+
# Return the URL of the uploaded image
|
|
288
|
+
upload_link = f"{response['url']}{response['fields']['key']}"
|
|
289
|
+
return upload_link
|
|
290
|
+
|
|
291
|
+
def _validate_query(self, text_input: str, images_input: List[str]) -> List[Dict[str, Any]]:
|
|
292
|
+
"""
|
|
293
|
+
Validate the input for the query method.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
text_input : str
|
|
298
|
+
The text input to the function.
|
|
299
|
+
images_input : List[str]
|
|
300
|
+
A list of image URLs or images encoded in base64 with their metadata to be sent as input to the function.
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
List[Dict[str, Any]]
|
|
305
|
+
A list of dictionaries containing the image metadata.
|
|
306
|
+
|
|
307
|
+
Raises
|
|
308
|
+
------
|
|
309
|
+
ValueError
|
|
310
|
+
If the input is invalid.
|
|
311
|
+
"""
|
|
312
|
+
if not isinstance(text_input, str) or not isinstance(images_input, list):
|
|
313
|
+
raise ValueError("Text input must be a string and images input must be a list of strings.")
|
|
314
|
+
for image in images_input:
|
|
315
|
+
if not isinstance(image, str):
|
|
316
|
+
raise ValueError("Images input must be a list of strings.")
|
|
317
|
+
|
|
318
|
+
# Assert that either text or images or both must be provded
|
|
319
|
+
if len(text_input) == 0 and len(images_input) == 0:
|
|
320
|
+
raise ValueError("Either text or images or both must be provided as input.")
|
|
321
|
+
|
|
322
|
+
# Check if the text input is within the limit
|
|
323
|
+
if len(text_input) > MAX_TEXT_LENGTH:
|
|
324
|
+
raise ValueError(f"Text input must be less than {MAX_TEXT_LENGTH} characters.")
|
|
325
|
+
|
|
326
|
+
# Check if the images input is within the limit
|
|
327
|
+
if len(images_input) > MAX_IMAGE_UPLOADS:
|
|
328
|
+
raise ValueError(f"Number of images must be less than {MAX_IMAGE_UPLOADS}.")
|
|
329
|
+
|
|
330
|
+
# Check if input is an valid image
|
|
331
|
+
image_info = []
|
|
332
|
+
for image in images_input:
|
|
333
|
+
is_base64, base64_info = is_base64_image(maybe_base64=image)
|
|
334
|
+
if is_base64:
|
|
335
|
+
file_type = base64_info["media_type"].split("/")[1]
|
|
336
|
+
|
|
337
|
+
is_public_url = is_public_url_image(maybe_url_image=image)
|
|
338
|
+
if is_public_url:
|
|
339
|
+
response = requests.get(image)
|
|
340
|
+
response.raise_for_status()
|
|
341
|
+
file_type = response.headers["content-type"].split("/")[1]
|
|
342
|
+
|
|
343
|
+
is_local = is_local_image(maybe_local_image=image)
|
|
344
|
+
if is_local:
|
|
345
|
+
file_type = os.path.splitext(image)[1][1:]
|
|
346
|
+
|
|
347
|
+
if not (is_base64 or is_public_url or is_local):
|
|
348
|
+
raise ValueError("Images must be local paths, public URLs or base64 encoded strings.")
|
|
349
|
+
|
|
350
|
+
# Determine the source of image
|
|
351
|
+
if is_base64:
|
|
352
|
+
source = "base64"
|
|
353
|
+
elif is_public_url:
|
|
354
|
+
source = "url"
|
|
355
|
+
elif is_local:
|
|
356
|
+
source = "local"
|
|
357
|
+
|
|
358
|
+
# Check if the image type is supported
|
|
359
|
+
file_type = file_type.lower()
|
|
360
|
+
if file_type not in SUPPORTED_IMAGE_EXTENSIONS:
|
|
361
|
+
raise ValueError(
|
|
362
|
+
f"Image file type {file_type} is not supported. Supported types are {SUPPORTED_IMAGE_EXTENSIONS}."
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Check if the image size is within the limit
|
|
366
|
+
size = get_image_size(image=image, source=source)
|
|
367
|
+
if size > MAX_IMAGE_SIZE_MB:
|
|
368
|
+
raise ValueError(f"Individual image sizes must be less than {MAX_IMAGE_SIZE_MB} MB each.")
|
|
369
|
+
|
|
370
|
+
image_info.append({"image": image, "file_type": file_type, "size": size, "source": source})
|
|
371
|
+
|
|
372
|
+
return image_info
|
|
373
|
+
|
|
374
|
+
def _query(
|
|
375
|
+
self, is_async: bool, fn_name: str, text_input: Optional[str], images_input: Optional[List[str]]
|
|
376
|
+
) -> Union[Dict[str, Any], Coroutine[Any, Any, Dict[str, Any]]]:
|
|
377
|
+
"""Internal method to handle both synchronous and asynchronous query requests.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
is_async : bool
|
|
382
|
+
Whether to perform an asynchronous request.
|
|
383
|
+
fn_name : str
|
|
384
|
+
The name of the function to query.
|
|
385
|
+
text_input : str, optional
|
|
386
|
+
The text input to the function.
|
|
387
|
+
images_input : List[str], optional
|
|
388
|
+
A list of image URLs or images encoded in base64 with their metadata to be sent as input to the function.
|
|
389
|
+
|
|
390
|
+
Returns
|
|
391
|
+
-------
|
|
392
|
+
Union[Dict[str, Any], Coroutine[Any, Any, dict]]
|
|
393
|
+
A dictionary containing the query results, or a coroutine that returns such a dictionary.
|
|
394
|
+
|
|
395
|
+
Raises
|
|
396
|
+
------
|
|
397
|
+
ValueError
|
|
398
|
+
If the input is invalid.
|
|
399
|
+
"""
|
|
400
|
+
# Validate the input
|
|
401
|
+
image_info = self._validate_query(text_input=text_input, images_input=images_input)
|
|
402
|
+
|
|
403
|
+
# Create links for all images that are not public URLs and upload images
|
|
404
|
+
image_urls = []
|
|
405
|
+
upload_id = generate_random_base16_code()
|
|
406
|
+
for i, info in enumerate(image_info):
|
|
407
|
+
if info["source"] == "url" or info["source"] == "base64" or info["source"] == "local":
|
|
408
|
+
url = self._upload_image(fn_name=fn_name, upload_id=upload_id, image_info=info)
|
|
409
|
+
else:
|
|
410
|
+
raise ValueError(f"Image at index {i} must be a public URL or a path to a local image file.")
|
|
411
|
+
image_urls.append(url)
|
|
412
|
+
|
|
413
|
+
# Make the request
|
|
414
|
+
endpoint = "query"
|
|
415
|
+
data = {"name": fn_name, "text": text_input, "images": image_urls}
|
|
416
|
+
request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
|
|
417
|
+
|
|
418
|
+
if is_async:
|
|
419
|
+
|
|
420
|
+
async def _async_query():
|
|
421
|
+
response = await request
|
|
422
|
+
return self._process_query_response(response=response)
|
|
423
|
+
|
|
424
|
+
return _async_query()
|
|
425
|
+
else:
|
|
426
|
+
response = request # the request has already been made and the response is available
|
|
427
|
+
return self._process_query_response(response=response)
|
|
428
|
+
|
|
429
|
+
async def aquery(
|
|
430
|
+
self, fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = []
|
|
431
|
+
) -> Dict[str, Any]:
|
|
432
|
+
"""Asynchronously queries a function with the given function ID and input.
|
|
94
433
|
|
|
95
434
|
Parameters
|
|
96
435
|
----------
|
|
97
436
|
fn_name : str
|
|
98
437
|
The name of the function to query.
|
|
99
|
-
|
|
100
|
-
The input to the function.
|
|
438
|
+
text_input : str, optional
|
|
439
|
+
The text input to the function.
|
|
440
|
+
images_input : List[str], optional
|
|
441
|
+
A list of image URLs or images encoded in base64 with their metadata to be sent as input to the function.
|
|
101
442
|
|
|
102
443
|
Returns
|
|
103
444
|
-------
|
|
@@ -105,17 +446,69 @@ class WecoAI:
|
|
|
105
446
|
A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
|
|
106
447
|
and the latency in milliseconds.
|
|
107
448
|
"""
|
|
449
|
+
return await self._query(fn_name=fn_name, text_input=text_input, images_input=images_input, is_async=True)
|
|
108
450
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
451
|
+
def query(self, fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = []) -> Dict[str, Any]:
|
|
452
|
+
"""Synchronously queries a function with the given function ID and input.
|
|
453
|
+
|
|
454
|
+
Parameters
|
|
455
|
+
----------
|
|
456
|
+
fn_name : str
|
|
457
|
+
The name of the function to query.
|
|
458
|
+
text_input : str, optional
|
|
459
|
+
The text input to the function.
|
|
460
|
+
images_input : List[str], optional
|
|
461
|
+
A list of image URLs or images encoded in base64 with their metadata to be sent as input to the function.
|
|
462
|
+
|
|
463
|
+
Returns
|
|
464
|
+
-------
|
|
465
|
+
dict
|
|
466
|
+
A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
|
|
467
|
+
and the latency in milliseconds.
|
|
468
|
+
"""
|
|
469
|
+
return self._query(fn_name=fn_name, text_input=text_input, images_input=images_input, is_async=False)
|
|
470
|
+
|
|
471
|
+
def batch_query(self, fn_names: Union[str, List[str]], batch_inputs: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
472
|
+
"""Synchronously queries multiple functions using asynchronous calls internally.
|
|
473
|
+
|
|
474
|
+
This method uses the asynchronous queries to submit all queries concurrently
|
|
475
|
+
and waits for all responses to be received before returning the results.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
fn_name : Union[str, List[str]]
|
|
480
|
+
The name of the function or a list of function names to query.
|
|
481
|
+
Note that if a single function name is provided, it will be used for all queries.
|
|
482
|
+
If a list of function names is provided, the length must match the number of queries.
|
|
483
|
+
|
|
484
|
+
batch_inputs : List[Dict[str, Any]]
|
|
485
|
+
A list of inputs for the functions to query. The input must be a dictionary containing the data to be processed. e.g.,
|
|
486
|
+
when providing for a text input, the dictionary should be {"text_input": "input text"}, for an image input, the dictionary should be {"images_input": ["url1", "url2", ...]}
|
|
487
|
+
and for a combination of text and image inputs, the dictionary should be {"text_input": "input text", "images_input": ["url1", "url2", ...]}.
|
|
488
|
+
Note that the index of each input must correspond to the index of the function name when both inputs are lists.
|
|
489
|
+
|
|
490
|
+
Returns
|
|
491
|
+
-------
|
|
492
|
+
List[Dict[str, Any]]
|
|
493
|
+
A list of dictionaries, each containing the output of a function query,
|
|
494
|
+
in the same order as the input queries.
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
Raises
|
|
498
|
+
------
|
|
499
|
+
ValueError
|
|
500
|
+
If the number of function names (when provided as a list) does not match the number of inputs.
|
|
501
|
+
"""
|
|
502
|
+
if isinstance(fn_names, str):
|
|
503
|
+
fn_names = [fn_names] * len(batch_inputs)
|
|
504
|
+
elif len(fn_names) != len(batch_inputs):
|
|
505
|
+
raise ValueError("The number of function names must match the number of inputs.")
|
|
506
|
+
|
|
507
|
+
async def run_queries():
|
|
508
|
+
tasks = [
|
|
509
|
+
self.aquery(fn_name=fn_name, **fn_input) # unpack the input kwargs
|
|
510
|
+
for fn_name, fn_input in zip(fn_names, batch_inputs)
|
|
511
|
+
]
|
|
512
|
+
return await asyncio.gather(*tasks)
|
|
513
|
+
|
|
514
|
+
return asyncio.run(run_queries())
|
weco/constants.py
ADDED
weco/functional.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from typing import Any, Dict, Optional
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
from .client import WecoAI
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
# TODO: Implement the closing stuff for the client
|
|
6
7
|
def build(task_description: str, api_key: str = None) -> tuple[str, str]:
|
|
7
|
-
"""Builds a specialized function given a task description.
|
|
8
|
+
"""Builds a specialized function synchronously given a task description.
|
|
8
9
|
|
|
9
10
|
Parameters
|
|
10
11
|
----------
|
|
@@ -23,15 +24,39 @@ def build(task_description: str, api_key: str = None) -> tuple[str, str]:
|
|
|
23
24
|
return response
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
-
"""
|
|
27
|
+
async def abuild(task_description: str, api_key: str = None) -> tuple[str, str]:
|
|
28
|
+
"""Builds a specialized function asynchronously given a task description.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
task_description : str
|
|
33
|
+
A description of the task for which the function is being built.
|
|
34
|
+
api_key : str
|
|
35
|
+
The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
tuple[str, str]
|
|
40
|
+
A tuple containing the name and description of the function.
|
|
41
|
+
"""
|
|
42
|
+
client = WecoAI(api_key=api_key)
|
|
43
|
+
response = await client.abuild(task_description=task_description)
|
|
44
|
+
return response
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def query(
|
|
48
|
+
fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
|
|
49
|
+
) -> Dict[str, Any]:
|
|
50
|
+
"""Queries a function synchronously with the given function ID and input.
|
|
28
51
|
|
|
29
52
|
Parameters
|
|
30
53
|
----------
|
|
31
54
|
fn_name : str
|
|
32
55
|
The name of the function to query.
|
|
33
|
-
|
|
34
|
-
The input to the function.
|
|
56
|
+
text_input : str, optional
|
|
57
|
+
The text input to the function.
|
|
58
|
+
images_input : List[str], optional
|
|
59
|
+
A list of image URLs or base64 encoded images to be used as input to the function.
|
|
35
60
|
api_key : str
|
|
36
61
|
The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
|
|
37
62
|
|
|
@@ -42,5 +67,67 @@ def query(fn_name: str, fn_input: str, api_key: Optional[str] = None) -> Dict[st
|
|
|
42
67
|
and the latency in milliseconds.
|
|
43
68
|
"""
|
|
44
69
|
client = WecoAI(api_key=api_key)
|
|
45
|
-
response = client.query(fn_name=fn_name,
|
|
70
|
+
response = client.query(fn_name=fn_name, text_input=text_input, images_input=images_input)
|
|
46
71
|
return response
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def aquery(
|
|
75
|
+
fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
|
|
76
|
+
) -> Dict[str, Any]:
|
|
77
|
+
"""Queries a function asynchronously with the given function ID and input.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
fn_name : str
|
|
82
|
+
The name of the function to query.
|
|
83
|
+
text_input : str, optional
|
|
84
|
+
The text input to the function.
|
|
85
|
+
images_input : List[str], optional
|
|
86
|
+
A list of image URLs to be used as input to the function.
|
|
87
|
+
api_key : str
|
|
88
|
+
The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
dict
|
|
93
|
+
A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
|
|
94
|
+
and the latency in milliseconds.
|
|
95
|
+
"""
|
|
96
|
+
client = WecoAI(api_key=api_key)
|
|
97
|
+
response = await client.aquery(fn_name=fn_name, text_input=text_input, images_input=images_input)
|
|
98
|
+
return response
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def batch_query(
|
|
102
|
+
fn_names: str | List[str], batch_inputs: List[Dict[str, Any]], api_key: Optional[str] = None
|
|
103
|
+
) -> List[Dict[str, Any]]:
|
|
104
|
+
"""Synchronously queries multiple functions using asynchronous calls internally.
|
|
105
|
+
|
|
106
|
+
This method uses the asynchronous queries to submit all queries concurrently
|
|
107
|
+
and waits for all responses to be received before returning the results.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
fn_name : str | List[str]
|
|
112
|
+
The name of the function or a list of function names to query.
|
|
113
|
+
Note that if a single function name is provided, it will be used for all queries.
|
|
114
|
+
If a list of function names is provided, the length must match the number of queries.
|
|
115
|
+
|
|
116
|
+
batch_inputs : List[str]
|
|
117
|
+
A list of inputs for the functions to query. The input must be a dictionary containing the data to be processed. e.g.,
|
|
118
|
+
when providing for a text input, the dictionary should be {"text_input": "input text"}, for an image input, the dictionary should be {"images_input": ["url1", "url2", ...]}
|
|
119
|
+
and for a combination of text and image inputs, the dictionary should be {"text_input": "input text", "images_input": ["url1", "url2", ...]}.
|
|
120
|
+
Note that the index of each input must correspond to the index of the function name when both inputs are lists.
|
|
121
|
+
|
|
122
|
+
api_key : str, optional
|
|
123
|
+
The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
List[Dict[str, Any]]
|
|
128
|
+
A list of dictionaries, each containing the output of a function query,
|
|
129
|
+
in the same order as the input queries.
|
|
130
|
+
"""
|
|
131
|
+
client = WecoAI(api_key=api_key)
|
|
132
|
+
responses = client.batch_query(fn_names=fn_names, batch_inputs=batch_inputs)
|
|
133
|
+
return responses
|
weco/utils.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
import random
|
|
4
|
+
import re
|
|
5
|
+
import string
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from typing import Dict, Optional, Tuple
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_local_image(maybe_local_image: str) -> bool:
|
|
15
|
+
"""
|
|
16
|
+
Check if the file is a local image.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
maybe_local_image : str
|
|
21
|
+
The file path.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
bool
|
|
26
|
+
True if the file is a local image, False otherwise.
|
|
27
|
+
"""
|
|
28
|
+
if not os.path.exists(maybe_local_image): # Check if the file exists
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
try: # Check if the file is an image
|
|
32
|
+
Image.open(maybe_local_image)
|
|
33
|
+
except IOError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_base64_image(maybe_base64: str) -> Tuple[bool, Optional[Dict[str, str]]]:
|
|
40
|
+
"""
|
|
41
|
+
Check if the image is a base64 encoded image and if so, extract the image information from the encoded data or URL provided.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
data : str
|
|
46
|
+
The image data or URL.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
Tuple[bool, Optional[Dict[str, str]]]
|
|
51
|
+
"""
|
|
52
|
+
pattern = r"data:(?P<media_type>[\w/]+);(?P<source_type>\w+),(?P<encoding>.*)"
|
|
53
|
+
match = re.match(pattern, maybe_base64)
|
|
54
|
+
if match:
|
|
55
|
+
return True, match.groupdict()
|
|
56
|
+
|
|
57
|
+
return False, None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_public_url_image(maybe_url_image: str) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Check if the string is a publicly accessible URL
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
maybe_url_image : str
|
|
67
|
+
The URL to check.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
bool
|
|
72
|
+
True if the URL is publicly accessible, False otherwise.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
# Check if it is a valid URL
|
|
76
|
+
if not urlparse(maybe_url_image).scheme:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
# Check if the URL is publicly accessible
|
|
80
|
+
response = requests.head(maybe_url_image)
|
|
81
|
+
if response.status_code != 200:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Check if the URL is an image
|
|
85
|
+
content_type = response.headers.get("content-type")
|
|
86
|
+
if not content_type:
|
|
87
|
+
return False
|
|
88
|
+
if not content_type.startswith("image"):
|
|
89
|
+
return False
|
|
90
|
+
except Exception:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_image_size(image: str, source: str) -> float:
|
|
97
|
+
"""
|
|
98
|
+
Get the size of the image in MB.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
image : str
|
|
103
|
+
The image data or URL.
|
|
104
|
+
|
|
105
|
+
source : str
|
|
106
|
+
The source of the image. It can be 'base64', 'url', or 'local'.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
float
|
|
111
|
+
The size of the image in MB.
|
|
112
|
+
|
|
113
|
+
Raises
|
|
114
|
+
------
|
|
115
|
+
ValueError
|
|
116
|
+
If the image is not a valid image.
|
|
117
|
+
"""
|
|
118
|
+
if source == "base64":
|
|
119
|
+
_, base64_info = is_base64_image(maybe_base64=image)
|
|
120
|
+
img_data = base64.b64decode(base64_info["encoding"])
|
|
121
|
+
elif source == "url":
|
|
122
|
+
response = requests.get(image)
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
img_data = response.content
|
|
125
|
+
elif source == "local":
|
|
126
|
+
with open(image, "rb") as f:
|
|
127
|
+
img_data = f.read()
|
|
128
|
+
else:
|
|
129
|
+
raise ValueError("Invalid image input")
|
|
130
|
+
|
|
131
|
+
img = Image.open(BytesIO(img_data))
|
|
132
|
+
img_byte_arr = BytesIO()
|
|
133
|
+
img.save(img_byte_arr, format=img.format)
|
|
134
|
+
return img_byte_arr.tell() / 1000000 # MB
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def preprocess_image(image: Image, file_type: str) -> Tuple:
|
|
138
|
+
"""
|
|
139
|
+
Preprocess the image by converting it to RGB if it has an alpha channel.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
image : Image
|
|
144
|
+
The image to preprocess.
|
|
145
|
+
file_type : str
|
|
146
|
+
The file type of the image.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
Image
|
|
151
|
+
The preprocessed image.
|
|
152
|
+
file_type : str
|
|
153
|
+
The file type of the image.
|
|
154
|
+
"""
|
|
155
|
+
# Do not rescale or resize. Only do this if latency becomes an issue.
|
|
156
|
+
# Remove the alpha channel for PNG and WEBP images if it exists.
|
|
157
|
+
if image.mode in ("RGBA", "LA") or (image.mode == "P" and "transparency" in image.info):
|
|
158
|
+
image = image.convert("RGB")
|
|
159
|
+
|
|
160
|
+
# If the image file type is JPG, convert to JPEG for PIL compatibility.
|
|
161
|
+
if file_type == "jpg":
|
|
162
|
+
file_type = "jpeg"
|
|
163
|
+
return image, file_type
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def generate_random_base16_code(length: int = 5):
|
|
167
|
+
"""
|
|
168
|
+
Generate a random base16 code.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
length : int
|
|
173
|
+
The length of the code.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
str
|
|
178
|
+
The random base16 code.
|
|
179
|
+
"""
|
|
180
|
+
return "".join(random.choices(string.hexdigits, k=length))
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: weco
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A client facing API for interacting with the WeCo AI function builder service.
|
|
5
|
+
Author-email: WeCo AI Team <dhruv@weco.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/WecoAI/weco-python
|
|
8
|
+
Keywords: AI,LLM,machine learning,data science,function builder,AI function
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: asyncio
|
|
16
|
+
Requires-Dist: httpx[http2]
|
|
17
|
+
Requires-Dist: pillow
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: flake8 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: flake8-pyproject ; extra == 'dev'
|
|
21
|
+
Requires-Dist: black ; extra == 'dev'
|
|
22
|
+
Requires-Dist: isort ; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio ; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-xdist ; extra == 'dev'
|
|
25
|
+
Requires-Dist: build ; extra == 'dev'
|
|
26
|
+
Requires-Dist: setuptools-scm ; extra == 'dev'
|
|
27
|
+
|
|
28
|
+
<div align="center" style="display: flex; align-items: center; justify-content: center;">
|
|
29
|
+
<img src="assets/weco.svg" alt="WeCo AI" style="height: 50px; margin-right: 10px;">
|
|
30
|
+
<a href="https://git.io/typing-svg"><img src="https://readme-typing-svg.demolab.com?font=Georgia&size=32&duration=4000&pause=400&color=FD4578&vCenter=true&multiline=false&width=200&height=50&lines=WeCo+Client" alt="Typing SVG" /></a>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
[](https://opensource.org/licenses/MIT)
|
|
35
|
+
|
|
36
|
+
<!-- TODO: Update examples -->
|
|
37
|
+
# $f$(👷♂️)
|
|
38
|
+
|
|
39
|
+
<a href="https://colab.research.google.com/github/WecoAI/weco-python/blob/main/examples/cookbook.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" width=110 height=20/></a>
|
|
40
|
+
<a target="_blank" href="https://lightning.ai/new?repo_url=https%3A%2F%2Fgithub.com%2FWecoAI%2Fweco-python%2Fblob%2Fmain%2Fexamples%2Fcookbook.ipynb"><img src="https://pl-bolts-doc-images.s3.us-east-2.amazonaws.com/app-2/studio-badge.svg" alt="Open in Studio" width=100 height=20/></a>
|
|
41
|
+
|
|
42
|
+
A client facing API for interacting with the [WeCo AI](https://www.weco.ai/) function builder [service](https://weco-app.vercel.app/function)!
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Use this API to build *complex* systems *fast*. We lower the barrier of entry to software engineer, data science and machine learning by providing an interface to prototype difficult solutions quickly in just a few lines of code.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
Install the `weco` package simply by calling this in your terminal of choice:
|
|
50
|
+
```bash
|
|
51
|
+
pip install weco
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- The **build** function enables quick and easy prototyping of new functions via LLMs through just natural language. We encourage users to do this through our [web console](https://weco-app.vercel.app/function) for maximum control and ease of use, however, you can also do this through our API as shown in [here](examples/cookbook.ipynb).
|
|
57
|
+
- The **query** function allows you to test and use the newly created function in your own code.
|
|
58
|
+
- We offer asynchronous versions of the above clients.
|
|
59
|
+
- We provide a **batch_query** functions that allows users to batch functions for various inputs as well as multiple inputs for the same function in a query. This is helpful to make a large number of queries more efficiently.
|
|
60
|
+
- We also offer multimodality capabilities. You can now query our client with both **language** AND **vision** inputs!
|
|
61
|
+
|
|
62
|
+
We provide both services in two ways:
|
|
63
|
+
- `weco.WecoAI` client to be used when you want to maintain the same client service across a portion of code. This is better for dense service usage.
|
|
64
|
+
- `weco.query` and `weco.build` to be used when you only require sparse usage.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
When using the WeCo API, you will need to set the API key:
|
|
69
|
+
You can find/setup your API key [here](https://weco-app.vercel.app/account) by navigating to the API key tab. Once you have your API key, you may pass it to the `weco` client using the `api_key` argument input or set it as an environment variable such as:
|
|
70
|
+
```bash
|
|
71
|
+
export WECO_API_KEY=<YOUR_WECO_API_KEY>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
We create a function on the [web console](https://weco-app.vercel.app/function) for the following task:
|
|
77
|
+
> "Analyze a business idea and provide a structured evaluation. Output a JSON with 'viability_score' (0-100), 'strengths' (list), 'weaknesses' (list), and 'next_steps' (list)."
|
|
78
|
+
|
|
79
|
+
Now, you're ready to query this function anywhere in your code!
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from weco import query
|
|
83
|
+
response = query(
|
|
84
|
+
fn_name="BusinessIdeaAnalyzer-XYZ123", # Replace with your actual function name
|
|
85
|
+
text_input="A subscription service for personalized, AI-generated bedtime stories for children."
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For more examples and an advanced user guide, check out our function builder [cookbook](examples/cookbook.ipynb).
|
|
90
|
+
|
|
91
|
+
## Happy building $f$(👷♂️)!
|
|
92
|
+
|
|
93
|
+
## Contributing
|
|
94
|
+
|
|
95
|
+
We value your contributions! If you believe you can help to improve our package enabling people to build AI with AI, please contribute!
|
|
96
|
+
|
|
97
|
+
Use the following steps as a guideline to help you make contributions:
|
|
98
|
+
|
|
99
|
+
1. Download and install package from source:
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/WecoAI/weco-python.git
|
|
102
|
+
cd weco-python
|
|
103
|
+
pip install -e ".[dev]"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
2. Create a new branch for your feature or bugfix:
|
|
107
|
+
```bash
|
|
108
|
+
git checkout -b feature/your-feature-name
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
3. Make your changes and run tests to ensure everything is working:
|
|
112
|
+
|
|
113
|
+
> **Tests can be expensive to run as they make LLM requests with the API key being used so it is the developers best interests to write small and simple tests that adds coverage for a large portion of the package.**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pytest -n auto tests
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
4. Commit and push your changes, then open a PR for us to view 😁
|
|
120
|
+
|
|
121
|
+
Please ensure your code follows our style guidelines (Numpy docstrings) and includes appropriate tests. We appreciate your contributions!
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
weco/__init__.py,sha256=qiKpnrm6t0n0bpAtXEKJO1Yz2xYXnJJRZBWt-cH7DdU,168
|
|
2
|
+
weco/client.py,sha256=MGwLxV0pgZ_dighM5tvE32VbM1lspppTq8EmdZnXCfE,20608
|
|
3
|
+
weco/constants.py,sha256=zIVqyFh6JQVWkPpeh7ABUvTmJ9CjQk_4LPlz4pEO0Uk,145
|
|
4
|
+
weco/functional.py,sha256=MpyFaREjoPBWqY0iLgsN0JovL6cyCyNf5SzYI0i1b5w,5328
|
|
5
|
+
weco/utils.py,sha256=UUSw6ocqWdlSmIXVcH66DAL4NuLU2rFOyviD8aTWsv0,4371
|
|
6
|
+
weco-0.1.4.dist-info/LICENSE,sha256=NvpxfBuSajszAczWBGKxhHe4gsvil1H63zmu8xXZdL0,1064
|
|
7
|
+
weco-0.1.4.dist-info/METADATA,sha256=YonF54xTkK71fPqBSuRmRcyqQDRh059LKxUpVvrZ94w,5957
|
|
8
|
+
weco-0.1.4.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
9
|
+
weco-0.1.4.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
10
|
+
weco-0.1.4.dist-info/RECORD,,
|
weco-0.1.1.dist-info/METADATA
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: weco
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: A client facing API for interacting with the WeCo AI function builder service.
|
|
5
|
-
Home-page: https://github.com/WecoAI/weco
|
|
6
|
-
Author: ['WeCo AI Team']
|
|
7
|
-
Author-email: dhruv@weco.ai
|
|
8
|
-
License: MIT
|
|
9
|
-
Keywords: artificial intelligence,machine learning,data science,function builder,LLM
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.8
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: requests
|
|
16
|
-
|
|
17
|
-
[](https://git.io/typing-svg)
|
|
18
|
-
|
|
19
|
-
[](https://opensource.org/licenses/MIT)
|
|
20
|
-

|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# WeCo $f$(👷♂️)
|
|
24
|
-
|
|
25
|
-
A client facing API for interacting with the [WeCo AI](https://www.weco.ai/) function builder [service](https://weco-app.vercel.app/function)!
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Use this API to build *complex* systems *fast*. We lower the barrier of entry to software engineer, data science and machine learning by providing an interface to prototype difficult solutions quickly in just a few lines of code.
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
|
|
32
|
-
Install the `weco` package simply by calling this in your terminal of choice:
|
|
33
|
-
```bash
|
|
34
|
-
pip install weco
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
- The **build** function enables quick and easy prototyping of new functions via LLMs through just natural language. We encourage users to do this through our [web console](https://weco-app.vercel.app/function) for maximum control and ease of use, however, you can also do this through our API as shown in [here](examples/).
|
|
40
|
-
- The **query** function allows you to test and use the newly created function in your own code.
|
|
41
|
-
|
|
42
|
-
We provide both services in two ways:
|
|
43
|
-
- `weco.WecoAI` client to be used when you want to maintain the same client service across a portion of code. This is better for dense service usage. An example is shown [here](examples/example_client.py).
|
|
44
|
-
- `weco.query` and `weco.build` to be used when you only require sparse usage. An example is provided [here](examples/example_functional.py).
|
|
45
|
-
|
|
46
|
-
## Usage
|
|
47
|
-
|
|
48
|
-
When using the WeCo API, you will need to set the API key:
|
|
49
|
-
You can find/setup your API key [here](https://weco-app.vercel.app/account) by navigating to the API key tab. Once you have your API key, you may pass it to the `weco` client using the `api_key` argument input or set it as an environment variable such as:
|
|
50
|
-
```
|
|
51
|
-
export WECO_API_KEY=<YOUR_WECO_API_KEY>
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Example
|
|
55
|
-
|
|
56
|
-
We create a function on the [web console](https://weco-app.vercel.app/function) for the following task:
|
|
57
|
-
> "I want to evaluate the feasibility of a machine learning task. Give me a json object with three keys - 'feasibility', 'justification', and 'suggestions'."
|
|
58
|
-
|
|
59
|
-
Now, you're ready to query this function anywhere in your code!
|
|
60
|
-
|
|
61
|
-
```python
|
|
62
|
-
from weco import query
|
|
63
|
-
response = query(
|
|
64
|
-
fn_name=fn_name,
|
|
65
|
-
fn_input="I want to train a model to predict house prices using the Boston Housing dataset hosted on Kaggle.",
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Enjoy $f$(👷♂️)!
|
weco-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
weco/__init__.py,sha256=Xl80uFblDNbhIxL3_BQgYc_SninCeOeF0shnzPCrxrw,119
|
|
2
|
-
weco/client.py,sha256=YOUgWJwLKlp9GR2u40pmnjaBtmDjLtdP_pUuvDAMCJQ,3744
|
|
3
|
-
weco/functional.py,sha256=AnQ0PaZKr2K9a7wxXfVPJTTJLgrn1niZDQctdkj5vFA,1501
|
|
4
|
-
weco-0.1.1.dist-info/LICENSE,sha256=NvpxfBuSajszAczWBGKxhHe4gsvil1H63zmu8xXZdL0,1064
|
|
5
|
-
weco-0.1.1.dist-info/METADATA,sha256=ieRckEKjQ-shxHf5VPcNCHENd6CUpJz3i43-EeLSnMw,3214
|
|
6
|
-
weco-0.1.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
7
|
-
weco-0.1.1.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
|
|
8
|
-
weco-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|