versionhq 1.1.9.4__tar.gz → 1.1.9.10__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.
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.github/workflows/publish.yml +1 -1
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.github/workflows/publish_testpypi.yml +3 -3
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.github/workflows/run_tests.yml +1 -1
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/PKG-INFO +2 -2
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/pyproject.toml +1 -1
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/__init__.py +1 -1
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/agent/model.py +13 -22
- versionhq-1.1.9.10/src/versionhq/clients/customer/__init__.py +10 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/customer/model.py +29 -13
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/product/model.py +40 -3
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/workflow/model.py +11 -5
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/task/model.py +20 -36
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/tool/model.py +0 -34
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq.egg-info/PKG-INFO +2 -2
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq.egg-info/SOURCES.txt +1 -0
- versionhq-1.1.9.10/tests/clients/customer_test.py +31 -0
- versionhq-1.1.9.10/tests/clients/product_test.py +26 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/clients/workflow_test.py +3 -1
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/task/task_test.py +30 -10
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/uv.lock +10 -10
- versionhq-1.1.9.4/src/versionhq/clients/customer/__init__.py +0 -5
- versionhq-1.1.9.4/tests/clients/product_test.py +0 -11
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.github/workflows/security_check.yml +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.gitignore +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.pre-commit-config.yaml +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/.python-version +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/LICENSE +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/README.md +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/SECURITY.md +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/db/preprocess.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/requirements-dev.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/requirements.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/runtime.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/setup.cfg +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/cache_handler.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/i18n.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/logger.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/process_config.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/rpm_controller.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/_utils/usage_metrics.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/agent/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/agent/parser.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/cli/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/product/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/clients/workflow/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/llm/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/llm/llm_vars.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/llm/model.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/storage/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/storage/task_output_storage.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/task/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/task/formatter.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/task/log_handler.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/team/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/team/model.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/team/team_planner.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/tool/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/tool/composio_tool.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/tool/decorator.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq/tool/tool_handler.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq.egg-info/dependency_links.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq.egg-info/requires.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/src/versionhq.egg-info/top_level.txt +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/agent/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/agent/agent_test.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/cli/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/conftest.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/task/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/team/Prompts/Demo_test.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/team/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/team/team_test.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/tool/__init__.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/tool/composio_test.py +0 -0
- {versionhq-1.1.9.4 → versionhq-1.1.9.10}/tests/tool/tool_test.py +0 -0
@@ -19,10 +19,10 @@ jobs:
|
|
19
19
|
uses: astral-sh/setup-uv@v4
|
20
20
|
with:
|
21
21
|
version: "0.5.11"
|
22
|
-
|
22
|
+
|
23
23
|
- name: Build release distributions
|
24
24
|
run: |
|
25
|
-
uv venv --python
|
25
|
+
uv venv --python 3.13.1
|
26
26
|
source .venv/bin/activate
|
27
27
|
uv pip install --upgrade pip twine
|
28
28
|
uv pip install -r requirements.txt
|
@@ -38,7 +38,7 @@ jobs:
|
|
38
38
|
runs-on: ubuntu-latest
|
39
39
|
needs:
|
40
40
|
- release-build
|
41
|
-
|
41
|
+
|
42
42
|
permissions:
|
43
43
|
id-token: write
|
44
44
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.9.
|
3
|
+
Version: 1.1.9.10
|
4
4
|
Summary: LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows
|
5
5
|
Author-email: Kuriko Iwai <kuriko@versi0n.io>
|
6
6
|
License: MIT License
|
@@ -15,7 +15,7 @@ exclude = ["test*", "__pycache__"]
|
|
15
15
|
|
16
16
|
[project]
|
17
17
|
name = "versionhq"
|
18
|
-
version = "1.1.9.
|
18
|
+
version = "1.1.9.10"
|
19
19
|
authors = [{ name = "Kuriko Iwai", email = "kuriko@versi0n.io" }]
|
20
20
|
description = "LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows"
|
21
21
|
readme = "README.md"
|
@@ -322,7 +322,7 @@ class Agent(BaseModel):
|
|
322
322
|
When encountering errors, we try the task execution up to `self.max_retry_limit` times.
|
323
323
|
"""
|
324
324
|
|
325
|
-
task_execution_counter = 0
|
325
|
+
task_execution_counter, raw_response = 0, None
|
326
326
|
|
327
327
|
messages = []
|
328
328
|
messages.append({"role": "user", "content": prompts}) #! REFINEME
|
@@ -331,31 +331,28 @@ class Agent(BaseModel):
|
|
331
331
|
|
332
332
|
callbacks = kwargs.get("callbacks", None)
|
333
333
|
|
334
|
-
|
335
|
-
messages=messages,
|
336
|
-
output_formats=output_formats,
|
337
|
-
field_list=response_fields,
|
338
|
-
callbacks=callbacks,
|
334
|
+
raw_response = self.llm.call(
|
335
|
+
messages=messages, output_formats=output_formats, field_list=response_fields, callbacks=callbacks
|
339
336
|
)
|
340
337
|
task_execution_counter += 1
|
341
|
-
self._logger.log(level="info", message=f"Agent's first response: {
|
338
|
+
self._logger.log(level="info", message=f"Agent's first response in {type(raw_response).__name__}: {raw_response}", color="blue")
|
342
339
|
|
343
|
-
if (
|
340
|
+
if (raw_response is None or raw_response == "") and task_execution_counter < self.max_retry_limit:
|
344
341
|
while task_execution_counter <= self.max_retry_limit:
|
345
|
-
|
342
|
+
raw_response = self.llm.call(
|
346
343
|
messages=messages,
|
347
344
|
output_formats=output_formats,
|
348
345
|
field_list=response_fields,
|
349
346
|
callbacks=callbacks,
|
350
347
|
)
|
351
348
|
task_execution_counter += 1
|
352
|
-
self._logger.log(level="info", message=f"Agent's next response: {
|
349
|
+
self._logger.log(level="info", message=f"Agent's next response in {type(raw_response).__name__}: {raw_response}", color="blue")
|
353
350
|
|
354
|
-
elif
|
351
|
+
elif raw_response is None or raw_response == "":
|
355
352
|
self._logger.log(level="error", message="Received None or empty response from the model", color="red")
|
356
353
|
raise ValueError("Invalid response from LLM call - None or empty.")
|
357
354
|
|
358
|
-
return
|
355
|
+
return raw_response
|
359
356
|
|
360
357
|
|
361
358
|
def execute_task(self, task, context: Optional[str] = None) -> str:
|
@@ -392,31 +389,25 @@ class Agent(BaseModel):
|
|
392
389
|
if task.take_tool_res_as_final:
|
393
390
|
return tool_results
|
394
391
|
|
395
|
-
|
396
|
-
|
397
392
|
# if self.team and self.team._train:
|
398
393
|
# task_prompt = self._training_handler(task_prompt=task_prompt)
|
399
394
|
# else:
|
400
395
|
# task_prompt = self._use_trained_data(task_prompt=task_prompt)
|
401
396
|
|
402
397
|
try:
|
403
|
-
|
398
|
+
raw_response = self.invoke(
|
404
399
|
prompts=task_prompt,
|
405
400
|
output_formats=task.expected_output_formats,
|
406
401
|
response_fields=task.output_field_list,
|
407
|
-
)
|
402
|
+
)
|
408
403
|
|
409
404
|
except Exception as e:
|
410
405
|
self._times_executed += 1
|
411
406
|
if self._times_executed > self.max_retry_limit:
|
412
407
|
raise e
|
413
|
-
|
408
|
+
raw_response = self.execute_task(task, context)
|
414
409
|
|
415
410
|
if self.max_rpm and self._rpm_controller:
|
416
411
|
self._rpm_controller.stop_rpm_counter()
|
417
412
|
|
418
|
-
|
419
|
-
# if tool_result.get("result_as_answer", False):
|
420
|
-
# result = tool_result["result"]
|
421
|
-
|
422
|
-
return result
|
413
|
+
return raw_response
|
@@ -10,14 +10,32 @@ from versionhq.clients.customer import Status
|
|
10
10
|
|
11
11
|
class BaseCustomer(ABC, BaseModel):
|
12
12
|
"""
|
13
|
-
Abstract base class for the base customer
|
13
|
+
Abstract base class for the base customer storing current status on the workflow and deployment method.
|
14
|
+
"""
|
15
|
+
status: Status = Field(default=Status.NOT_ASSIGNED)
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
def _deploy(self, *args, **kwargs) -> Any:
|
19
|
+
"""Any method to deploy targeting the customer"""
|
20
|
+
|
21
|
+
|
22
|
+
def deploy(self, *args, **kwargs) -> Any:
|
23
|
+
if self.status is Status.READY_TO_DEPLOY:
|
24
|
+
return self._deploy(self, **args, **kwargs)
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
class Customer(BaseCustomer):
|
29
|
+
"""
|
30
|
+
Customer class to store customer info and handle deployment methods.
|
14
31
|
"""
|
15
32
|
|
16
33
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
17
34
|
name: Optional[str] = Field(default=None, description="customer's name if any")
|
18
35
|
products: Optional[List[Product]] = Field(default=list, description="store products that the customer is associated with")
|
19
36
|
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
20
|
-
|
37
|
+
function: Optional[Callable] = Field(default=None, descripition="store deploy function")
|
38
|
+
config: Optional[Dict[str, Any]] = Field(default=None, description="config to the function")
|
21
39
|
|
22
40
|
|
23
41
|
@field_validator("id", mode="before")
|
@@ -27,11 +45,10 @@ class BaseCustomer(ABC, BaseModel):
|
|
27
45
|
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
28
46
|
|
29
47
|
|
30
|
-
def
|
48
|
+
def fetch_product_providers(self) -> List[ProductProvider] | None:
|
31
49
|
"""
|
32
50
|
Return list of ProductProvider if the customer has `product_list`
|
33
51
|
"""
|
34
|
-
|
35
52
|
res = []
|
36
53
|
if self.products:
|
37
54
|
for item in self.products:
|
@@ -39,15 +56,14 @@ class BaseCustomer(ABC, BaseModel):
|
|
39
56
|
res.appned(item.provider)
|
40
57
|
return res
|
41
58
|
|
59
|
+
def _deploy(self, *args, **kwargs):
|
60
|
+
return self.deploy(self, *args, **kwargs)
|
42
61
|
|
43
|
-
@abstractmethod
|
44
|
-
def _deploy(self, *args, **kwargs) -> Any:
|
45
|
-
"""Any method to deploy targeting the customer"""
|
46
62
|
|
63
|
+
def deploy(self, *args, **kwargs):
|
64
|
+
self.status = Status.ACTIVE_ON_WORKFLOW
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
analysis: str = Field(default=None, description="store the latest analysis results on the customer")
|
53
|
-
status: str = Field(default=Status.ON_WORKFLOW)
|
66
|
+
if self.function:
|
67
|
+
return self.function(**self.config)
|
68
|
+
|
69
|
+
return super().deploy(*args, **kwargs)
|
@@ -16,7 +16,7 @@ class ProductProvider(ABC, BaseModel):
|
|
16
16
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
17
17
|
name: Optional[str] = Field(default=None)
|
18
18
|
region: Optional[str] = Field(default=None, description="region of client's main business operation")
|
19
|
-
data_pipelines: Optional[List[ComposioAppName | str]] = Field(
|
19
|
+
data_pipelines: Optional[List[ComposioAppName | str]] = Field(default=None)
|
20
20
|
destination_services: Optional[List[ComposioAppName | str]] = Field(default=None)
|
21
21
|
|
22
22
|
@field_validator("id", mode="before")
|
@@ -26,12 +26,41 @@ class ProductProvider(ABC, BaseModel):
|
|
26
26
|
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
27
27
|
|
28
28
|
|
29
|
+
@model_validator(mode="after")
|
30
|
+
def set_up_destinations(self):
|
31
|
+
"""
|
32
|
+
Set up the destination services and data pipeines using ComposioAppName class.
|
33
|
+
"""
|
34
|
+
if self.destination_services is not None:
|
35
|
+
results = []
|
36
|
+
for item in self.destination_services:
|
37
|
+
if isinstance(item, ComposioAppName):
|
38
|
+
results.append(item)
|
39
|
+
elif isinstance(item, str) and item in ComposioAppName:
|
40
|
+
results.append(ComposioAppName(item))
|
41
|
+
else:
|
42
|
+
results.append(item)
|
43
|
+
self.destination_services = results
|
44
|
+
|
45
|
+
if self.data_pipelines is not None:
|
46
|
+
results = []
|
47
|
+
for item in self.data_pipelines:
|
48
|
+
if isinstance(item, ComposioAppName):
|
49
|
+
results.append(item)
|
50
|
+
elif isinstance(item, str) and item in ComposioAppName:
|
51
|
+
results.append(ComposioAppName(item))
|
52
|
+
else:
|
53
|
+
results.append(item)
|
54
|
+
self.data_pipelines = results
|
55
|
+
|
56
|
+
return self
|
57
|
+
|
58
|
+
|
29
59
|
|
30
60
|
class Product(BaseModel):
|
31
61
|
"""
|
32
|
-
|
62
|
+
A class to store product information used to create outbound
|
33
63
|
"""
|
34
|
-
|
35
64
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
36
65
|
name: Optional[str] = Field(default=None, description="product name")
|
37
66
|
description: Optional[str] = Field(
|
@@ -44,6 +73,7 @@ class Product(BaseModel):
|
|
44
73
|
usp: Optional[str] = Field(default=None)
|
45
74
|
landing_url: Optional[str] = Field(default=None, description="marketing url of the product if any")
|
46
75
|
cohort_timeframe: Optional[int] = Field(default=30, description="ideal cohort timeframe of the product in days")
|
76
|
+
notes: Optional[str] = Field(default=None, description="any notes from the client to consider. cascade to the agent")
|
47
77
|
|
48
78
|
|
49
79
|
@field_validator("id", mode="before")
|
@@ -51,3 +81,10 @@ class Product(BaseModel):
|
|
51
81
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
52
82
|
if v:
|
53
83
|
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
84
|
+
|
85
|
+
|
86
|
+
@field_validator("cohort_timeframe", mode="before")
|
87
|
+
@classmethod
|
88
|
+
def _deny_non_int_input(cls, v: Optional[UUID4]) -> None:
|
89
|
+
if not isinstance(v, int):
|
90
|
+
raise PydanticCustomError("invalid_input", "This field only accepts inputs in integer.", {})
|
@@ -96,7 +96,7 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
96
96
|
_created_at: Optional[datetime]
|
97
97
|
_updated_at: Optional[datetime]
|
98
98
|
|
99
|
-
model_config = ConfigDict()
|
99
|
+
model_config = ConfigDict(extra="allow")
|
100
100
|
|
101
101
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
102
102
|
messaging_components: List[MessagingComponent] = Field(default_factory=list, description="store messaging components in the workflow")
|
@@ -121,15 +121,21 @@ class MessagingWorkflow(ABC, BaseModel):
|
|
121
121
|
@model_validator(mode="after")
|
122
122
|
def set_up_destination(self):
|
123
123
|
"""
|
124
|
-
Set up the destination service
|
125
|
-
Prioritize customer's destination to the product provider's destination list.
|
124
|
+
Set up the destination service using ComposioAppName class.
|
126
125
|
"""
|
127
|
-
if self.destination
|
126
|
+
if isinstance(self.destination, ComposioAppName):
|
127
|
+
pass
|
128
|
+
|
129
|
+
elif isinstance(self.destination, str) and self.destination in ComposioAppName:
|
130
|
+
self.destination = ComposioAppName(self.destination)
|
131
|
+
|
132
|
+
elif self.destination is None:
|
128
133
|
# if self.customer is not None:
|
129
134
|
# self.destination = self.customer.on
|
130
135
|
|
131
136
|
if self.product.provider is not None and self.product.provider.destination_services:
|
132
|
-
|
137
|
+
applied_service = self.product.provider.destination_services[0]
|
138
|
+
self.destination = ComposioAppName(applied_service) if applied_service in ComposioAppName else applied_service
|
133
139
|
|
134
140
|
return self
|
135
141
|
|
@@ -78,20 +78,11 @@ class TaskOutput(BaseModel):
|
|
78
78
|
return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
|
79
79
|
|
80
80
|
|
81
|
-
def to_dict(self) -> Dict[str, Any]:
|
81
|
+
def to_dict(self) -> Dict[str, Any] | None:
|
82
82
|
"""
|
83
83
|
Convert pydantic / raw output into dict and return the dict.
|
84
|
-
When we only have `raw` output, return `{ output: raw }` to avoid an error
|
85
84
|
"""
|
86
|
-
|
87
|
-
output_dict = {}
|
88
|
-
if self.json_dict:
|
89
|
-
output_dict.update(self.json_dict)
|
90
|
-
elif self.pydantic:
|
91
|
-
output_dict.update(self.pydantic.model_dump())
|
92
|
-
else:
|
93
|
-
output_dict.upate({ "output": self.raw })
|
94
|
-
return output_dict
|
85
|
+
return self.json_dict if self.json_dict is not None else self.pydantic.model_dump() if self.pydantic else None
|
95
86
|
|
96
87
|
|
97
88
|
def context_prompting(self) -> str:
|
@@ -245,36 +236,29 @@ class Task(BaseModel):
|
|
245
236
|
return "\n".join(task_slices)
|
246
237
|
|
247
238
|
|
248
|
-
def
|
239
|
+
def _create_json_output(self, raw_result: str) -> Dict[str, Any]:
|
249
240
|
"""
|
250
241
|
Create json (dict) output from the raw result.
|
251
242
|
"""
|
243
|
+
import ast
|
252
244
|
|
253
|
-
output_json_dict:
|
245
|
+
output_json_dict: Dict[str, Any] = dict()
|
254
246
|
|
255
|
-
|
256
|
-
|
247
|
+
try:
|
248
|
+
raw_result = raw_result.replace("{'", '{"').replace("': '", '": "').replace("'}", '"}').replace("', '", '", "').replace("['", '["').replace("']", '"]')
|
249
|
+
r = json.dumps(eval(str(raw_result)))
|
250
|
+
output_json_dict = json.loads(r)
|
257
251
|
|
258
|
-
|
259
|
-
|
252
|
+
if isinstance(output_json_dict, str):
|
253
|
+
output_json_dict = ast.literal_eval(r)
|
260
254
|
|
261
|
-
|
262
|
-
|
263
|
-
output_json_dict = json.loads(raw_result)
|
264
|
-
except json.JSONDecodeError:
|
265
|
-
try:
|
266
|
-
output_json_dict = eval(raw_result)
|
267
|
-
except:
|
268
|
-
try:
|
269
|
-
import ast
|
270
|
-
output_json_dict = ast.literal_eval(raw_result)
|
271
|
-
except:
|
272
|
-
output_json_dict = { "output": raw_result }
|
255
|
+
except:
|
256
|
+
output_json_dict = { "output": raw_result }
|
273
257
|
|
274
258
|
return output_json_dict
|
275
259
|
|
276
260
|
|
277
|
-
def
|
261
|
+
def _create_pydantic_output(self, output_json_dict: Dict[str, Any], raw_result: Any = None) -> Optional[Any]:
|
278
262
|
"""
|
279
263
|
Create pydantic output from the `raw` result.
|
280
264
|
"""
|
@@ -379,18 +363,18 @@ class Task(BaseModel):
|
|
379
363
|
agent = agent_to_delegate
|
380
364
|
self.delegations += 1
|
381
365
|
|
382
|
-
if self.take_tool_res_as_final
|
366
|
+
if self.take_tool_res_as_final == True:
|
383
367
|
output = agent.execute_task(task=self, context=context)
|
384
368
|
task_output = TaskOutput(task_id=self.id, tool_output=output)
|
385
369
|
|
386
370
|
else:
|
387
371
|
output_raw, output_json_dict, output_pydantic = agent.execute_task(task=self, context=context), None, None
|
388
372
|
|
389
|
-
if self.expected_output_json:
|
390
|
-
output_json_dict = self.
|
373
|
+
if self.expected_output_json == True:
|
374
|
+
output_json_dict = self._create_json_output(raw_result=output_raw)
|
391
375
|
|
392
|
-
if self.expected_output_pydantic:
|
393
|
-
output_pydantic = self.
|
376
|
+
if self.expected_output_pydantic == True:
|
377
|
+
output_pydantic = self._create_pydantic_output(output_json_dict=output_json_dict)
|
394
378
|
|
395
379
|
task_output = TaskOutput(
|
396
380
|
task_id=self.id,
|
@@ -441,7 +425,7 @@ class Task(BaseModel):
|
|
441
425
|
output_formats_to_follow[item.title] = f"<Return your answer in {item.type.__name__}>"
|
442
426
|
|
443
427
|
output_prompt = f"""
|
444
|
-
|
428
|
+
Output only valid JSON conforming to the specified format. Use double quotes for keys and values:
|
445
429
|
{output_formats_to_follow}
|
446
430
|
"""
|
447
431
|
return output_prompt
|
@@ -174,40 +174,6 @@ class Tool(BaseTool):
|
|
174
174
|
return f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nGoal: {self.goal}"
|
175
175
|
|
176
176
|
|
177
|
-
|
178
|
-
# @classmethod
|
179
|
-
# def from_composio(
|
180
|
-
# cls, func: Callable = None, tool_name: str = "Composio tool"
|
181
|
-
# ) -> "Tool":
|
182
|
-
# """
|
183
|
-
# Create Tool from the composio tool, ensuring the Tool instance has a func to be executed.
|
184
|
-
# Refer to the `args_schema` from the func signature if any. Else, create an `args_schema`.
|
185
|
-
# """
|
186
|
-
|
187
|
-
# if not func:
|
188
|
-
# raise ValueError("Params must have a callable 'function' attribute.")
|
189
|
-
|
190
|
-
# # args_schema = getattr(tool, "args_schema", None)
|
191
|
-
# args_fields = {}
|
192
|
-
# func_signature = signature(func)
|
193
|
-
# annotations = func_signature.parameters
|
194
|
-
# for name, param in annotations.items():
|
195
|
-
# if name != "self":
|
196
|
-
# param_annotation = (
|
197
|
-
# param.annotation if param.annotation != param.empty else Any
|
198
|
-
# )
|
199
|
-
# field_info = Field(default=..., description="")
|
200
|
-
# args_fields[name] = (param_annotation, field_info)
|
201
|
-
|
202
|
-
# args_schema = (
|
203
|
-
# create_model(f"{tool_name}Input", **args_fields)
|
204
|
-
# if args_fields
|
205
|
-
# else create_model(f"{tool_name}Input", __base__=BaseModel)
|
206
|
-
# )
|
207
|
-
|
208
|
-
# return cls(name=tool_name, func=func, args_schema=args_schema)
|
209
|
-
|
210
|
-
|
211
177
|
class ToolSet(BaseModel):
|
212
178
|
"""
|
213
179
|
Store the tool called and any kwargs used.
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.9.
|
3
|
+
Version: 1.1.9.10
|
4
4
|
Summary: LLM orchestration frameworks for model-agnostic AI agents that handle complex outbound workflows
|
5
5
|
Author-email: Kuriko Iwai <kuriko@versi0n.io>
|
6
6
|
License: MIT License
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import os
|
2
|
+
import pytest
|
3
|
+
import datetime
|
4
|
+
from typing import List, Dict, Any, Optional, Callable
|
5
|
+
|
6
|
+
from versionhq.clients.product.model import Product, ProductProvider
|
7
|
+
from versionhq.clients.customer.model import Customer, Status
|
8
|
+
from versionhq.tool import ComposioAppName
|
9
|
+
|
10
|
+
|
11
|
+
def test_create_customer():
|
12
|
+
provider = ProductProvider(name="demo", region="US", data_pipelines=["hubspot", "demo crm"], destination_services=["test"])
|
13
|
+
product = Product(name="demo", description="demo", provider=provider, landing_url="www.com", cohort_timeframe=30)
|
14
|
+
|
15
|
+
def custom_deploy(item):
|
16
|
+
return f"custom deploy with {item}"
|
17
|
+
|
18
|
+
class CustomCustomer(Customer):
|
19
|
+
name: str = "custom"
|
20
|
+
staus: Status = Status.READY_TO_DEPLOY
|
21
|
+
products: List[Product] = [product,]
|
22
|
+
analysis: str = "analysis"
|
23
|
+
function: Optional[Callable] = custom_deploy
|
24
|
+
config: Optional[Dict[str, Any]] = dict(item="custom config")
|
25
|
+
|
26
|
+
customer = CustomCustomer()
|
27
|
+
|
28
|
+
assert customer.id is not None
|
29
|
+
assert customer.deploy() == "custom deploy with custom config"
|
30
|
+
assert customer.status == Status.ACTIVE_ON_WORKFLOW
|
31
|
+
assert [isinstance(item, Product) for item in customer.products]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import os
|
2
|
+
import pytest
|
3
|
+
import datetime
|
4
|
+
|
5
|
+
from versionhq.clients.workflow.model import Score, ScoreFormat, MessagingWorkflow, MessagingComponent
|
6
|
+
from versionhq.clients.product.model import Product, ProductProvider
|
7
|
+
from versionhq.tool import ComposioAppName
|
8
|
+
|
9
|
+
|
10
|
+
def test_create_product_provider():
|
11
|
+
provider = ProductProvider(name="demo", region="US", data_pipelines=["hubspot", "demo crm"] )
|
12
|
+
|
13
|
+
assert provider.id is not None
|
14
|
+
assert provider.name == "demo"
|
15
|
+
assert isinstance(provider.data_pipelines[0], ComposioAppName)
|
16
|
+
assert isinstance(provider.data_pipelines[1], str)
|
17
|
+
assert provider.destination_services is None
|
18
|
+
|
19
|
+
|
20
|
+
def test_create_product():
|
21
|
+
provider = ProductProvider(name="demo", region="US", data_pipelines=["hubspot", "demo crm"], destination_services=["test"])
|
22
|
+
product = Product(name="demo", description="demo", provider=provider, audience="demo", usp="demo", landing_url="www.com", cohort_timeframe=30)
|
23
|
+
|
24
|
+
assert product.id is not None
|
25
|
+
assert product.name == "demo"
|
26
|
+
assert product.provider.id is not None
|
@@ -4,6 +4,7 @@ import datetime
|
|
4
4
|
|
5
5
|
from versionhq.clients.workflow.model import Score, ScoreFormat, MessagingWorkflow, MessagingComponent
|
6
6
|
from versionhq.clients.product.model import Product, ProductProvider
|
7
|
+
from versionhq.tool import ComposioAppName
|
7
8
|
|
8
9
|
|
9
10
|
def test_store_scores():
|
@@ -44,7 +45,7 @@ def test_setup_messaging_workflow_with_provider():
|
|
44
45
|
name="demo provider",
|
45
46
|
region="US",
|
46
47
|
data_pipelines=["data"],
|
47
|
-
destination_services=["
|
48
|
+
destination_services=["linkedin", "email",]
|
48
49
|
)
|
49
50
|
product = Product(
|
50
51
|
description="demo p",
|
@@ -60,3 +61,4 @@ def test_setup_messaging_workflow_with_provider():
|
|
60
61
|
assert messaging_workflow.id is not None
|
61
62
|
assert messaging_workflow.destination is not None
|
62
63
|
assert messaging_workflow.destination in provider.destination_services
|
64
|
+
assert isinstance(messaging_workflow.destination, ComposioAppName)
|
@@ -52,19 +52,13 @@ def test_sync_execute_task():
|
|
52
52
|
|
53
53
|
|
54
54
|
def test_async_execute_task():
|
55
|
-
agent = Agent(role="demo agent
|
56
|
-
task = Task(
|
57
|
-
description="Analyze the client's business model and define the optimal cohort timeframe.",
|
58
|
-
output_field_list=[
|
59
|
-
ResponseField(title="test1", type=str, required=True),
|
60
|
-
ResponseField(title="test2", type=list, required=True),
|
61
|
-
],
|
62
|
-
)
|
55
|
+
agent = Agent(role="demo agent", goal="My amazing goals")
|
56
|
+
task = Task(description="Return string: 'test'")
|
63
57
|
|
64
|
-
with patch.object(Agent, "execute_task", return_value="
|
58
|
+
with patch.object(Agent, "execute_task", return_value="test") as execute:
|
65
59
|
execution = task.execute_async(agent=agent)
|
66
60
|
result = execution.result()
|
67
|
-
assert result.raw == "
|
61
|
+
assert result.raw == "test"
|
68
62
|
execute.assert_called_once_with(task=task, context=None)
|
69
63
|
|
70
64
|
|
@@ -301,4 +295,30 @@ def test_task_with_tools():
|
|
301
295
|
assert res.tool_output[2] == "empty function"
|
302
296
|
|
303
297
|
|
298
|
+
def test_create_json_dict_output():
|
299
|
+
agent = Agent(role="demo agent 6", goal="My amazing goals")
|
300
|
+
task = Task(
|
301
|
+
description="Analyze the client's business model.",
|
302
|
+
output_field_list=[
|
303
|
+
ResponseField(title="str", type=str, required=True),
|
304
|
+
ResponseField(title="list", type=list),
|
305
|
+
ResponseField(title="dict", type=dict)
|
306
|
+
],
|
307
|
+
)
|
308
|
+
|
309
|
+
res = task.execute_sync(agent=agent)
|
310
|
+
|
311
|
+
assert isinstance(res, TaskOutput)
|
312
|
+
assert isinstance(res.json_dict, dict)
|
313
|
+
assert [k == "str" for k, v in res.json_dict.items()]
|
314
|
+
assert [k == "list" for k, v in res.json_dict.items()]
|
315
|
+
assert [k == "dict" for k, v in res.json_dict.items()]
|
316
|
+
assert isinstance(res.json_dict["str"], str)
|
317
|
+
assert isinstance(res.json_dict["list"], list)
|
318
|
+
assert isinstance(res.json_dict["dict"], dict)
|
319
|
+
|
320
|
+
|
304
321
|
# token usage logic
|
322
|
+
|
323
|
+
if __name__ == "__main__":
|
324
|
+
test_create_json_dict_output()
|
@@ -853,14 +853,14 @@ wheels = [
|
|
853
853
|
|
854
854
|
[[package]]
|
855
855
|
name = "langchain-text-splitters"
|
856
|
-
version = "0.3.
|
856
|
+
version = "0.3.5"
|
857
857
|
source = { registry = "https://pypi.org/simple" }
|
858
858
|
dependencies = [
|
859
859
|
{ name = "langchain-core" },
|
860
860
|
]
|
861
|
-
sdist = { url = "https://files.pythonhosted.org/packages/10/
|
861
|
+
sdist = { url = "https://files.pythonhosted.org/packages/10/35/a6f8d6b1bb0e6e8c00b49bce4d1a115f8b68368b1899f65bb34dbbb44160/langchain_text_splitters-0.3.5.tar.gz", hash = "sha256:11cb7ca3694e5bdd342bc16d3875b7f7381651d4a53cbb91d34f22412ae16443", size = 26318 }
|
862
862
|
wheels = [
|
863
|
-
{ url = "https://files.pythonhosted.org/packages/
|
863
|
+
{ url = "https://files.pythonhosted.org/packages/4b/83/f8081c3bea416bd9d9f0c26af795c74f42c24f9ad3c4fbf361b7d69de134/langchain_text_splitters-0.3.5-py3-none-any.whl", hash = "sha256:8c9b059827438c5fa8f327b4df857e307828a5ec815163c9b5c9569a3e82c8ee", size = 31620 },
|
864
864
|
]
|
865
865
|
|
866
866
|
[[package]]
|
@@ -896,7 +896,7 @@ wheels = [
|
|
896
896
|
|
897
897
|
[[package]]
|
898
898
|
name = "litellm"
|
899
|
-
version = "1.57.
|
899
|
+
version = "1.57.2"
|
900
900
|
source = { registry = "https://pypi.org/simple" }
|
901
901
|
dependencies = [
|
902
902
|
{ name = "aiohttp" },
|
@@ -911,9 +911,9 @@ dependencies = [
|
|
911
911
|
{ name = "tiktoken" },
|
912
912
|
{ name = "tokenizers" },
|
913
913
|
]
|
914
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
914
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d4/1c/e5669149f8f1d57103e97c28a8f06fc9e111370d4b6d0b4dfc76f4a11fe4/litellm-1.57.2.tar.gz", hash = "sha256:0a07c4e288f4bd9033335d5606d7da497f1193d51cf262b96812f40b8842a842", size = 6299758 }
|
915
915
|
wheels = [
|
916
|
-
{ url = "https://files.pythonhosted.org/packages/
|
916
|
+
{ url = "https://files.pythonhosted.org/packages/9a/46/d96a6902c3529f96f8ea1fc2ba08fd48b50652141ddce9bf8faf0acdbbe6/litellm-1.57.2-py3-none-any.whl", hash = "sha256:b572c0d3d3c33ff3a4d18928ac6f051d10ac159814017a817d88ec7af9a8600c", size = 6582805 },
|
917
917
|
]
|
918
918
|
|
919
919
|
[[package]]
|
@@ -1137,7 +1137,7 @@ wheels = [
|
|
1137
1137
|
|
1138
1138
|
[[package]]
|
1139
1139
|
name = "openai"
|
1140
|
-
version = "1.59.
|
1140
|
+
version = "1.59.4"
|
1141
1141
|
source = { registry = "https://pypi.org/simple" }
|
1142
1142
|
dependencies = [
|
1143
1143
|
{ name = "anyio" },
|
@@ -1149,9 +1149,9 @@ dependencies = [
|
|
1149
1149
|
{ name = "tqdm" },
|
1150
1150
|
{ name = "typing-extensions" },
|
1151
1151
|
]
|
1152
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
1152
|
+
sdist = { url = "https://files.pythonhosted.org/packages/38/db/0e1376bdee3de8c16d91647d47dc47a26d2d6036931c76844e7d3e3fb989/openai-1.59.4.tar.gz", hash = "sha256:b946dc5a2308dc1e03efbda80bf1cd64b6053b536851ad519f57ee44401663d2", size = 344405 }
|
1153
1153
|
wheels = [
|
1154
|
-
{ url = "https://files.pythonhosted.org/packages/
|
1154
|
+
{ url = "https://files.pythonhosted.org/packages/99/01/1eefc235bb79174826b2fa0cad05bc2eab90eae97bf78c765887d7430e46/openai-1.59.4-py3-none-any.whl", hash = "sha256:82113498699998e98104f87c19a890e82df9b01251a0395484360575d3a1d98a", size = 454810 },
|
1155
1155
|
]
|
1156
1156
|
|
1157
1157
|
[[package]]
|
@@ -1964,7 +1964,7 @@ wheels = [
|
|
1964
1964
|
|
1965
1965
|
[[package]]
|
1966
1966
|
name = "versionhq"
|
1967
|
-
version = "1.1.9.
|
1967
|
+
version = "1.1.9.10"
|
1968
1968
|
source = { editable = "." }
|
1969
1969
|
dependencies = [
|
1970
1970
|
{ name = "appdirs" },
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import pytest
|
3
|
-
import datetime
|
4
|
-
|
5
|
-
from versionhq.clients.workflow.model import Score, ScoreFormat, MessagingWorkflow, MessagingComponent
|
6
|
-
from versionhq.clients.product.model import Product, ProductProvider
|
7
|
-
|
8
|
-
|
9
|
-
def test_create_product():
|
10
|
-
provider = ProductProvider()
|
11
|
-
product = Product()
|
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
|
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
|
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
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|