weco 0.1.5__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: weco
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A client facing API for interacting with the WeCo AI function builder service.
5
5
  Author-email: WeCo AI Team <dhruv@weco.ai>
6
6
  License: MIT
@@ -326,6 +326,13 @@
326
326
  "for key, value in query_response.items(): print(f\"{key}: {value}\")"
327
327
  ]
328
328
  },
329
+ {
330
+ "cell_type": "markdown",
331
+ "metadata": {},
332
+ "source": [
333
+ "## A/B Testing with Function Versions"
334
+ ]
335
+ },
329
336
  {
330
337
  "cell_type": "markdown",
331
338
  "metadata": {},
@@ -10,7 +10,7 @@ authors = [
10
10
  ]
11
11
  description = "A client facing API for interacting with the WeCo AI function builder service."
12
12
  readme = "README.md"
13
- version = "0.1.5"
13
+ version = "0.1.6"
14
14
  license = {text = "MIT"}
15
15
  requires-python = ">=3.8"
16
16
  dependencies = ["asyncio", "httpx[http2]", "pillow"]
@@ -8,8 +8,9 @@ from weco import abuild, aquery
8
8
  @pytest.mark.asyncio
9
9
  async def test_abuild(text_evaluator, image_evaluator, text_and_image_evaluator):
10
10
  for evaluator in [text_evaluator, image_evaluator, text_and_image_evaluator]:
11
- fn_name, fn_desc = await evaluator
11
+ fn_name, version_number, fn_desc = await evaluator
12
12
  assert isinstance(fn_name, str)
13
+ assert isinstance(version_number, int)
13
14
  assert isinstance(fn_desc, str)
14
15
 
15
16
 
@@ -23,16 +24,17 @@ async def assert_query_response(query_response):
23
24
 
24
25
  @pytest.fixture
25
26
  async def text_evaluator():
26
- fn_name, fn_desc = await abuild(
27
- task_description="Evaluate the sentiment of the given text. Provide a json object with 'sentiment' and 'explanation' keys."
27
+ fn_name, version_number, fn_desc = await abuild(
28
+ task_description="Evaluate the sentiment of the given text. Provide a json object with 'sentiment' and 'explanation' keys.",
29
+ multimodal=False
28
30
  )
29
- return fn_name, fn_desc
31
+ return fn_name, version_number, fn_desc
30
32
 
31
33
 
32
34
  @pytest.mark.asyncio
33
35
  async def test_text_aquery(text_evaluator):
34
- fn_name, _ = await text_evaluator
35
- query_response = await aquery(fn_name=fn_name, text_input="I love this product!")
36
+ fn_name, version_number, _ = await text_evaluator
37
+ query_response = await aquery(fn_name=fn_name, version_number=version_number, text_input="I love this product!")
36
38
 
37
39
  await assert_query_response(query_response)
38
40
  assert set(query_response["output"].keys()) == {"sentiment", "explanation"}
@@ -40,17 +42,19 @@ async def test_text_aquery(text_evaluator):
40
42
 
41
43
  @pytest.fixture
42
44
  async def image_evaluator():
43
- fn_name, fn_desc = await abuild(
44
- task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys."
45
+ fn_name, version_number, fn_desc = await abuild(
46
+ task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys.",
47
+ multimodal=True
45
48
  )
46
- return fn_name, fn_desc
49
+ return fn_name, version_number, fn_desc
47
50
 
48
51
 
49
52
  @pytest.mark.asyncio
50
53
  async def test_image_aquery(image_evaluator):
51
- fn_name, _ = await image_evaluator
54
+ fn_name, version_number, _ = await image_evaluator
52
55
  query_response = await aquery(
53
56
  fn_name=fn_name,
57
+ version_number=version_number,
54
58
  images_input=[
55
59
  "https://www.integratedtreatmentservices.co.uk/wp-content/uploads/2013/12/Objects-of-Reference.jpg",
56
60
  "https://t4.ftcdn.net/jpg/05/70/90/23/360_F_570902339_kNj1reH40GFXakTy98EmfiZHci2xvUCS.jpg",
@@ -63,17 +67,19 @@ async def test_image_aquery(image_evaluator):
63
67
 
64
68
  @pytest.fixture
65
69
  async def text_and_image_evaluator():
66
- fn_name, fn_desc = await abuild(
67
- task_description="Evaluate, solve and arrive at a numerical answer for the image provided. Provide a json object with 'answer' and 'explanation' keys."
70
+ fn_name, version_number, fn_desc = await abuild(
71
+ task_description="Evaluate, solve and arrive at a numerical answer for the image provided. Provide a json object with 'answer' and 'explanation' keys.",
72
+ multimodal=True
68
73
  )
69
- return fn_name, fn_desc
74
+ return fn_name, version_number, fn_desc
70
75
 
71
76
 
72
77
  @pytest.mark.asyncio
73
78
  async def test_text_and_image_aquery(text_and_image_evaluator):
74
- fn_name, _ = await text_and_image_evaluator
79
+ fn_name, version_number, _ = await text_and_image_evaluator
75
80
  query_response = await aquery(
76
81
  fn_name=fn_name,
82
+ version_number=version_number,
77
83
  text_input="Find x and y.",
78
84
  images_input=[
79
85
  "https://i.ytimg.com/vi/cblHUeq3bkE/hq720.jpg?sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&rs=AOn4CLAKn3piY91QRCBzRgnzAPf7MPrjDQ"
@@ -0,0 +1,75 @@
1
+ import pytest
2
+
3
+ from weco import batch_query, build
4
+
5
+
6
+ # Internally, these functions use the WecoAI client
7
+ # therefore, we can test both the client and functional forms here
8
+ @pytest.fixture
9
+ def ml_task_evaluator():
10
+ fn_name, version_number, _ = build(
11
+ task_description="I want to evaluate the feasibility of a machine learning task. Give me a json object with three keys - 'feasibility', 'justification', and 'suggestions'.",
12
+ multimodal=False
13
+ )
14
+ return fn_name, version_number
15
+
16
+
17
+ @pytest.fixture
18
+ def ml_task_inputs():
19
+ return [
20
+ {"text_input": "I want to train a model to predict house prices using the Boston Housing dataset hosted on Kaggle."},
21
+ {"text_input": "I want to train a model to classify digits using the MNIST dataset hosted on Kaggle using a Google Colab notebook."},
22
+ ]
23
+
24
+
25
+ @pytest.fixture
26
+ def image_evaluator():
27
+ fn_name, version_number, _ = build(
28
+ task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys.",
29
+ multimodal=True
30
+ )
31
+ return fn_name, version_number
32
+
33
+
34
+ @pytest.fixture
35
+ def image_inputs():
36
+ return [
37
+ {"images_input": ["https://www.integratedtreatmentservices.co.uk/wp-content/uploads/2013/12/Objects-of-Reference.jpg"]},
38
+ {"images_input": ["https://t4.ftcdn.net/jpg/05/70/90/23/360_F_570902339_kNj1reH40GFXakTy98EmfiZHci2xvUCS.jpg"]},
39
+ ]
40
+
41
+
42
+ def test_batch_query_text(ml_task_evaluator, ml_task_inputs):
43
+ fn_name, version_number = ml_task_evaluator
44
+ batch_inputs = ml_task_inputs
45
+
46
+ query_responses = batch_query(fn_name=fn_name, version_number=version_number, batch_inputs=batch_inputs)
47
+
48
+ assert len(query_responses) == len(batch_inputs)
49
+
50
+ for query_response in query_responses:
51
+ assert isinstance(query_response["output"], dict)
52
+ assert isinstance(query_response["in_tokens"], int)
53
+ assert isinstance(query_response["out_tokens"], int)
54
+ assert isinstance(query_response["latency_ms"], float)
55
+
56
+ output = query_response["output"]
57
+ assert set(output.keys()) == {"feasibility", "justification", "suggestions"}
58
+
59
+
60
+ def test_batch_query_image(image_evaluator, image_inputs):
61
+ fn_name, version_number = image_evaluator
62
+ batch_inputs = image_inputs
63
+
64
+ query_responses = batch_query(fn_name=fn_name, version_number=version_number, batch_inputs=batch_inputs)
65
+
66
+ assert len(query_responses) == len(batch_inputs)
67
+
68
+ for query_response in query_responses:
69
+ assert isinstance(query_response["output"], dict)
70
+ assert isinstance(query_response["in_tokens"], int)
71
+ assert isinstance(query_response["out_tokens"], int)
72
+ assert isinstance(query_response["latency_ms"], float)
73
+
74
+ output = query_response["output"]
75
+ assert set(output.keys()) == {"description", "objects"}
@@ -7,8 +7,9 @@ from weco import build, query
7
7
  # therefore, we can test both the client and functional forms here
8
8
  def test_build(text_evaluator, image_evaluator, text_and_image_evaluator):
9
9
  for evaluator in [text_evaluator, image_evaluator, text_and_image_evaluator]:
10
- fn_name, fn_desc = evaluator
10
+ fn_name, version_number, fn_desc = evaluator
11
11
  assert isinstance(fn_name, str)
12
+ assert isinstance(version_number, int)
12
13
  assert isinstance(fn_desc, str)
13
14
 
14
15
 
@@ -22,15 +23,16 @@ def assert_query_response(query_response):
22
23
 
23
24
  @pytest.fixture
24
25
  def text_evaluator():
25
- fn_name, fn_desc = build(
26
- task_description="Evaluate the sentiment of the given text. Provide a json object with 'sentiment' and 'explanation' keys."
26
+ fn_name, version_number, fn_desc = build(
27
+ task_description="Evaluate the sentiment of the given text. Provide a json object with 'sentiment' and 'explanation' keys.",
28
+ multimodal=False
27
29
  )
28
- return fn_name, fn_desc
30
+ return fn_name, version_number, fn_desc
29
31
 
30
32
 
31
33
  def test_text_query(text_evaluator):
32
- fn_name, _ = text_evaluator
33
- query_response = query(fn_name=fn_name, text_input="I love this product!")
34
+ fn_name, version_number, _ = text_evaluator
35
+ query_response = query(fn_name=fn_name, version_number=version_number, text_input="I love this product!")
34
36
 
35
37
  assert_query_response(query_response)
36
38
  assert set(query_response["output"].keys()) == {"sentiment", "explanation"}
@@ -38,16 +40,18 @@ def test_text_query(text_evaluator):
38
40
 
39
41
  @pytest.fixture
40
42
  def image_evaluator():
41
- fn_name, fn_desc = build(
42
- task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys."
43
+ fn_name, version_number, fn_desc = build(
44
+ task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys.",
45
+ multimodal=True
43
46
  )
44
- return fn_name, fn_desc
47
+ return fn_name, version_number, fn_desc
45
48
 
46
49
 
47
50
  def test_image_query(image_evaluator):
48
- fn_name, _ = image_evaluator
51
+ fn_name, version_number, _ = image_evaluator
49
52
  query_response = query(
50
53
  fn_name=fn_name,
54
+ version_number=version_number,
51
55
  images_input=[
52
56
  "https://www.integratedtreatmentservices.co.uk/wp-content/uploads/2013/12/Objects-of-Reference.jpg",
53
57
  "https://t4.ftcdn.net/jpg/05/70/90/23/360_F_570902339_kNj1reH40GFXakTy98EmfiZHci2xvUCS.jpg",
@@ -60,16 +64,18 @@ def test_image_query(image_evaluator):
60
64
 
61
65
  @pytest.fixture
62
66
  def text_and_image_evaluator():
63
- fn_name, fn_desc = build(
64
- task_description="Evaluate, solve and arrive at a numerical answer for the image provided. Perform any additional things if instructed. Provide a json object with 'answer' and 'explanation' keys."
67
+ fn_name, version_number, fn_desc = build(
68
+ task_description="Evaluate, solve and arrive at a numerical answer for the image provided. Perform any additional things if instructed. Provide a json object with 'answer' and 'explanation' keys.",
69
+ multimodal=True
65
70
  )
66
- return fn_name, fn_desc
71
+ return fn_name, version_number, fn_desc
67
72
 
68
73
 
69
74
  def test_text_and_image_query(text_and_image_evaluator):
70
- fn_name, _ = text_and_image_evaluator
75
+ fn_name, version_number, _ = text_and_image_evaluator
71
76
  query_response = query(
72
77
  fn_name=fn_name,
78
+ version_number=version_number,
73
79
  text_input="Find x and y.",
74
80
  images_input=[
75
81
  "https://i.ytimg.com/vi/cblHUeq3bkE/hq720.jpg?sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&rs=AOn4CLAKn3piY91QRCBzRgnzAPf7MPrjDQ"
@@ -65,11 +65,6 @@ class WecoAI:
65
65
  self.client = httpx.Client(http2=http2, timeout=timeout)
66
66
  self.async_client = httpx.AsyncClient(http2=http2, timeout=timeout)
67
67
 
68
- def close(self):
69
- """Close both synchronous and asynchronous clients."""
70
- self.client.close()
71
- asyncio.run(self.async_client.aclose())
72
-
73
68
  def _headers(self) -> Dict[str, str]:
74
69
  """Constructs the headers for the API requests."""
75
70
  return {
@@ -158,20 +153,24 @@ class WecoAI:
158
153
  "latency_ms": response["latency_ms"],
159
154
  }
160
155
 
161
- def _build(self, task_description: str, is_async: bool) -> Union[Tuple[str, str], Coroutine[Any, Any, Tuple[str, str]]]:
156
+ def _build(self, task_description: str, multimodal: bool, is_async: bool) -> Union[Tuple[str, int, str], Coroutine[Any, Any, Tuple[str, int, str]]]:
162
157
  """Internal method to handle both synchronous and asynchronous build requests.
163
158
 
164
159
  Parameters
165
160
  ----------
166
161
  task_description : str
167
162
  A description of the task for which the function is being built.
163
+
164
+ multimodal : bool
165
+ Whether the function is multimodal or not.
166
+
168
167
  is_async : bool
169
168
  Whether to perform an asynchronous request.
170
169
 
171
170
  Returns
172
171
  -------
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.
172
+ Union[tuple[str, int, str], Coroutine[Any, Any, tuple[str, int, str]]]
173
+ A tuple containing the name, version number and description of the function, or a coroutine that returns such a tuple.
175
174
 
176
175
  Raises
177
176
  ------
@@ -185,51 +184,57 @@ class WecoAI:
185
184
  raise ValueError(f"Task description must be less than {MAX_TEXT_LENGTH} characters.")
186
185
 
187
186
  endpoint = "build"
188
- data = {"request": task_description}
187
+ data = {"request": task_description, "multimodal": multimodal}
189
188
  request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
190
189
 
191
190
  if is_async:
192
-
191
+ # return 0 for the version number
193
192
  async def _async_build():
194
193
  response = await request
195
- return response["name"], response["description"]
194
+ return response["function_name"], 0, response["description"]
196
195
 
197
196
  return _async_build()
198
197
  else:
199
198
  response = request # the request has already been made and the response is available
200
- return response["name"], response["description"]
199
+ return response["function_name"], 0, response["description"]
201
200
 
202
- async def abuild(self, task_description: str) -> Tuple[str, str]:
201
+ async def abuild(self, task_description: str, multimodal: bool = False) -> Tuple[str, int, str]:
203
202
  """Asynchronously builds a specialized function given a task description.
204
203
 
205
204
  Parameters
206
205
  ----------
207
206
  task_description : str
208
207
  A description of the task for which the function is being built.
208
+
209
+ multimodal : bool, optional
210
+ Whether the function is multimodal or not (default is False).
209
211
 
210
212
  Returns
211
213
  -------
212
214
  tuple[str, str]
213
- A tuple containing the name and description of the function.
215
+ A tuple containing the name, version number and description of the function.
214
216
  """
215
- return await self._build(task_description=task_description, is_async=True)
217
+ return await self._build(task_description=task_description, multimodal=multimodal, is_async=True)
216
218
 
217
- def build(self, task_description: str) -> Tuple[str, str]:
219
+ def build(self, task_description: str, multimodal: bool = False) -> Tuple[str, int, str]:
218
220
  """Synchronously builds a specialized function given a task description.
219
221
 
220
222
  Parameters
221
223
  ----------
222
224
  task_description : str
223
225
  A description of the task for which the function is being built.
226
+
227
+ multimodal : bool, optional
228
+ Whether the function is multimodal or not (default is False).
224
229
 
225
230
  Returns
226
231
  -------
227
232
  tuple[str, str]
228
- A tuple containing the name and description of the function.
233
+ A tuple containing the name, version number and description of the function.
229
234
  """
230
- return self._build(task_description=task_description, is_async=False)
235
+ return self._build(task_description=task_description, multimodal=multimodal, is_async=False)
231
236
 
232
- def _upload_image(self, fn_name: str, upload_id: str, image_info: Dict[str, Any]) -> str:
237
+ def _upload_image(self, fn_name: str, version_number: int, upload_id: str, image_info: Dict[str, Any]) -> str:
233
238
  """
234
239
  Uploads an image to an S3 bucket and returns the URL of the uploaded image.
235
240
 
@@ -237,6 +242,8 @@ class WecoAI:
237
242
  ----------
238
243
  fn_name : str
239
244
  The name of the function for which the image is being uploaded.
245
+ version_number : int
246
+ The version number of the function for which the image is being uploaded.
240
247
  upload_id: str
241
248
  A unique identifier for the image upload.
242
249
  image_info : Dict[str, Any]
@@ -271,13 +278,12 @@ class WecoAI:
271
278
 
272
279
  # Request a presigned URL from the server
273
280
  endpoint = "upload_link"
274
- request_data = {"fn_name": fn_name, "upload_id": upload_id, "file_type": file_type}
281
+ request_data = {"fn_name": fn_name, "version_number": version_number, "upload_id": upload_id, "file_type": file_type}
275
282
  # This needs to be a synchronous request since we need the presigned URL to upload the image
276
283
  response = self._make_request(endpoint=endpoint, data=request_data, is_async=False)
277
284
 
278
285
  # Upload the image to the S3 bucket
279
- image_name = generate_random_base16_code()
280
- files = {"file": (f"{image_name}.{file_type}", upload_data)}
286
+ files = {"file": upload_data}
281
287
  http_response = requests.post(response["url"], data=response["fields"], files=files)
282
288
  if http_response.status_code == 204:
283
289
  pass
@@ -372,7 +378,7 @@ class WecoAI:
372
378
  return image_info
373
379
 
374
380
  def _query(
375
- self, is_async: bool, fn_name: str, text_input: Optional[str], images_input: Optional[List[str]]
381
+ self, is_async: bool, fn_name: str, version_number: Optional[int], text_input: Optional[str], images_input: Optional[List[str]]
376
382
  ) -> Union[Dict[str, Any], Coroutine[Any, Any, Dict[str, Any]]]:
377
383
  """Internal method to handle both synchronous and asynchronous query requests.
378
384
 
@@ -382,6 +388,8 @@ class WecoAI:
382
388
  Whether to perform an asynchronous request.
383
389
  fn_name : str
384
390
  The name of the function to query.
391
+ version_number : int, optional
392
+ The version number of the function to query.
385
393
  text_input : str, optional
386
394
  The text input to the function.
387
395
  images_input : List[str], optional
@@ -397,6 +405,8 @@ class WecoAI:
397
405
  ValueError
398
406
  If the input is invalid.
399
407
  """
408
+ warnings.warn("Setting the version number of the function is not yet supported. Currently, the first version of the function will be used i.e., version 0.")
409
+ version_number = 0
400
410
  # Validate the input
401
411
  image_info = self._validate_query(text_input=text_input, images_input=images_input)
402
412
 
@@ -405,14 +415,14 @@ class WecoAI:
405
415
  upload_id = generate_random_base16_code()
406
416
  for i, info in enumerate(image_info):
407
417
  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)
418
+ url = self._upload_image(fn_name=fn_name, version_number=version_number, upload_id=upload_id, image_info=info)
409
419
  else:
410
420
  raise ValueError(f"Image at index {i} must be a public URL or a path to a local image file.")
411
421
  image_urls.append(url)
412
422
 
413
423
  # Make the request
414
424
  endpoint = "query"
415
- data = {"name": fn_name, "text": text_input, "images": image_urls}
425
+ data = {"name": fn_name, "text": text_input, "images": image_urls, "version_number": version_number}
416
426
  request = self._make_request(endpoint=endpoint, data=data, is_async=is_async)
417
427
 
418
428
  if is_async:
@@ -427,7 +437,7 @@ class WecoAI:
427
437
  return self._process_query_response(response=response)
428
438
 
429
439
  async def aquery(
430
- self, fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = []
440
+ self, fn_name: str, version_number: Optional[int] = -1, text_input: Optional[str] = "", images_input: Optional[List[str]] = []
431
441
  ) -> Dict[str, Any]:
432
442
  """Asynchronously queries a function with the given function ID and input.
433
443
 
@@ -435,6 +445,8 @@ class WecoAI:
435
445
  ----------
436
446
  fn_name : str
437
447
  The name of the function to query.
448
+ version_number : int, optional
449
+ The version number of the function to query. If not provided, the latest version will be used. Pass -1 to use the latest version.
438
450
  text_input : str, optional
439
451
  The text input to the function.
440
452
  images_input : List[str], optional
@@ -446,15 +458,17 @@ class WecoAI:
446
458
  A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
447
459
  and the latency in milliseconds.
448
460
  """
449
- return await self._query(fn_name=fn_name, text_input=text_input, images_input=images_input, is_async=True)
461
+ return await self._query(fn_name=fn_name, version_number=version_number, text_input=text_input, images_input=images_input, is_async=True)
450
462
 
451
- def query(self, fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = []) -> Dict[str, Any]:
463
+ def query(self, fn_name: str, version_number: Optional[int] = -1, text_input: Optional[str] = "", images_input: Optional[List[str]] = []) -> Dict[str, Any]:
452
464
  """Synchronously queries a function with the given function ID and input.
453
465
 
454
466
  Parameters
455
467
  ----------
456
468
  fn_name : str
457
469
  The name of the function to query.
470
+ version_number : int, optional
471
+ The version number of the function to query. If not provided, the latest version will be used. Pass -1 to use the latest version.
458
472
  text_input : str, optional
459
473
  The text input to the function.
460
474
  images_input : List[str], optional
@@ -466,49 +480,35 @@ class WecoAI:
466
480
  A dictionary containing the output of the function, the number of input tokens, the number of output tokens,
467
481
  and the latency in milliseconds.
468
482
  """
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.
483
+ return self._query(fn_name=fn_name, version_number=version_number, text_input=text_input, images_input=images_input, is_async=False)
473
484
 
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.
485
+ def batch_query(self, fn_name: str, batch_inputs: List[Dict[str, Any]], version_number: Optional[int] = -1) -> List[Dict[str, Any]]:
486
+ """Batch queries a function version with a list of inputs.
476
487
 
477
488
  Parameters
478
489
  ----------
479
- fn_name : Union[str, List[str]]
490
+ fn_name : str
480
491
  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
492
 
484
493
  batch_inputs : List[Dict[str, Any]]
485
494
  A list of inputs for the functions to query. The input must be a dictionary containing the data to be processed. e.g.,
486
495
  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
496
  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.
497
+
498
+ version_number : int, optional
499
+ The version number of the function to query. If not provided, the latest version will be used. Pass -1 to use the latest version.
489
500
 
490
501
  Returns
491
502
  -------
492
503
  List[Dict[str, Any]]
493
504
  A list of dictionaries, each containing the output of a function query,
494
505
  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
506
  """
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
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
- ]
508
+ tasks = list(map(
509
+ lambda fn_input: self.aquery(fn_name=fn_name, version_number=version_number, **fn_input),
510
+ batch_inputs
511
+ ))
512
512
  return await asyncio.gather(*tasks)
513
513
 
514
514
  return asyncio.run(run_queries())
@@ -3,14 +3,15 @@ from typing import Any, Dict, List, Optional
3
3
  from .client import WecoAI
4
4
 
5
5
 
6
- # TODO: Implement the closing stuff for the client
7
- def build(task_description: str, api_key: str = None) -> tuple[str, str]:
6
+ def build(task_description: str, multimodal: bool = False, api_key: str = None) -> tuple[str, int, str]:
8
7
  """Builds a specialized function synchronously given a task description.
9
8
 
10
9
  Parameters
11
10
  ----------
12
11
  task_description : str
13
12
  A description of the task for which the function is being built.
13
+ multimodal : bool, optional
14
+ A flag to indicate if the function should be multimodal. Default is False.
14
15
  api_key : str
15
16
  The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
16
17
 
@@ -20,32 +21,34 @@ def build(task_description: str, api_key: str = None) -> tuple[str, str]:
20
21
  A tuple containing the name and description of the function.
21
22
  """
22
23
  client = WecoAI(api_key=api_key)
23
- response = client.build(task_description=task_description)
24
+ response = client.build(task_description=task_description, multimodal=multimodal)
24
25
  return response
25
26
 
26
27
 
27
- async def abuild(task_description: str, api_key: str = None) -> tuple[str, str]:
28
+ async def abuild(task_description: str, multimodal: bool = False, api_key: str = None) -> tuple[str, int, str]:
28
29
  """Builds a specialized function asynchronously given a task description.
29
30
 
30
31
  Parameters
31
32
  ----------
32
33
  task_description : str
33
34
  A description of the task for which the function is being built.
35
+ multimodal : bool, optional
36
+ A flag to indicate if the function should be multimodal. Default is False.
34
37
  api_key : str
35
38
  The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
36
39
 
37
40
  Returns
38
41
  -------
39
42
  tuple[str, str]
40
- A tuple containing the name and description of the function.
43
+ A tuple containing the name, version number and description of the function.
41
44
  """
42
45
  client = WecoAI(api_key=api_key)
43
- response = await client.abuild(task_description=task_description)
46
+ response = await client.abuild(task_description=task_description, multimodal=multimodal)
44
47
  return response
45
48
 
46
49
 
47
50
  def query(
48
- fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
51
+ fn_name: str, version_number: Optional[int] = -1, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
49
52
  ) -> Dict[str, Any]:
50
53
  """Queries a function synchronously with the given function ID and input.
51
54
 
@@ -53,6 +56,8 @@ def query(
53
56
  ----------
54
57
  fn_name : str
55
58
  The name of the function to query.
59
+ version_number : int, optional
60
+ The version number of the function to query. If not provided, the latest version is used. Default is -1 for the same behavior.
56
61
  text_input : str, optional
57
62
  The text input to the function.
58
63
  images_input : List[str], optional
@@ -67,12 +72,12 @@ def query(
67
72
  and the latency in milliseconds.
68
73
  """
69
74
  client = WecoAI(api_key=api_key)
70
- response = client.query(fn_name=fn_name, text_input=text_input, images_input=images_input)
75
+ response = client.query(fn_name=fn_name, version_number=version_number, text_input=text_input, images_input=images_input)
71
76
  return response
72
77
 
73
78
 
74
79
  async def aquery(
75
- fn_name: str, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
80
+ fn_name: str, version_number: Optional[int] = -1, text_input: Optional[str] = "", images_input: Optional[List[str]] = [], api_key: Optional[str] = None
76
81
  ) -> Dict[str, Any]:
77
82
  """Queries a function asynchronously with the given function ID and input.
78
83
 
@@ -80,6 +85,8 @@ async def aquery(
80
85
  ----------
81
86
  fn_name : str
82
87
  The name of the function to query.
88
+ version_number : int, optional
89
+ The version number of the function to query. If not provided, the latest version is used. Default is -1 for the same behavior.
83
90
  text_input : str, optional
84
91
  The text input to the function.
85
92
  images_input : List[str], optional
@@ -94,12 +101,12 @@ async def aquery(
94
101
  and the latency in milliseconds.
95
102
  """
96
103
  client = WecoAI(api_key=api_key)
97
- response = await client.aquery(fn_name=fn_name, text_input=text_input, images_input=images_input)
104
+ response = await client.aquery(fn_name=fn_name, version_number=version_number, text_input=text_input, images_input=images_input)
98
105
  return response
99
106
 
100
107
 
101
108
  def batch_query(
102
- fn_names: str | List[str], batch_inputs: List[Dict[str, Any]], api_key: Optional[str] = None
109
+ fn_name: str, batch_inputs: List[Dict[str, Any]], version_number: Optional[int] = -1, api_key: Optional[str] = None
103
110
  ) -> List[Dict[str, Any]]:
104
111
  """Synchronously queries multiple functions using asynchronous calls internally.
105
112
 
@@ -117,7 +124,9 @@ def batch_query(
117
124
  A list of inputs for the functions to query. The input must be a dictionary containing the data to be processed. e.g.,
118
125
  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
126
  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.
127
+
128
+ version_number : int, optional
129
+ The version number of the function to query. If not provided, the latest version is used. Default is -1 for the same behavior.
121
130
 
122
131
  api_key : str, optional
123
132
  The API key for the WecoAI service. If not provided, the API key must be set using the environment variable - WECO_API_KEY.
@@ -129,5 +138,5 @@ def batch_query(
129
138
  in the same order as the input queries.
130
139
  """
131
140
  client = WecoAI(api_key=api_key)
132
- responses = client.batch_query(fn_names=fn_names, batch_inputs=batch_inputs)
141
+ responses = client.batch_query(fn_name=fn_name, version_number=version_number, batch_inputs=batch_inputs)
133
142
  return responses
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: weco
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A client facing API for interacting with the WeCo AI function builder service.
5
5
  Author-email: WeCo AI Team <dhruv@weco.ai>
6
6
  License: MIT
@@ -1,66 +0,0 @@
1
- import pytest
2
-
3
- from weco import batch_query, build
4
-
5
-
6
- # Internally, these functions use the WecoAI client
7
- # therefore, we can test both the client and functional forms here
8
- # We do NOT need to test for a list of function names here since interally
9
- # we cast the input to a list of function names to match the fn_input size
10
- @pytest.fixture
11
- def ml_task_evaluator():
12
- fn_name, _ = build(
13
- task_description="I want to evaluate the feasibility of a machine learning task. Give me a json object with three keys - 'feasibility', 'justification', and 'suggestions'."
14
- )
15
- return fn_name
16
-
17
-
18
- @pytest.fixture
19
- def ml_task_inputs():
20
- return [
21
- {"text_input": "I want to train a model to predict house prices using the Boston Housing dataset hosted on Kaggle."},
22
- {
23
- "text_input": "I want to train a model to classify digits using the MNIST dataset hosted on Kaggle using a Google Colab notebook."
24
- },
25
- ]
26
-
27
-
28
- @pytest.fixture
29
- def image_evaluator():
30
- fn_name, _ = build(
31
- task_description="Describe the contents of the given images. Provide a json object with 'description' and 'objects' keys."
32
- )
33
- return fn_name
34
-
35
-
36
- @pytest.fixture
37
- def image_inputs():
38
- return [
39
- {
40
- "images_input": [
41
- "https://www.integratedtreatmentservices.co.uk/wp-content/uploads/2013/12/Objects-of-Reference.jpg"
42
- ]
43
- },
44
- {"images_input": ["https://t4.ftcdn.net/jpg/05/70/90/23/360_F_570902339_kNj1reH40GFXakTy98EmfiZHci2xvUCS.jpg"]},
45
- ]
46
-
47
-
48
- def test_batch_query(ml_task_evaluator, ml_task_inputs, image_evaluator, image_inputs):
49
- fn_names = [ml_task_evaluator, ml_task_evaluator, image_evaluator, image_evaluator]
50
- batch_inputs = ml_task_inputs + image_inputs
51
-
52
- query_responses = batch_query(fn_names=fn_names, batch_inputs=batch_inputs)
53
-
54
- assert len(query_responses) == len(batch_inputs)
55
-
56
- for i, query_response in enumerate(query_responses):
57
- assert isinstance(query_response["output"], dict)
58
- assert isinstance(query_response["in_tokens"], int)
59
- assert isinstance(query_response["out_tokens"], int)
60
- assert isinstance(query_response["latency_ms"], float)
61
-
62
- output = query_response["output"]
63
- if i < len(ml_task_inputs):
64
- assert set(output.keys()) == {"feasibility", "justification", "suggestions"}
65
- else:
66
- assert set(output.keys()) == {"description", "objects"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes