versionhq 1.1.8__tar.gz → 1.1.8.1__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.
Files changed (72) hide show
  1. {versionhq-1.1.8 → versionhq-1.1.8.1}/PKG-INFO +1 -1
  2. {versionhq-1.1.8 → versionhq-1.1.8.1}/pyproject.toml +1 -1
  3. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/__init__.py +1 -1
  4. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/task/model.py +4 -3
  5. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/team/model.py +112 -51
  6. versionhq-1.1.8.1/src/versionhq/team/team_planner.py +93 -0
  7. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/tool/model.py +0 -1
  8. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq.egg-info/PKG-INFO +1 -1
  9. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/team/team_test.py +120 -7
  10. {versionhq-1.1.8 → versionhq-1.1.8.1}/uv.lock +1 -1
  11. versionhq-1.1.8/src/versionhq/team/team_planner.py +0 -55
  12. {versionhq-1.1.8 → versionhq-1.1.8.1}/.github/workflows/publish.yml +0 -0
  13. {versionhq-1.1.8 → versionhq-1.1.8.1}/.github/workflows/publish_testpypi.yml +0 -0
  14. {versionhq-1.1.8 → versionhq-1.1.8.1}/.github/workflows/run_tests.yml +0 -0
  15. {versionhq-1.1.8 → versionhq-1.1.8.1}/.github/workflows/security_check.yml +0 -0
  16. {versionhq-1.1.8 → versionhq-1.1.8.1}/.gitignore +0 -0
  17. {versionhq-1.1.8 → versionhq-1.1.8.1}/.pre-commit-config.yaml +0 -0
  18. {versionhq-1.1.8 → versionhq-1.1.8.1}/.python-version +0 -0
  19. {versionhq-1.1.8 → versionhq-1.1.8.1}/LICENSE +0 -0
  20. {versionhq-1.1.8 → versionhq-1.1.8.1}/README.md +0 -0
  21. {versionhq-1.1.8 → versionhq-1.1.8.1}/SECURITY.md +0 -0
  22. {versionhq-1.1.8 → versionhq-1.1.8.1}/db/preprocess.py +0 -0
  23. {versionhq-1.1.8 → versionhq-1.1.8.1}/requirements-dev.txt +0 -0
  24. {versionhq-1.1.8 → versionhq-1.1.8.1}/requirements.txt +0 -0
  25. {versionhq-1.1.8 → versionhq-1.1.8.1}/runtime.txt +0 -0
  26. {versionhq-1.1.8 → versionhq-1.1.8.1}/setup.cfg +0 -0
  27. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/__init__.py +0 -0
  28. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/cache_handler.py +0 -0
  29. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/i18n.py +0 -0
  30. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/logger.py +0 -0
  31. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/process_config.py +0 -0
  32. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/rpm_controller.py +0 -0
  33. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/_utils/usage_metrics.py +0 -0
  34. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
  35. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
  36. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/agent/__init__.py +0 -0
  37. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/agent/model.py +0 -0
  38. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/agent/parser.py +0 -0
  39. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/cli/__init__.py +0 -0
  40. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/__init__.py +0 -0
  41. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/customer/__init__.py +0 -0
  42. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/customer/model.py +0 -0
  43. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/product/__init__.py +0 -0
  44. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/product/model.py +0 -0
  45. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/workflow/__init__.py +0 -0
  46. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/clients/workflow/model.py +0 -0
  47. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/llm/__init__.py +0 -0
  48. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/llm/llm_vars.py +0 -0
  49. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/llm/model.py +0 -0
  50. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/storage/__init__.py +0 -0
  51. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/storage/task_output_storage.py +0 -0
  52. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/task/__init__.py +0 -0
  53. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/task/formatter.py +0 -0
  54. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/task/log_handler.py +0 -0
  55. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/team/__init__.py +0 -0
  56. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/tool/__init__.py +0 -0
  57. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/tool/decorator.py +0 -0
  58. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq/tool/tool_handler.py +0 -0
  59. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq.egg-info/SOURCES.txt +0 -0
  60. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq.egg-info/dependency_links.txt +0 -0
  61. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq.egg-info/requires.txt +0 -0
  62. {versionhq-1.1.8 → versionhq-1.1.8.1}/src/versionhq.egg-info/top_level.txt +0 -0
  63. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/__init__.py +0 -0
  64. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/agent/__init__.py +0 -0
  65. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/agent/agent_test.py +0 -0
  66. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/cli/__init__.py +0 -0
  67. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/clients/workflow_test.py +0 -0
  68. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/conftest.py +0 -0
  69. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/task/__init__.py +0 -0
  70. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/task/task_test.py +0 -0
  71. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/team/Prompts/Demo_test.py +0 -0
  72. {versionhq-1.1.8 → versionhq-1.1.8.1}/tests/team/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: versionhq
3
- Version: 1.1.8
3
+ Version: 1.1.8.1
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.8"
18
+ version = "1.1.8.1"
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"
@@ -17,7 +17,7 @@ from versionhq.team.model import Team, TeamOutput
17
17
  from versionhq.tool.model import Tool
18
18
 
19
19
 
20
- __version__ = "1.1.8"
20
+ __version__ = "1.1.8.1"
21
21
  __all__ = [
22
22
  "Agent",
23
23
  "Customer",
@@ -399,7 +399,7 @@ Your outputs MUST adhere to the following format and should NOT include any irre
399
399
  """
400
400
  Run the core execution logic of the task.
401
401
  To speed up the process, when the format is not expected to return, we will skip the conversion process.
402
- When the task is allowed to delegate to another agent, we will select a responsible one in order of manager_agent > peer_agent > anoymous agent.
402
+ When the task is allowed to delegate to another agent, we will select a responsible one in order of manager > peer_agent > anoymous agent.
403
403
  """
404
404
  from versionhq.agent.model import Agent
405
405
  from versionhq.team.model import Team
@@ -410,8 +410,9 @@ Your outputs MUST adhere to the following format and should NOT include any irre
410
410
  agent_to_delegate = None
411
411
 
412
412
  if hasattr(agent, "team") and isinstance(agent.team, Team):
413
- if agent.team.manager_agent:
414
- agent_to_delegate = agent.team.manager_agent
413
+ if agent.team.managers:
414
+ idling_manager_agents = [manager.agent for manager in agent.team.managers if manager.task is None]
415
+ agent_to_delegate = idling_manager_agents[0] if idling_manager_agents else agent.team.managers[0]
415
416
  else:
416
417
  peers = [member.agent for member in agent.team.members if member.is_manager == False and member.agent.id is not agent.id]
417
418
  if len(peers) > 0:
@@ -113,6 +113,9 @@ class TeamMember(ABC, BaseModel):
113
113
  is_manager: bool = Field(default=False)
114
114
  task: Optional[Task] = Field(default=None)
115
115
 
116
+ def update(self, task: Task):
117
+ self.task = task
118
+
116
119
 
117
120
  class Team(BaseModel):
118
121
  """
@@ -165,34 +168,47 @@ class Team(BaseModel):
165
168
 
166
169
 
167
170
  @property
168
- def manager_agent(self) -> Agent:
169
- manager_agent = [member.agent for member in self.members if member.is_manager == True]
170
- return manager_agent[0] if len(manager_agent) > 0 else None
171
+ def managers(self) -> List[TeamMember] | None:
172
+ managers = [member for member in self.members if member.is_manager == True]
173
+ return managers if len(managers) > 0 else None
171
174
 
172
175
 
173
176
  @property
174
- def manager_task(self) -> Task:
177
+ def manager_tasks(self) -> List[Task] | None:
175
178
  """
176
- Aside from the team task, return the task that the `manager_agent` needs to handle.
177
- The task is set as second priority following to the team tasks.
179
+ Tasks (incl. team tasks) handled by managers in the team.
178
180
  """
179
- task = [member.task for member in self.members if member.is_manager == True]
180
- return task[0] if len(task) > 0 else None
181
+ if self.managers:
182
+ tasks = [manager.task for manager in self.managers if manager.task is not None]
183
+ return tasks if len(tasks) > 0 else None
184
+
185
+ return None
181
186
 
182
187
 
183
188
  @property
184
189
  def tasks(self):
185
190
  """
186
191
  Return all the tasks that the team needs to handle in order of priority:
187
- 1. team tasks,
192
+ 1. team tasks, -> assigned to the member
188
193
  2. manager_task,
189
194
  3. members' tasks
190
195
  """
191
- sorted_member_tasks = [member.task for member in self.members if member.is_manager == True] + [member.task for member in self.members if member.is_manager == False]
192
- return self.team_tasks + sorted_member_tasks if len(self.team_tasks) > 0 else sorted_member_tasks
196
+
197
+ team_tasks = self.team_tasks
198
+ manager_tasks = [member.task for member in self.members if member.is_manager == True and member.task is not None and member.task not in team_tasks]
199
+ member_tasks = [member.task for member in self.members if member.is_manager == False and member.task is not None and member.task not in team_tasks]
200
+
201
+ return team_tasks + manager_tasks + member_tasks
202
+
203
+
204
+ @property
205
+ def member_tasks_without_agent(self) -> List[Task] | None:
206
+ if self.members:
207
+ return [member.task for member in self.members if member.agent is None]
208
+
209
+ return None
193
210
 
194
211
 
195
- # validators
196
212
  @field_validator("id", mode="before")
197
213
  @classmethod
198
214
  def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
@@ -201,6 +217,21 @@ class Team(BaseModel):
201
217
  raise PydanticCustomError("may_not_set_field", "The 'id' field cannot be set by the user.", {})
202
218
 
203
219
 
220
+ @model_validator(mode="after")
221
+ def validate_tasks(self):
222
+ """
223
+ Validates if the model recognize all tasks that the team needs to handle.
224
+ """
225
+
226
+ if self.tasks:
227
+ if all(task in self.tasks for task in self.team_tasks) == False:
228
+ raise PydanticCustomError("task_validation_error", "`team_tasks` needs to be recognized in the task.", {})
229
+
230
+ if len(self.tasks) != len(self.team_tasks) + len([member for member in self.members if member.task is not None]):
231
+ raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
232
+ return self
233
+
234
+
204
235
  @model_validator(mode="after")
205
236
  def check_manager_llm(self):
206
237
  """
@@ -208,37 +239,29 @@ class Team(BaseModel):
208
239
  """
209
240
 
210
241
  if self.process == TaskHandlingProcess.hierarchical:
211
- if self.manager_agent is None:
242
+ if self.managers is None:
212
243
  raise PydanticCustomError(
213
- "missing_manager_llm_or_manager_agent",
214
- "Attribute `manager_llm` or `manager_agent` is required when using hierarchical process.",
244
+ "missing_manager_llm_or_manager",
245
+ "Attribute `manager_llm` or `manager` is required when using hierarchical process.",
215
246
  {},
216
247
  )
217
248
 
218
- if (self.manager_agent is not None) and (
219
- self.members.count(self.manager_agent) > 0
220
- ):
221
- raise PydanticCustomError(
222
- "manager_agent_in_agents",
223
- "Manager agent should not be included in agents list.",
224
- {},
225
- )
249
+ if self.managers and (self.manager_tasks is None or self.team_tasks is None):
250
+ raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
251
+
226
252
  return self
227
253
 
228
254
 
229
255
  @model_validator(mode="after")
230
256
  def validate_tasks(self):
231
257
  """
232
- Every team member should have a task to handle.
258
+ Sequential task processing without any team tasks require a task-agent pairing.
233
259
  """
234
- if self.process == TaskHandlingProcess.sequential:
260
+
261
+ if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
235
262
  for member in self.members:
236
263
  if member.task is None:
237
- raise PydanticCustomError(
238
- "missing_agent_in_task",
239
- f"Sequential process error: Agent is missing in the task with the following description: {member.task.description}",
240
- {},
241
- )
264
+ raise PydanticCustomError("missing_agent_in_task", f"Sequential process error: Agent is missing the task", {})
242
265
  return self
243
266
 
244
267
  @model_validator(mode="after")
@@ -249,7 +272,9 @@ class Team(BaseModel):
249
272
 
250
273
  async_task_count = 0
251
274
  for task in reversed(self.tasks):
252
- if task.async_execution:
275
+ if not task:
276
+ break
277
+ elif task.async_execution:
253
278
  async_task_count += 1
254
279
  else:
255
280
  break
@@ -262,21 +287,54 @@ class Team(BaseModel):
262
287
  )
263
288
  return self
264
289
 
290
+
265
291
  def _get_responsible_agent(self, task: Task) -> Agent:
266
- res = [member.agent for member in self.members if member.task.id == task.id]
267
- return None if len(res) == 0 else res[0]
292
+ if task is None:
293
+ return None
294
+ else:
295
+ res = [member.agent for member in self.members if member.task and member.task.id == task.id]
296
+ return None if len(res) == 0 else res[0]
297
+
298
+
299
+ def _handle_team_planning(self) -> None:
300
+ """
301
+ Form a team considering agents and tasks given, and update `self.members` field:
302
+ 1. Idling managers to take the team tasks.
303
+ 2. Idling members to take the remaining tasks starting from the team tasks to member tasks.
304
+ 3. Create agents to handle the rest tasks.
305
+ """
268
306
 
269
- # setup team planner
270
- def _handle_team_planning(self):
271
307
  team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planning_llm)
272
- result = team_planner._handle_task_planning()
308
+ idling_managers: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is True]
309
+ idling_members: List[TeamMember] = [member for member in self.members if member.task is None and member.is_manager is False]
310
+ unassigned_tasks: List[Task] = self.member_tasks_without_agent
311
+ new_team_members: List[TeamMember] = []
312
+
313
+
314
+ if self.team_tasks:
315
+ candidates = idling_managers + idling_members
316
+ if candidates:
317
+ i = 0
318
+ while i < len(candidates):
319
+ if self.team_tasks[i]:
320
+ candidates[i].task = self.team_tasks[i]
321
+ i += 1
322
+
323
+ if len(self.team_tasks) > i:
324
+ for item in self.team_tasks[i:]:
325
+ if item not in unassigned_tasks:
326
+ unassigned_tasks = [item, ] + unassigned_tasks
273
327
 
274
- if result is not None:
275
- for task in self.tasks:
276
- task_id = task.id
277
- task.description += (
278
- result[task_id] if hasattr(result, str(task_id)) else result
279
- )
328
+ else:
329
+ for item in self.team_tasks:
330
+ if item not in unassigned_tasks:
331
+ unassigned_tasks = [item, ] + unassigned_tasks
332
+
333
+ if unassigned_tasks:
334
+ new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
335
+
336
+ if new_team_members:
337
+ self.members += new_team_members
280
338
 
281
339
 
282
340
  # task execution
@@ -333,9 +391,11 @@ class Team(BaseModel):
333
391
  token_sum = agent._token_process.get_summary()
334
392
  total_usage_metrics.add_usage_metrics(token_sum)
335
393
 
336
- if self.manager_agent and hasattr(self.manager_agent, "_token_process"):
337
- token_sum = self.manager_agent._token_process.get_summary()
338
- total_usage_metrics.add_usage_metrics(token_sum)
394
+ if self.managers:
395
+ for manager in self.managers:
396
+ if hasattr(manager.agent, "_token_process"):
397
+ token_sum = manager.agent._token_process.get_summary()
398
+ total_usage_metrics.add_usage_metrics(token_sum)
339
399
 
340
400
  self.usage_metrics = total_usage_metrics
341
401
  return total_usage_metrics
@@ -366,7 +426,7 @@ class Team(BaseModel):
366
426
 
367
427
  responsible_agent = self._get_responsible_agent(task)
368
428
  if responsible_agent is None:
369
- responsible_agent = self.manager_agent if self.manager_agent else self.members[0].agent
429
+ self._handle_team_planning()
370
430
 
371
431
  if isinstance(task, ConditionalTask):
372
432
  skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
@@ -377,13 +437,13 @@ class Team(BaseModel):
377
437
  # self._log_task_start(task, responsible_agent)
378
438
 
379
439
  if task.async_execution:
380
- context = create_raw_outputs(tasks=[task, ],task_outputs=([last_sync_output,] if last_sync_output else []))
440
+ context = create_raw_outputs(tasks=[task, ], task_outputs=([last_sync_output,] if last_sync_output else []))
381
441
  future = task.execute_async(agent=responsible_agent, context=context, tools=responsible_agent.tools)
382
442
  futures.append((task, future, task_index))
383
443
  else:
384
444
  context = create_raw_outputs(tasks=[task,], task_outputs=([last_sync_output,] if last_sync_output else [] ))
385
445
  task_output = task.execute_sync(agent=responsible_agent, context=context, tools=responsible_agent.tools)
386
- if responsible_agent is self.manager_agent:
446
+ if self.managers and responsible_agent in [manager.agent for manager in self.managers]:
387
447
  lead_task_output = task_output
388
448
 
389
449
  task_outputs.append(task_output)
@@ -400,15 +460,16 @@ class Team(BaseModel):
400
460
  def kickoff(self, kwargs_before: Optional[Dict[str, str]] = None, kwargs_after: Optional[Dict[str, Any]] = None) -> TeamOutput:
401
461
  """
402
462
  Kickoff the team:
403
- 0. Plan the team action if we have `team_tasks` using `planning_llm`.
463
+ 0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planning_llm.
404
464
  1. Address `before_kickoff_callbacks` if any.
405
- 2. Handle team members' tasks in accordance with the `process`.
465
+ 2. Handle team members' tasks in accordance with the process.
406
466
  3. Address `after_kickoff_callbacks` if any.
407
467
  """
408
468
 
409
469
  metrics: List[UsageMetrics] = []
410
470
 
411
- if len(self.team_tasks) > 0 or self.planning_llm is not None:
471
+
472
+ if self.team_tasks or self.member_tasks_without_agent:
412
473
  self._handle_team_planning()
413
474
 
414
475
  if kwargs_before is not None:
@@ -0,0 +1,93 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from typing import Any, List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+ load_dotenv(override=True)
7
+
8
+
9
+ class TeamPlanner:
10
+ """
11
+ Assign agents to multiple tasks.
12
+ """
13
+
14
+ from versionhq.task.model import Task, ResponseField, TaskOutput
15
+ from versionhq.agent.model import Agent
16
+
17
+
18
+ def __init__(self, tasks: List[Task], planner_llm: Optional[Any] = None):
19
+ self.tasks = tasks
20
+ self.planner_llm = planner_llm if planner_llm else os.environ.get("LITELLM_MODEL_NAME")
21
+
22
+
23
+ def _handle_assign_agents(self, unassigned_tasks: List[Task]) -> List[Any]:
24
+ """
25
+ Build an agent and assign it a task, then return a list of TeamMember connecting the agent created and the task given.
26
+ """
27
+
28
+ from versionhq.agent.model import Agent
29
+ from versionhq.task.model import Task, ResponseField
30
+ from versionhq.team.model import TeamMember
31
+
32
+ new_member_list: List[TeamMember] = []
33
+ agent_creator = Agent(
34
+ role="agent_creator",
35
+ goal="build an ai agent that can competitively handle the task given",
36
+ llm=self.planner_llm,
37
+ )
38
+
39
+ for unassgined_task in unassigned_tasks:
40
+ task = Task(
41
+ description=f"""
42
+ Based on the following task summary, draft a AI agent's role and goal in concise manner.
43
+ Task summary: {unassgined_task.summary}
44
+ """,
45
+ expected_output_json=True,
46
+ output_field_list=[
47
+ ResponseField(title="goal", type=str, required=True),
48
+ ResponseField(title="role", type=str, required=True),
49
+ ],
50
+ )
51
+ res = task.execute_sync(agent=agent_creator)
52
+ agent = Agent(
53
+ role=res.json_dict["role"] if "role" in res.json_dict else res.raw,
54
+ goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
55
+ )
56
+ if agent.id:
57
+ team_member = TeamMember(agent=agent, task=unassgined_task, is_manager=False)
58
+ new_member_list.append(team_member)
59
+
60
+ return new_member_list
61
+
62
+
63
+
64
+ def _handle_task_planning(self, context: Optional[str] = None, tools: Optional[str] = None) -> TaskOutput:
65
+ """
66
+ Handles the team planning by creating detailed step-by-step plans for each task.
67
+ """
68
+
69
+ from versionhq.agent.model import Agent
70
+ from versionhq.task.model import Task, ResponseField
71
+
72
+ team_planner = Agent(
73
+ role="team planner",
74
+ goal="Plan extremely detailed, step-by-step plan based on the tasks and tools available to each agent so that they can perform the tasks in an exemplary manner and assign a task to each agent.",
75
+ llm=self.planner_llm,
76
+ )
77
+
78
+ task_summary_list = [task.summary for task in self.tasks]
79
+ task = Task(
80
+ description=f"""
81
+ Based on the following task summaries, create the most descriptive plan that the team can execute most efficiently. Take all the task summaries - task's description and tools available - into consideration. Your answer only contains a dictionary.
82
+
83
+ Task summaries: {" ".join(task_summary_list)}
84
+ """,
85
+ expected_output_json=False,
86
+ expected_output_pydantic=True,
87
+ output_field_list=[
88
+ ResponseField(title="task", type=str, required=True)
89
+ for task in self.tasks
90
+ ],
91
+ )
92
+ output = task.execute_sync(agent=team_planner, context=context, tools=tools)
93
+ return output
@@ -137,7 +137,6 @@ class Tool(ABC, BaseModel):
137
137
  The response will be cascaded to the Task and stored in the TaskOutput.
138
138
  """
139
139
 
140
- print(f"Using the tool: {self.name}")
141
140
  result = None
142
141
 
143
142
  if self.func is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: versionhq
3
- Version: 1.1.8
3
+ Version: 1.1.8.1
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
@@ -1,5 +1,4 @@
1
1
  import os
2
- from pydantic import BaseModel
3
2
 
4
3
  from versionhq.agent.model import Agent
5
4
  from versionhq.task.model import Task, ResponseField, TaskOutput
@@ -61,8 +60,8 @@ def test_form_team():
61
60
  assert team.id is not None
62
61
  assert team.key is not None
63
62
  assert isinstance(team.key, str)
64
- assert team.manager_agent is not None
65
- assert team.manager_task.id is task_1.id
63
+ assert team.managers is not None
64
+ assert task_1 in team.manager_tasks
66
65
  assert len(team.tasks) == 2
67
66
  for item in team.tasks:
68
67
  assert item.id is task_1.id or item.id is task_2.id
@@ -120,8 +119,8 @@ def test_form_team_without_leader():
120
119
  assert team.id is not None
121
120
  assert team.key is not None
122
121
  assert isinstance(team.key, str)
123
- assert team.manager_agent is None
124
- assert team.manager_task is None
122
+ assert team.managers is None
123
+ assert team.manager_tasks is None
125
124
  assert len(team.tasks) == 2
126
125
  for item in team.tasks:
127
126
  assert item.id is task_1.id or item.id is task_2.id
@@ -291,11 +290,125 @@ def test_kickoff_with_leader():
291
290
  assert res.team_id is team.id
292
291
  assert res.raw is not None
293
292
  assert res.json_dict is not None
294
- assert team.manager_agent.id is agent_b.id
293
+ assert team.managers[0].agent.id is agent_b.id
295
294
  assert len(res.task_output_list) == 2
296
295
  assert [item.raw is not None for item in res.task_output_list]
297
296
  assert len(team.tasks) == 2
298
297
  assert team.tasks[0].output.raw == res.raw
299
298
 
300
299
 
301
- # async, task handling process
300
+ def test_hierarchial_process():
301
+ """
302
+ Manager to handle the top priority task first.
303
+ """
304
+
305
+ agent_a = Agent(role="agent a", goal="My amazing goals", llm=MODEL_NAME)
306
+ agent_b = Agent(role="agent b", goal="My amazing goals", llm=MODEL_NAME)
307
+ agent_c = Agent(role="agent c", goal="My amazing goals", llm=MODEL_NAME)
308
+ task_1 = Task(
309
+ description="Analyze the client's business model.",
310
+ output_field_list=[ResponseField(title="task_1", type=str, required=True),],
311
+ )
312
+ task_2 = Task(
313
+ description="Define the cohort timeframe.",
314
+ output_field_list=[
315
+ ResponseField(title="task_2_1", type=int, required=True),
316
+ ResponseField(title="task_2_2", type=list, required=True),
317
+ ],
318
+ )
319
+ team = Team(
320
+ members=[
321
+ TeamMember(agent=agent_a, is_manager=False, task=task_1),
322
+ TeamMember(agent=agent_b, is_manager=True, task=task_2),
323
+ TeamMember(agent=agent_c, is_manager=False)
324
+ ],
325
+ process=TaskHandlingProcess.hierarchical
326
+ )
327
+ res = team.kickoff()
328
+
329
+ assert isinstance(res, TeamOutput)
330
+ assert res.team_id is team.id
331
+ assert res.raw is not None
332
+ assert res.json_dict is not None
333
+ assert team.managers[0].agent.id is agent_b.id
334
+ assert len(res.task_output_list) == 2
335
+ assert [item.raw is not None for item in res.task_output_list]
336
+ assert len(team.tasks) == 2
337
+ assert team.tasks[0].output.raw == res.raw
338
+
339
+
340
+ def test_handle_team_task():
341
+ """
342
+ Make the best team formation with agents and tasks given.
343
+ """
344
+
345
+ agent_a = Agent(role="agent a", goal="My amazing goals", llm=MODEL_NAME)
346
+ agent_b = Agent(role="agent b", goal="My amazing goals", llm=MODEL_NAME)
347
+ agent_c = Agent(role="agent c", goal="My amazing goals", llm=MODEL_NAME)
348
+ team_task = Task(
349
+ description="Define outbound strategies.",
350
+ output_field_list=[ResponseField(title="team_task_1", type=str, required=True),],
351
+ )
352
+ task_1 = Task(
353
+ description="Analyze the client's business model.",
354
+ output_field_list=[ResponseField(title="task_1", type=str, required=True),],
355
+ )
356
+ task_2 = Task(
357
+ description="Define the cohort timeframe.",
358
+ output_field_list=[
359
+ ResponseField(title="task_2_1", type=int, required=True),
360
+ ResponseField(title="task_2_2", type=list, required=True),
361
+ ],
362
+ )
363
+ team_solo = Team(
364
+ members=[
365
+ TeamMember(agent=agent_c, is_manager=False)
366
+ ],
367
+ team_tasks=[team_task, task_1, task_2, ]
368
+ )
369
+ team_flat = Team(
370
+ members=[
371
+ TeamMember(agent=agent_a, is_manager=False, task=task_1),
372
+ TeamMember(agent=agent_c, is_manager=False)
373
+ ],
374
+ team_tasks=[team_task, task_2,]
375
+ )
376
+ team_leader = Team(
377
+ members=[
378
+ TeamMember(agent=agent_a, is_manager=False, task=task_1),
379
+ TeamMember(agent=agent_b, is_manager=True, task=task_2),
380
+ TeamMember(agent=agent_c, is_manager=False)
381
+ ],
382
+ team_tasks=[team_task, ]
383
+ )
384
+ team_dual_leaders = Team(
385
+ members=[
386
+ TeamMember(agent=agent_a, is_manager=False, task=task_1),
387
+ TeamMember(agent=agent_b, is_manager=True, task=task_2),
388
+ TeamMember(agent=agent_c, is_manager=True)
389
+ ],
390
+ team_tasks=[team_task, ]
391
+ )
392
+ team_leader_without_task = Team(
393
+ members=[
394
+ TeamMember(agent=agent_a, is_manager=False, task=task_1),
395
+ TeamMember(agent=agent_b, is_manager=False, task=task_2),
396
+ TeamMember(agent=agent_c, is_manager=True)
397
+ ],
398
+ team_tasks=[team_task,]
399
+ )
400
+ teams = [team_solo, team_flat, team_leader, team_dual_leaders, team_leader_without_task]
401
+
402
+ for team in teams:
403
+ res = team.kickoff()
404
+ assert team._get_responsible_agent(task=team_task) is not None
405
+ assert isinstance(res, TeamOutput)
406
+ assert res.team_id is team.id
407
+ assert team.tasks[0].id is team_task.id
408
+ assert res.raw is not None
409
+ assert len(team.members) == 3
410
+ assert len(team.tasks) == 3
411
+
412
+
413
+ if __name__ == "__main__":
414
+ test_handle_team_task()
@@ -1644,7 +1644,7 @@ wheels = [
1644
1644
 
1645
1645
  [[package]]
1646
1646
  name = "versionhq"
1647
- version = "1.1.8"
1647
+ version = "1.1.8.1"
1648
1648
  source = { editable = "." }
1649
1649
  dependencies = [
1650
1650
  { name = "appdirs" },
@@ -1,55 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
- from typing import Any, List, Optional
4
- from pydantic import BaseModel, Field
5
-
6
- from versionhq.agent.model import Agent
7
- from versionhq.task.model import Task, ResponseField
8
-
9
- load_dotenv(override=True)
10
-
11
-
12
- class TeamPlanner:
13
- """
14
- (Optional) Plan how the team should handle multiple tasks using LLM.
15
- """
16
-
17
- def __init__(self, tasks: List[Task], planner_llm: Optional[Any] = None):
18
- self.tasks = tasks
19
- self.planner_llm = (
20
- planner_llm if planner_llm != None else os.environ.get("LITELLM_MODEL_NAME")
21
- )
22
-
23
- def _handle_task_planning(self) -> BaseModel:
24
- """
25
- Handles the team planning by creating detailed step-by-step plans for each task.
26
- """
27
-
28
- planning_agent = Agent(
29
- role="Task Execution Planner",
30
- goal="Your goal is to create an extremely detailed, step-by-step plan based on the tasks and tools available to each agent so that they can perform the tasks in an exemplary manner",
31
- backstory="You have a strong ability to design efficient organizational structures and task processes, minimizing unnecessary steps.",
32
- llm=self.planner_llm,
33
- )
34
-
35
- task_summary_list = [task.summary for task in self.tasks]
36
- task_to_handle = Task(
37
- description=f"""
38
- Based on the following task summaries, create the most descriptive plan that the team can execute most efficiently. Take all the task summaries - task's description and tools available - into consideration. Your answer only contains a dictionary.
39
-
40
- Task summaries: {" ".join(task_summary_list)}
41
- """,
42
- expected_output_json=False,
43
- expected_output_pydantic=True,
44
- output_field_list=[
45
- ResponseField(title=f"{task.id}", type=str, required=True)
46
- for task in self.tasks
47
- ],
48
- )
49
- task_output = task_to_handle.execute_sync(agent=planning_agent)
50
-
51
- if isinstance(task_output.pydantic, BaseModel):
52
- return task_output.pydantic
53
-
54
- else:
55
- return None
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