versionhq 1.1.6.4__py3-none-any.whl → 1.1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- versionhq/__init__.py +1 -1
- versionhq/_utils/usage_metrics.py +4 -12
- versionhq/agent/TEMPLATES/Backstory.py +7 -0
- versionhq/agent/TEMPLATES/__init__.py +0 -0
- versionhq/agent/model.py +94 -157
- versionhq/task/model.py +122 -150
- versionhq/team/model.py +70 -145
- versionhq/team/team_planner.py +2 -2
- versionhq/tool/model.py +6 -16
- {versionhq-1.1.6.4.dist-info → versionhq-1.1.7.0.dist-info}/METADATA +14 -9
- {versionhq-1.1.6.4.dist-info → versionhq-1.1.7.0.dist-info}/RECORD +14 -12
- {versionhq-1.1.6.4.dist-info → versionhq-1.1.7.0.dist-info}/LICENSE +0 -0
- {versionhq-1.1.6.4.dist-info → versionhq-1.1.7.0.dist-info}/WHEEL +0 -0
- {versionhq-1.1.6.4.dist-info → versionhq-1.1.7.0.dist-info}/top_level.txt +0 -0
versionhq/task/model.py
CHANGED
@@ -3,16 +3,10 @@ import threading
|
|
3
3
|
import uuid
|
4
4
|
from concurrent.futures import Future
|
5
5
|
from hashlib import md5
|
6
|
-
from typing import Any, Dict, List, Set, Optional, Tuple, Callable
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
BaseModel,
|
11
|
-
Field,
|
12
|
-
PrivateAttr,
|
13
|
-
field_validator,
|
14
|
-
model_validator,
|
15
|
-
)
|
6
|
+
from typing import Any, Dict, List, Set, Optional, Tuple, Callable, Union, Type
|
7
|
+
from typing_extensions import Annotated
|
8
|
+
|
9
|
+
from pydantic import UUID4, BaseModel, Field, PrivateAttr, field_validator, model_validator, create_model
|
16
10
|
from pydantic_core import PydanticCustomError
|
17
11
|
|
18
12
|
from versionhq._utils.process_config import process_config
|
@@ -24,11 +18,36 @@ class ResponseField(BaseModel):
|
|
24
18
|
"""
|
25
19
|
Field class to use in the response schema for the JSON response.
|
26
20
|
"""
|
27
|
-
|
28
21
|
title: str = Field(default=None)
|
29
|
-
type:
|
22
|
+
type: Type = Field(default=str)
|
30
23
|
required: bool = Field(default=True)
|
31
24
|
|
25
|
+
def _annotate(self, value: Any) -> Annotated:
|
26
|
+
"""
|
27
|
+
Address `create_model`
|
28
|
+
"""
|
29
|
+
return Annotated[self.type, value] if isinstance(value, self.type) else Annotated[str, str(value)]
|
30
|
+
|
31
|
+
def create_pydantic_model(self, result: Dict, base_model: Union[BaseModel | Any]) -> Any:
|
32
|
+
for k, v in result.items():
|
33
|
+
if k is not self.title or type(v) is not self.type:
|
34
|
+
pass
|
35
|
+
setattr(base_model, k, v)
|
36
|
+
return base_model
|
37
|
+
|
38
|
+
|
39
|
+
class AgentOutput(BaseModel):
|
40
|
+
"""
|
41
|
+
Keep adding agents' learning and recommendation and store it in `pydantic` field of `TaskOutput` class.
|
42
|
+
Since the TaskOutput class has `agent` field, we don't add any info on the agent that handled the task.
|
43
|
+
"""
|
44
|
+
customer_id: str = Field(default=None, max_length=126, description="customer uuid")
|
45
|
+
customer_analysis: str = Field(default=None, max_length=256, description="analysis of the customer")
|
46
|
+
business_overview: str = Field(default=None,max_length=256,description="analysis of the client's business")
|
47
|
+
cohort_timeframe: int = Field(default=None,max_length=256,description="Suitable cohort timeframe in days")
|
48
|
+
kpi_metrics: List[str] = Field(default=list, description="Ideal KPIs to be tracked")
|
49
|
+
assumptions: List[Dict[str, Any]] = Field(default=list, description="assumptions to test")
|
50
|
+
|
32
51
|
|
33
52
|
class TaskOutput(BaseModel):
|
34
53
|
"""
|
@@ -36,52 +55,13 @@ class TaskOutput(BaseModel):
|
|
36
55
|
Depending on the task output format, use `raw`, `pydantic`, `json_dict` accordingly.
|
37
56
|
"""
|
38
57
|
|
39
|
-
|
40
|
-
"""
|
41
|
-
Keep adding agents' learning and recommendation and store it in `pydantic` field of `TaskOutput` class.
|
42
|
-
Since the TaskOutput class has `agent` field, we don't add any info on the agent that handled the task.
|
43
|
-
"""
|
44
|
-
|
45
|
-
customer_id: str = Field(
|
46
|
-
default=None, max_length=126, description="customer uuid"
|
47
|
-
)
|
48
|
-
customer_analysis: str = Field(
|
49
|
-
default=None, max_length=256, description="analysis of the customer"
|
50
|
-
)
|
51
|
-
business_overview: str = Field(
|
52
|
-
default=None,
|
53
|
-
max_length=256,
|
54
|
-
description="analysis of the client's business",
|
55
|
-
)
|
56
|
-
cohort_timeframe: int = Field(
|
57
|
-
default=None,
|
58
|
-
max_length=256,
|
59
|
-
description="Suitable cohort timeframe in days",
|
60
|
-
)
|
61
|
-
kpi_metrics: List[str] = Field(
|
62
|
-
default=list, description="Ideal KPIs to be tracked"
|
63
|
-
)
|
64
|
-
assumptions: List[Dict[str, Any]] = Field(
|
65
|
-
default=list, description="assumptions to test"
|
66
|
-
)
|
67
|
-
|
68
|
-
task_id: UUID4 = Field(
|
69
|
-
default_factory=uuid.uuid4, frozen=True, description="store Task ID"
|
70
|
-
)
|
58
|
+
task_id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="store Task ID")
|
71
59
|
raw: str = Field(default="", description="Raw output of the task")
|
72
|
-
pydantic: Optional[
|
73
|
-
|
74
|
-
)
|
75
|
-
json_dict: Optional[Dict[str, Any]] = Field(
|
76
|
-
default=None, description="JSON dictionary of task"
|
77
|
-
)
|
60
|
+
pydantic: Optional[Any] = Field(default=None, description="`raw` converted to the abs. pydantic model")
|
61
|
+
json_dict: Union[Dict[str, Any]] = Field(default=None, description="`raw` converted to dictionary")
|
78
62
|
|
79
63
|
def __str__(self) -> str:
|
80
|
-
return (
|
81
|
-
str(self.pydantic)
|
82
|
-
if self.pydantic
|
83
|
-
else str(self.json_dict) if self.json_dict else self.raw
|
84
|
-
)
|
64
|
+
return str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw
|
85
65
|
|
86
66
|
@property
|
87
67
|
def json(self) -> Optional[str]:
|
@@ -114,51 +94,26 @@ class Task(BaseModel):
|
|
114
94
|
|
115
95
|
__hash__ = object.__hash__
|
116
96
|
|
117
|
-
id: UUID4 = Field(
|
118
|
-
default_factory=uuid.uuid4,
|
119
|
-
frozen=True,
|
120
|
-
description="unique identifier for the object, not set by user",
|
121
|
-
)
|
97
|
+
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True, description="unique identifier for the object, not set by user")
|
122
98
|
name: Optional[str] = Field(default=None)
|
123
99
|
description: str = Field(description="Description of the actual task")
|
124
100
|
_original_description: str = PrivateAttr(default=None)
|
125
101
|
|
126
102
|
# output
|
127
|
-
expected_output_raw: bool = Field(default=False)
|
128
103
|
expected_output_json: bool = Field(default=True)
|
129
104
|
expected_output_pydantic: bool = Field(default=False)
|
130
|
-
output_field_list: Optional[List[ResponseField]] = Field(
|
131
|
-
|
132
|
-
ResponseField(title="output", type="str", required=True),
|
133
|
-
]
|
134
|
-
)
|
135
|
-
output: Optional[TaskOutput] = Field(
|
136
|
-
default=None, description="store the final task output in TaskOutput class"
|
137
|
-
)
|
105
|
+
output_field_list: Optional[List[ResponseField]] = Field(default=[ResponseField(title="output")])
|
106
|
+
output: Optional[TaskOutput] = Field(default=None, description="store the final task output in TaskOutput class")
|
138
107
|
|
139
108
|
# task setup
|
140
|
-
context: Optional[List["Task"]] = Field(
|
141
|
-
|
142
|
-
)
|
143
|
-
tools_called: Optional[List[ToolCalled]] = Field(
|
144
|
-
default_factory=list, description="tools that the agent can use for this task"
|
145
|
-
)
|
146
|
-
take_tool_res_as_final: bool = Field(
|
147
|
-
default=False,
|
148
|
-
description="when set True, tools res will be stored in the `TaskOutput`",
|
149
|
-
)
|
109
|
+
context: Optional[List["Task"]] = Field(default=None, description="other tasks whose outputs should be used as context")
|
110
|
+
tools_called: Optional[List[ToolCalled]] = Field(default_factory=list, description="tools that the agent can use for this task")
|
111
|
+
take_tool_res_as_final: bool = Field(default=False,description="when set True, tools res will be stored in the `TaskOutput`")
|
150
112
|
|
151
113
|
prompt_context: Optional[str] = None
|
152
|
-
async_execution: bool = Field(
|
153
|
-
|
154
|
-
|
155
|
-
)
|
156
|
-
config: Optional[Dict[str, Any]] = Field(
|
157
|
-
default=None, description="configuration for the agent"
|
158
|
-
)
|
159
|
-
callback: Optional[Any] = Field(
|
160
|
-
default=None, description="callback to be executed after the task is completed."
|
161
|
-
)
|
114
|
+
async_execution: bool = Field(default=False,description="whether the task should be executed asynchronously or not")
|
115
|
+
config: Optional[Dict[str, Any]] = Field(default=None, description="configuration for the agent")
|
116
|
+
callback: Optional[Any] = Field(default=None, description="callback to be executed after the task is completed.")
|
162
117
|
|
163
118
|
# recording
|
164
119
|
processed_by_agents: Set[str] = Field(default_factory=set)
|
@@ -166,6 +121,7 @@ class Task(BaseModel):
|
|
166
121
|
tools_errors: int = 0
|
167
122
|
delegations: int = 0
|
168
123
|
|
124
|
+
|
169
125
|
@property
|
170
126
|
def output_prompt(self):
|
171
127
|
"""
|
@@ -174,25 +130,29 @@ class Task(BaseModel):
|
|
174
130
|
|
175
131
|
output_prompt, output_dict = "", dict()
|
176
132
|
for item in self.output_field_list:
|
177
|
-
output_dict[item.title] = f"your answer in {item.type}"
|
133
|
+
output_dict[item.title] = f"<Return your answer in {item.type.__name__}>"
|
178
134
|
|
179
135
|
output_prompt = f"""
|
180
|
-
|
136
|
+
Your outputs STRICTLY follow the following format and should NOT contain any other irrevant elements that not specified in the following format:
|
181
137
|
{output_dict}
|
182
138
|
"""
|
183
139
|
return output_prompt
|
184
140
|
|
141
|
+
|
185
142
|
@property
|
186
143
|
def expected_output_formats(self) -> List[TaskOutputFormat]:
|
187
|
-
|
144
|
+
"""
|
145
|
+
Return output formats in list with the ENUM item.
|
146
|
+
`TaskOutputFormat.RAW` is set as default.
|
147
|
+
"""
|
148
|
+
outputs = [TaskOutputFormat.RAW,]
|
188
149
|
if self.expected_output_json:
|
189
150
|
outputs.append(TaskOutputFormat.JSON)
|
190
151
|
if self.expected_output_pydantic:
|
191
152
|
outputs.append(TaskOutputFormat.PYDANTIC)
|
192
|
-
if self.expected_output_raw:
|
193
|
-
outputs.append(TaskOutputFormat.RAW)
|
194
153
|
return outputs
|
195
154
|
|
155
|
+
|
196
156
|
@property
|
197
157
|
def key(self) -> str:
|
198
158
|
output_format = (
|
@@ -207,6 +167,7 @@ class Task(BaseModel):
|
|
207
167
|
source = [self.description, output_format]
|
208
168
|
return md5("|".join(source).encode(), usedforsecurity=False).hexdigest()
|
209
169
|
|
170
|
+
|
210
171
|
@property
|
211
172
|
def summary(self) -> str:
|
212
173
|
return f"""
|
@@ -216,32 +177,30 @@ class Task(BaseModel):
|
|
216
177
|
"task_tools": {", ".join([tool_called.tool.name for tool_called in self.tools_called])}
|
217
178
|
"""
|
218
179
|
|
180
|
+
|
219
181
|
# validators
|
220
182
|
@model_validator(mode="before")
|
221
183
|
@classmethod
|
222
184
|
def process_model_config(cls, values: Dict[str, Any]):
|
223
185
|
return process_config(values_to_update=values, model_class=cls)
|
224
186
|
|
187
|
+
|
225
188
|
@field_validator("id", mode="before")
|
226
189
|
@classmethod
|
227
190
|
def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
|
228
191
|
if v:
|
229
|
-
raise PydanticCustomError(
|
230
|
-
|
231
|
-
)
|
192
|
+
raise PydanticCustomError("may_not_set_field", "This field is not to be set by the user.", {})
|
193
|
+
|
232
194
|
|
233
195
|
@model_validator(mode="after")
|
234
196
|
def validate_required_fields(self):
|
235
|
-
required_fields = [
|
236
|
-
"description",
|
237
|
-
]
|
197
|
+
required_fields = ["description",]
|
238
198
|
for field in required_fields:
|
239
199
|
if getattr(self, field) is None:
|
240
|
-
raise ValueError(
|
241
|
-
f"{field} must be provided either directly or through config"
|
242
|
-
)
|
200
|
+
raise ValueError( f"{field} must be provided either directly or through config")
|
243
201
|
return self
|
244
202
|
|
203
|
+
|
245
204
|
@model_validator(mode="after")
|
246
205
|
def set_attributes_based_on_config(self) -> "Task":
|
247
206
|
"""
|
@@ -253,15 +212,14 @@ class Task(BaseModel):
|
|
253
212
|
setattr(self, key, value)
|
254
213
|
return self
|
255
214
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
return self
|
215
|
+
|
216
|
+
## comment out as we set raw as the default TaskOutputFormat
|
217
|
+
# @model_validator(mode="after")
|
218
|
+
# def validate_output_format(self):
|
219
|
+
# if self.expected_output_json == False and self.expected_output_pydantic == False:
|
220
|
+
# raise PydanticCustomError("Need to choose at least one output format.")
|
221
|
+
# return self
|
222
|
+
|
265
223
|
|
266
224
|
@model_validator(mode="after")
|
267
225
|
def backup_description(self):
|
@@ -269,57 +227,69 @@ class Task(BaseModel):
|
|
269
227
|
self._original_description = self.description
|
270
228
|
return self
|
271
229
|
|
230
|
+
|
272
231
|
def prompt(self, customer=str | None, product_overview=str | None) -> str:
|
273
232
|
"""
|
274
|
-
|
233
|
+
Format the task prompt.
|
275
234
|
"""
|
276
|
-
|
277
235
|
task_slices = [
|
278
236
|
self.description,
|
279
237
|
f"Customer overview: {customer}",
|
280
238
|
f"Product overview: {product_overview}",
|
281
|
-
f"
|
239
|
+
f"{self.output_prompt}",
|
282
240
|
]
|
283
241
|
return "\n".join(task_slices)
|
284
242
|
|
285
|
-
def _export_output(
|
286
|
-
self, result: Any
|
287
|
-
) -> Tuple[Optional[BaseModel], Optional[Dict[str, Any]]]:
|
288
|
-
output_pydantic: Optional[BaseModel] = None
|
289
|
-
output_json: Optional[Dict[str, Any]] = None
|
290
|
-
dict_output = None
|
291
243
|
|
292
|
-
|
244
|
+
def create_json_output(self, raw_result: Any) -> Optional[Dict[str, Any]]:
|
245
|
+
"""
|
246
|
+
Create json (dict) output from the raw result.
|
247
|
+
"""
|
248
|
+
|
249
|
+
output_json_dict: Optional[Dict[str, Any]] = None
|
250
|
+
|
251
|
+
if isinstance(raw_result, BaseModel):
|
252
|
+
output_json_dict = raw_result.model_dump()
|
253
|
+
|
254
|
+
elif isinstance(raw_result, dict):
|
255
|
+
output_json_dict = raw_result
|
256
|
+
|
257
|
+
elif isinstance(raw_result, str):
|
293
258
|
try:
|
294
|
-
|
259
|
+
output_json_dict = json.loads(raw_result)
|
295
260
|
except json.JSONDecodeError:
|
296
261
|
try:
|
297
|
-
|
262
|
+
output_json_dict = eval(raw_result)
|
298
263
|
except:
|
299
264
|
try:
|
300
265
|
import ast
|
301
|
-
|
302
|
-
dict_output = ast.literal_eval(result)
|
266
|
+
output_json_dict = ast.literal_eval(raw_result)
|
303
267
|
except:
|
304
|
-
|
268
|
+
output_json_dict = { "output": raw_result }
|
305
269
|
|
306
|
-
|
307
|
-
if isinstance(result, dict):
|
308
|
-
output_json = result
|
309
|
-
elif isinstance(result, BaseModel):
|
310
|
-
output_json = result.model_dump()
|
311
|
-
else:
|
312
|
-
output_json = dict_output
|
270
|
+
return output_json_dict
|
313
271
|
|
314
|
-
if self.expected_output_pydantic:
|
315
|
-
if isinstance(result, BaseModel):
|
316
|
-
output_pydantic = result
|
317
|
-
elif isinstance(result, dict):
|
318
|
-
output_json = result
|
319
|
-
else:
|
320
|
-
output_pydantic = None
|
321
272
|
|
322
|
-
|
273
|
+
|
274
|
+
def create_pydantic_output(self, output_json_dict: Dict[str, Any], raw_result: Any = None) -> Optional[Any]:
|
275
|
+
"""
|
276
|
+
Create pydantic output from the raw result.
|
277
|
+
"""
|
278
|
+
|
279
|
+
output_pydantic = None #! REFINEME
|
280
|
+
if isinstance(raw_result, BaseModel):
|
281
|
+
output_pydantic = raw_result
|
282
|
+
|
283
|
+
elif hasattr(output_json_dict, "output"):
|
284
|
+
output_pydantic = create_model("PydanticTaskOutput", output=output_json_dict["output"], __base__=BaseModel)
|
285
|
+
|
286
|
+
else:
|
287
|
+
output_pydantic = create_model("PydanticTaskOutput", __base__=BaseModel)
|
288
|
+
for item in self.output_field_list:
|
289
|
+
item.create_pydantic_model(result=output_json_dict, base_model=output_pydantic)
|
290
|
+
|
291
|
+
return output_pydantic
|
292
|
+
|
323
293
|
|
324
294
|
def _get_output_format(self) -> TaskOutputFormat:
|
325
295
|
if self.output_json == True:
|
@@ -328,6 +298,7 @@ class Task(BaseModel):
|
|
328
298
|
return TaskOutputFormat.PYDANTIC
|
329
299
|
return TaskOutputFormat.RAW
|
330
300
|
|
301
|
+
|
331
302
|
def interpolate_inputs(self, inputs: Dict[str, Any]) -> None:
|
332
303
|
"""
|
333
304
|
Interpolate inputs into the task description and expected output.
|
@@ -343,6 +314,7 @@ class Task(BaseModel):
|
|
343
314
|
"""
|
344
315
|
return self._execute_core(agent, context)
|
345
316
|
|
317
|
+
|
346
318
|
def execute_async(self, agent, context: Optional[str] = None) -> Future[TaskOutput]:
|
347
319
|
"""
|
348
320
|
Execute the task asynchronously.
|
@@ -356,26 +328,27 @@ class Task(BaseModel):
|
|
356
328
|
).start()
|
357
329
|
return future
|
358
330
|
|
359
|
-
|
360
|
-
|
361
|
-
) -> None:
|
331
|
+
|
332
|
+
def _execute_task_async(self, agent, context: Optional[str], future: Future[TaskOutput]) -> None:
|
362
333
|
"""Execute the task asynchronously with context handling."""
|
363
334
|
result = self._execute_core(agent, context)
|
364
335
|
future.set_result(result)
|
365
336
|
|
337
|
+
|
366
338
|
def _execute_core(self, agent, context: Optional[str]) -> TaskOutput:
|
367
339
|
"""
|
368
340
|
Run the core execution logic of the task.
|
369
341
|
"""
|
370
342
|
|
371
343
|
self.prompt_context = context
|
372
|
-
|
373
|
-
|
344
|
+
raw_result = agent.execute_task(task=self, context=context)
|
345
|
+
output_json_dict = self.create_json_output(raw_result=raw_result)
|
346
|
+
output_pydantic = self.create_pydantic_output(output_json_dict=output_json_dict)
|
374
347
|
task_output = TaskOutput(
|
375
348
|
task_id=self.id,
|
376
|
-
raw=
|
349
|
+
raw=raw_result,
|
377
350
|
pydantic=output_pydantic,
|
378
|
-
json_dict=
|
351
|
+
json_dict=output_json_dict
|
379
352
|
)
|
380
353
|
self.output = task_output
|
381
354
|
self.processed_by_agents.add(agent.role)
|
@@ -396,7 +369,6 @@ class Task(BaseModel):
|
|
396
369
|
# else pydantic_output.model_dump_json() if pydantic_output else result
|
397
370
|
# )
|
398
371
|
# self._save_file(content)
|
399
|
-
|
400
372
|
return task_output
|
401
373
|
|
402
374
|
|