weco 0.1.0__py3-none-any.whl → 0.1.3__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 ADDED
@@ -0,0 +1,4 @@
1
+ from .client import WecoAI
2
+ from .functional import abuild, aquery, batch_query, build, query
3
+
4
+ __all__ = ["WecoAI", "build", "abuild", "query", "aquery", "batch_query"]
weco/client.py ADDED
@@ -0,0 +1,297 @@
1
+ import asyncio
2
+ import os
3
+ import warnings
4
+ from typing import Any, Callable, Coroutine, Dict, List, Tuple
5
+
6
+ import httpx
7
+
8
+
9
+ class WecoAI:
10
+ """A client for the WecoAI function builder API that allows users to build and query specialized functions built by LLMs.
11
+ 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.
12
+ Our client supports both synchronous and asynchronous request paradigms and uses HTTP/2 for faster communication with the API.
13
+
14
+ Attributes
15
+ ----------
16
+ api_key : str
17
+ The API key used for authentication.
18
+ """
19
+
20
+ def __init__(self, api_key: str = None, timeout: float = 30.0) -> None:
21
+ """Initializes the WecoAI client with the provided API key and base URL.
22
+
23
+ Parameters
24
+ ----------
25
+ api_key : str, optional
26
+ The API key used for authentication. If not provided, the client will attempt to read it from the environment variable - WECO_API_KEY.
27
+
28
+ Raises
29
+ ------
30
+ ValueError
31
+ If the API key is not provided to the client, is not set as an environment variable or is not a string.
32
+ """
33
+ # Manage the API key
34
+ if api_key is None or not isinstance(api_key, str):
35
+ try:
36
+ api_key = os.environ["WECO_API_KEY"]
37
+ except KeyError:
38
+ raise ValueError("WECO_API_KEY must be passed to client or set as an environment variable")
39
+ self.api_key = api_key
40
+
41
+ self.base_url = "https://function-builder.vercel.app"
42
+
43
+ # Setup clients
44
+ self.client = httpx.Client(http2=False, timeout=timeout)
45
+ self.async_client = httpx.AsyncClient(http2=False, timeout=timeout)
46
+
47
+ def __del__(self):
48
+ """Closes the HTTP clients when the WecoAI instance is deleted."""
49
+ try:
50
+ self.client.close()
51
+ if not self.async_client.is_closed:
52
+ try:
53
+ loop = asyncio.get_event_loop()
54
+ if loop.is_running():
55
+ loop.create_task(self.async_client.aclose())
56
+ else:
57
+ loop.run_until_complete(self.async_client.aclose())
58
+ except RuntimeError:
59
+ # If the event loop is closed, we can't do anything about it
60
+ pass
61
+ except AttributeError:
62
+ # If the client is not initialized, we can't do anything about it
63
+ pass
64
+
65
+ def _headers(self) -> Dict[str, str]:
66
+ """Constructs the headers for the API requests."""
67
+ return {
68
+ "Authorization": f"Bearer {self.api_key}",
69
+ "Content-Type": "application/json",
70
+ }
71
+
72
+ def _make_request(self, endpoint: str, data: Dict[str, Any], is_async: bool = False) -> Callable:
73
+ """Creates a callable for making either synchronous or asynchronous requests.
74
+
75
+ Parameters
76
+ ----------
77
+ endpoint : str
78
+ The API endpoint to which the request will be made.
79
+ data : dict
80
+ The data to be sent in the request body.
81
+ is_async : bool, optional
82
+ Whether to create an asynchronous request (default is False).
83
+
84
+ Returns
85
+ -------
86
+ Callable
87
+ A callable that performs the HTTP request.
88
+ """
89
+ url = f"{self.base_url}/{endpoint}"
90
+ headers = self._headers()
91
+
92
+ if is_async:
93
+
94
+ async def _request():
95
+ response = await self.async_client.post(url, json=data, headers=headers)
96
+ response.raise_for_status()
97
+ return response.json()
98
+
99
+ return _request()
100
+ else:
101
+
102
+ def _request():
103
+ response = self.client.post(url, json=data, headers=headers)
104
+ response.raise_for_status()
105
+ return response.json()
106
+
107
+ return _request()
108
+
109
+ def _process_response(self, response: Dict[str, Any]) -> Dict[str, Any]:
110
+ """Processes the API response and handles warnings.
111
+
112
+ Parameters
113
+ ----------
114
+ response : dict
115
+ The raw API response.
116
+
117
+ Returns
118
+ -------
119
+ dict
120
+ A processed dictionary containing the output, token counts, and latency.
121
+
122
+ Raises
123
+ ------
124
+ UserWarning
125
+ If there are any warnings in the API response.
126
+ """
127
+ for _warning in response.get("warnings", []):
128
+ warnings.warn(_warning)
129
+
130
+ return {
131
+ "output": response["response"],
132
+ "in_tokens": response["num_input_tokens"],
133
+ "out_tokens": response["num_output_tokens"],
134
+ "latency_ms": response["latency_ms"],
135
+ }
136
+
137
+ def _build(self, task_description: str, is_async: bool) -> Tuple[str, str] | Coroutine[Any, Any, Tuple[str, str]]:
138
+ """Internal method to handle both synchronous and asynchronous build requests.
139
+
140
+ Parameters
141
+ ----------
142
+ task_description : str
143
+ A description of the task for which the function is being built.
144
+ is_async : bool
145
+ Whether to perform an asynchronous request.
146
+
147
+ Returns
148
+ -------
149
+ tuple[str, str] | Coroutine[Any, Any, tuple[str, str]]
150
+ A tuple containing the name and description of the function, or a coroutine that returns such a tuple.
151
+ """
152
+ endpoint = "build"
153
+ data = {"request": task_description}
154
+ request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
155
+
156
+ if is_async:
157
+
158
+ async def _async_build():
159
+ response = await request
160
+ return response["name"], response["description"]
161
+
162
+ return _async_build()
163
+ else:
164
+ response = request # the request has already been made and the response is available
165
+ return response["name"], response["description"]
166
+
167
+ async def abuild(self, task_description: str) -> Tuple[str, str]:
168
+ """Asynchronously builds a specialized function given a task description.
169
+
170
+ Parameters
171
+ ----------
172
+ task_description : str
173
+ A description of the task for which the function is being built.
174
+
175
+ Returns
176
+ -------
177
+ tuple[str, str]
178
+ A tuple containing the name and description of the function.
179
+ """
180
+ return await self._build(task_description=task_description, is_async=True)
181
+
182
+ def build(self, task_description: str) -> Tuple[str, str]:
183
+ """Synchronously builds a specialized function given a task description.
184
+
185
+ Parameters
186
+ ----------
187
+ task_description : str
188
+ A description of the task for which the function is being built.
189
+
190
+ Returns
191
+ -------
192
+ tuple[str, str]
193
+ A tuple containing the name and description of the function.
194
+ """
195
+ return self._build(task_description=task_description, is_async=False)
196
+
197
+ def _query(self, fn_name: str, fn_input: str, is_async: bool) -> Dict[str, Any] | Coroutine[Any, Any, Dict[str, Any]]:
198
+ """Internal method to handle both synchronous and asynchronous query requests.
199
+
200
+ Parameters
201
+ ----------
202
+ fn_name : str
203
+ The name of the function to query.
204
+ fn_input : str
205
+ The input to the function.
206
+ is_async : bool
207
+ Whether to perform an asynchronous request.
208
+
209
+ Returns
210
+ -------
211
+ dict | Coroutine[Any, Any, dict]
212
+ A dictionary containing the query results, or a coroutine that returns such a dictionary.
213
+ """
214
+ endpoint = "query"
215
+ data = {"name": fn_name, "user_message": fn_input}
216
+ request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
217
+
218
+ if is_async:
219
+
220
+ async def _async_query():
221
+ response = await request
222
+ return self._process_response(response=response)
223
+
224
+ return _async_query()
225
+ else:
226
+ response = request # the request has already been made and the response is available
227
+ return self._process_response(response=response)
228
+
229
+ async def aquery(self, fn_name: str, fn_input: str) -> Dict[str, Any]:
230
+ """Asynchronously queries a function with the given function ID and input.
231
+
232
+ Parameters
233
+ ----------
234
+ fn_name : str
235
+ The name of the function to query.
236
+ fn_input : str
237
+ The input to the function.
238
+
239
+ Returns
240
+ -------
241
+ dict
242
+ A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
243
+ and the latency in milliseconds.
244
+ """
245
+ return await self._query(fn_name=fn_name, fn_input=fn_input, is_async=True)
246
+
247
+ def query(self, fn_name: str, fn_input: str) -> Dict[str, Any]:
248
+ """Synchronously queries a function with the given function ID and input.
249
+
250
+ Parameters
251
+ ----------
252
+ fn_name : str
253
+ The name of the function to query.
254
+ fn_input : str
255
+ The input to the function.
256
+
257
+ Returns
258
+ -------
259
+ dict
260
+ A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
261
+ and the latency in milliseconds.
262
+ """
263
+ return self._query(fn_name=fn_name, fn_input=fn_input, is_async=False)
264
+
265
+ def batch_query(self, fn_names: str | List[str], batch_inputs: List[str]) -> List[Dict[str, Any]]:
266
+ """Synchronously queries multiple functions using asynchronous calls internally.
267
+
268
+ This method uses the asynchronous queries to submit all queries concurrently
269
+ and waits for all responses to be received before returning the results.
270
+
271
+ Parameters
272
+ ----------
273
+ fn_name : str | List[str]
274
+ The name of the function or a list of function names to query.
275
+ Note that if a single function name is provided, it will be used for all queries.
276
+ If a list of function names is provided, the length must match the number of queries.
277
+
278
+ batch_inputs : List[str]
279
+ A list of inputs for the functions to query.
280
+ Note that the index of each input must correspond to the index of the function name.
281
+
282
+ Returns
283
+ -------
284
+ List[Dict[str, Any]]
285
+ A list of dictionaries, each containing the output of a function query,
286
+ in the same order as the input queries.
287
+ """
288
+ if isinstance(fn_names, str):
289
+ fn_names = [fn_names] * len(batch_inputs)
290
+ elif len(fn_names) != len(batch_inputs):
291
+ raise ValueError("The number of function names must match the number of inputs.")
292
+
293
+ async def run_queries():
294
+ tasks = [self.aquery(fn_name=fn_name, fn_input=fn_input) for fn_name, fn_input in zip(fn_names, batch_inputs)]
295
+ return await asyncio.gather(*tasks)
296
+
297
+ return asyncio.run(run_queries())
weco/functional.py ADDED
@@ -0,0 +1,120 @@
1
+ from typing import Any, Dict, List, Optional
2
+
3
+ from .client import WecoAI
4
+
5
+
6
+ def build(task_description: str, api_key: str = None) -> tuple[str, str]:
7
+ """Builds a specialized function synchronously given a task description.
8
+
9
+ Parameters
10
+ ----------
11
+ task_description : str
12
+ A description of the task for which the function is being built.
13
+ api_key : str
14
+ The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
15
+
16
+ Returns
17
+ -------
18
+ tuple[str, str]
19
+ A tuple containing the name and description of the function.
20
+ """
21
+ client = WecoAI(api_key=api_key)
22
+ response = client.build(task_description=task_description)
23
+ return response
24
+
25
+
26
+ async def abuild(task_description: str, api_key: str = None) -> tuple[str, str]:
27
+ """Builds a specialized function asynchronously given a task description.
28
+
29
+ Parameters
30
+ ----------
31
+ task_description : str
32
+ A description of the task for which the function is being built.
33
+ api_key : str
34
+ The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
35
+
36
+ Returns
37
+ -------
38
+ tuple[str, str]
39
+ A tuple containing the name and description of the function.
40
+ """
41
+ client = WecoAI(api_key=api_key)
42
+ response = await client.abuild(task_description=task_description)
43
+ return response
44
+
45
+
46
+ def query(fn_name: str, fn_input: str, api_key: Optional[str] = None) -> Dict[str, Any]:
47
+ """Queries a function synchronously with the given function ID and input.
48
+
49
+ Parameters
50
+ ----------
51
+ fn_name : str
52
+ The name of the function to query.
53
+ fn_input : str
54
+ The input to the function.
55
+ api_key : str
56
+ The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
57
+
58
+ Returns
59
+ -------
60
+ dict
61
+ A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
62
+ and the latency in milliseconds.
63
+ """
64
+ client = WecoAI(api_key=api_key)
65
+ response = client.query(fn_name=fn_name, fn_input=fn_input)
66
+ return response
67
+
68
+
69
+ async def aquery(fn_name: str, fn_input: str, api_key: Optional[str] = None) -> Dict[str, Any]:
70
+ """Queries a function asynchronously with the given function ID and input.
71
+
72
+ Parameters
73
+ ----------
74
+ fn_name : str
75
+ The name of the function to query.
76
+ fn_input : str
77
+ The input to the function.
78
+ api_key : str
79
+ The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
80
+
81
+ Returns
82
+ -------
83
+ dict
84
+ A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
85
+ and the latency in milliseconds.
86
+ """
87
+ client = WecoAI(api_key=api_key)
88
+ response = await client.aquery(fn_name=fn_name, fn_input=fn_input)
89
+ return response
90
+
91
+
92
+ def batch_query(fn_names: str | List[str], batch_inputs: List[str], api_key: Optional[str] = None) -> List[Dict[str, Any]]:
93
+ """Synchronously queries multiple functions using asynchronous calls internally.
94
+
95
+ This method uses the asynchronous queries to submit all queries concurrently
96
+ and waits for all responses to be received before returning the results.
97
+
98
+ Parameters
99
+ ----------
100
+ fn_name : str | List[str]
101
+ The name of the function or a list of function names to query.
102
+ Note that if a single function name is provided, it will be used for all queries.
103
+ If a list of function names is provided, the length must match the number of queries.
104
+
105
+ batch_inputs : List[str]
106
+ A list of inputs for the functions to query.
107
+ Note that the index of each input must correspond to the index of the function name.
108
+
109
+ api_key : str, optional
110
+ The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
111
+
112
+ Returns
113
+ -------
114
+ List[Dict[str, Any]]
115
+ A list of dictionaries, each containing the output of a function query,
116
+ in the same order as the input queries.
117
+ """
118
+ client = WecoAI(api_key=api_key)
119
+ responses = client.batch_query(fn_names=fn_names, batch_inputs=batch_inputs)
120
+ return responses
@@ -1,26 +1,33 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: weco
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
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
5
+ Author-email: WeCo AI Team <dhruv@weco.ai>
8
6
  License: MIT
9
- Keywords: artificial intelligence,machine learning,data science,function builder,LLM
7
+ Project-URL: Homepage, https://github.com/WecoAI/weco-python
8
+ Keywords: AI,LLM,machine learning,data science,function builder
10
9
  Classifier: Programming Language :: Python :: 3
11
10
  Classifier: Operating System :: OS Independent
11
+ Classifier: License :: OSI Approved :: MIT License
12
12
  Requires-Python: >=3.8
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: requests
16
-
17
- [![Typing SVG](https://readme-typing-svg.demolab.com?font=Georgia&size=32&duration=4000&pause=400&color=FD4578&vCenter=true&multiline=false&width=750&height=100&lines=WeCo:+Function+Builder+Client;)](https://git.io/typing-svg)
18
-
19
- [![License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
20
- ![Python](https://img.shields.io/badge/Python-3.10.14-blue)
21
-
22
-
23
- # WeCo $f$(👷‍♂️)
15
+ Requires-Dist: asyncio
16
+ Requires-Dist: httpx[http2]
17
+ Provides-Extra: dev
18
+ Requires-Dist: build ; extra == 'dev'
19
+ Requires-Dist: setuptools-scm ; extra == 'dev'
20
+ Requires-Dist: flake8 ; extra == 'dev'
21
+ Requires-Dist: flake8-pyproject ; extra == 'dev'
22
+ Requires-Dist: black ; extra == 'dev'
23
+ Requires-Dist: isort ; extra == 'dev'
24
+
25
+ <div align="center" style="display: flex; align-items: center; justify-content: center;">
26
+ <img src="assets/weco.svg" alt="WeCo AI" style="height: 50px; margin-right: 10px;">
27
+ <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>
28
+ </div>
29
+
30
+ # $f$(👷‍♂️)
24
31
 
25
32
  A client facing API for interacting with the [WeCo AI](https://www.weco.ai/) function builder [service](https://weco-app.vercel.app/function)!
26
33
 
@@ -36,18 +43,20 @@ pip install weco
36
43
 
37
44
  ## Features
38
45
 
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/).
46
+ - 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).
40
47
  - The **query** function allows you to test and use the newly created function in your own code.
48
+ - We offer asynchronous versions of the above clients.
49
+ - 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.
41
50
 
42
51
  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).
52
+ - `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.
53
+ - `weco.query` and `weco.build` to be used when you only require sparse usage.
45
54
 
46
55
  ## Usage
47
56
 
48
57
  When using the WeCo API, you will need to set the API key:
49
58
  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
- ```
59
+ ```bash
51
60
  export WECO_API_KEY=<YOUR_WECO_API_KEY>
52
61
  ```
53
62
 
@@ -66,4 +75,6 @@ response = query(
66
75
  )
67
76
  ```
68
77
 
69
- ## Enjoy $f$(👷‍♂️)!
78
+ For more examples and an advanced user guide, check out our function builder [cookbook](examples/cookbook.ipynb).
79
+
80
+ ## Happy building $f$(👷‍♂️)!
@@ -0,0 +1,8 @@
1
+ weco/__init__.py,sha256=qiKpnrm6t0n0bpAtXEKJO1Yz2xYXnJJRZBWt-cH7DdU,168
2
+ weco/client.py,sha256=RZ6wsFohWhcJSz8HIGTIfVpXozUE_bYK7K037GSlZdA,10991
3
+ weco/functional.py,sha256=oZljY8enxw8nPKP1TGBc8AxERt3Y9ukpV1_40vIJ8oE,4375
4
+ weco-0.1.3.dist-info/LICENSE,sha256=NvpxfBuSajszAczWBGKxhHe4gsvil1H63zmu8xXZdL0,1064
5
+ weco-0.1.3.dist-info/METADATA,sha256=vqzoh9iOLs7ZbSVjQwZz1jiLmSdEITITT0A7FWAdz0c,3876
6
+ weco-0.1.3.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
7
+ weco-0.1.3.dist-info/top_level.txt,sha256=F0N7v6e2zBSlsorFv-arAq2yDxQbzX3KVO8GxYhPUeE,5
8
+ weco-0.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1 @@
1
+ weco
@@ -1,5 +0,0 @@
1
- weco-0.1.0.dist-info/LICENSE,sha256=NvpxfBuSajszAczWBGKxhHe4gsvil1H63zmu8xXZdL0,1064
2
- weco-0.1.0.dist-info/METADATA,sha256=ea8ara4e-SA2DxtiUbnxZqSN9yR1iFFiDwxF671veiQ,3214
3
- weco-0.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
4
- weco-0.1.0.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
- weco-0.1.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
-
File without changes