versionhq 1.2.2.1__tar.gz → 1.2.2.3__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 (149) hide show
  1. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/PKG-INFO +1 -1
  2. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/pyproject.toml +1 -1
  3. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/__init__.py +4 -2
  4. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent_network/model.py +9 -3
  5. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/source_docling.py +4 -4
  6. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/memory/contextual_memory.py +37 -15
  7. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/ltm_sqlite_storage.py +1 -1
  8. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/evaluation.py +1 -1
  9. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task_graph/model.py +73 -17
  10. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq.egg-info/PKG-INFO +1 -1
  11. versionhq-1.2.2.3/tests/task_graph/task_graph_test.py +88 -0
  12. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/usecase_test.py +15 -0
  13. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/uv.lock +197 -216
  14. versionhq-1.2.2.1/tests/task_graph/task_graph_test.py +0 -27
  15. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.env.sample +0 -0
  16. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.github/workflows/deploy_docs.yml +0 -0
  17. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.github/workflows/publish.yml +0 -0
  18. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.github/workflows/publish_testpypi.yml +0 -0
  19. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.github/workflows/run_tests.yml +0 -0
  20. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.github/workflows/security_check.yml +0 -0
  21. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.gitignore +0 -0
  22. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.pre-commit-config.yaml +0 -0
  23. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/.python-version +0 -0
  24. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/LICENSE +0 -0
  25. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/README.md +0 -0
  26. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/SECURITY.md +0 -0
  27. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/db/preprocess.py +0 -0
  28. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/CNAME +0 -0
  29. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/_logos/favicon.ico +0 -0
  30. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/_logos/logo192.png +0 -0
  31. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent/config.md +0 -0
  32. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent/index.md +0 -0
  33. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent/task-handling.md +0 -0
  34. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent-network/config.md +0 -0
  35. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent-network/form.md +0 -0
  36. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent-network/index.md +0 -0
  37. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/agent-network/ref.md +0 -0
  38. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/llm/index.md +0 -0
  39. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/evaluation.md +0 -0
  40. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/index.md +0 -0
  41. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/response-field.md +0 -0
  42. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/task-execution.md +0 -0
  43. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/task-output.md +0 -0
  44. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/task-ref.md +0 -0
  45. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task/task-strc-response.md +0 -0
  46. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/task-graph/index.md +0 -0
  47. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/core/tool.md +0 -0
  48. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/index.md +0 -0
  49. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/quickstart.md +0 -0
  50. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/stylesheets/main.css +0 -0
  51. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/docs/tags.md +0 -0
  52. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/mkdocs.yml +0 -0
  53. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/requirements-dev.txt +0 -0
  54. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/requirements.txt +0 -0
  55. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/runtime.txt +0 -0
  56. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/setup.cfg +0 -0
  57. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/__init__.py +0 -0
  58. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/i18n.py +0 -0
  59. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/logger.py +0 -0
  60. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/process_config.py +0 -0
  61. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/usage_metrics.py +0 -0
  62. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/_utils/vars.py +0 -0
  63. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/TEMPLATES/Backstory.py +0 -0
  64. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/TEMPLATES/__init__.py +0 -0
  65. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/__init__.py +0 -0
  66. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/inhouse_agents.py +0 -0
  67. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/model.py +0 -0
  68. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/parser.py +0 -0
  69. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent/rpm_controller.py +0 -0
  70. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent_network/__init__.py +0 -0
  71. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/agent_network/formation.py +0 -0
  72. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/cli/__init__.py +0 -0
  73. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/__init__.py +0 -0
  74. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/customer/__init__.py +0 -0
  75. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/customer/model.py +0 -0
  76. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/product/__init__.py +0 -0
  77. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/product/model.py +0 -0
  78. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/workflow/__init__.py +0 -0
  79. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/clients/workflow/model.py +0 -0
  80. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/__init__.py +0 -0
  81. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/_utils.py +0 -0
  82. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/embedding.py +0 -0
  83. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/model.py +0 -0
  84. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/source.py +0 -0
  85. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/knowledge/storage.py +0 -0
  86. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/llm/__init__.py +0 -0
  87. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/llm/llm_vars.py +0 -0
  88. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/llm/model.py +0 -0
  89. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/memory/__init__.py +0 -0
  90. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/memory/model.py +0 -0
  91. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/__init__.py +0 -0
  92. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/base.py +0 -0
  93. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/mem0_storage.py +0 -0
  94. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/rag_storage.py +0 -0
  95. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/task_output_storage.py +0 -0
  96. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/storage/utils.py +0 -0
  97. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/TEMPLATES/Description.py +0 -0
  98. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/__init__.py +0 -0
  99. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/formatter.py +0 -0
  100. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/model.py +0 -0
  101. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task/structured_response.py +0 -0
  102. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task_graph/__init__.py +0 -0
  103. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task_graph/colors.py +0 -0
  104. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/task_graph/draft.py +0 -0
  105. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/__init__.py +0 -0
  106. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/cache_handler.py +0 -0
  107. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/composio_tool.py +0 -0
  108. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/composio_tool_vars.py +0 -0
  109. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/decorator.py +0 -0
  110. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/model.py +0 -0
  111. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq/tool/tool_handler.py +0 -0
  112. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq.egg-info/SOURCES.txt +0 -0
  113. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq.egg-info/dependency_links.txt +0 -0
  114. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq.egg-info/requires.txt +0 -0
  115. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/src/versionhq.egg-info/top_level.txt +0 -0
  116. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/__init__.py +0 -0
  117. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent/__init__.py +0 -0
  118. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent/agent_test.py +0 -0
  119. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent/doc_test.py +0 -0
  120. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent_network/Prompts/Demo_test.py +0 -0
  121. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent_network/__init__.py +0 -0
  122. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent_network/agent_network_test.py +0 -0
  123. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/agent_network/doc_test.py +0 -0
  124. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/cli/__init__.py +0 -0
  125. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/clients/customer_test.py +0 -0
  126. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/clients/product_test.py +0 -0
  127. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/clients/workflow_test.py +0 -0
  128. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/conftest.py +0 -0
  129. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/doc_test.py +0 -0
  130. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/formation_test.py +0 -0
  131. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/knowledge/__init__.py +0 -0
  132. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/knowledge/knowledge_test.py +0 -0
  133. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/knowledge/mock_report_compressed.pdf +0 -0
  134. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/llm/__init__.py +0 -0
  135. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/llm/llm_test.py +0 -0
  136. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/memory/__init__.py +0 -0
  137. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/memory/memory_test.py +0 -0
  138. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/__init__.py +0 -0
  139. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/doc_taskoutput_test.py +0 -0
  140. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/doc_test.py +0 -0
  141. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/eval_test.py +0 -0
  142. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/llm_connection_test.py +0 -0
  143. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task/task_test.py +0 -0
  144. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task_graph/__init__.py +0 -0
  145. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/task_graph/doc_test.py +0 -0
  146. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/tool/__init__.py +0 -0
  147. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/tool/composio_test.py +0 -0
  148. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/tool/doc_test.py +0 -0
  149. {versionhq-1.2.2.1 → versionhq-1.2.2.3}/tests/tool/tool_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: versionhq
3
- Version: 1.2.2.1
3
+ Version: 1.2.2.3
4
4
  Summary: An agentic orchestration framework for building agent networks that handle task automation.
5
5
  Author-email: Kuriko Iwai <kuriko@versi0n.io>
6
6
  License: MIT License
@@ -15,7 +15,7 @@ exclude = ["test*", "__pycache__", "*.egg-info"]
15
15
 
16
16
  [project]
17
17
  name = "versionhq"
18
- version = "1.2.2.1"
18
+ version = "1.2.2.3"
19
19
  authors = [{ name = "Kuriko Iwai", email = "kuriko@versi0n.io" }]
20
20
  description = "An agentic orchestration framework for building agent networks that handle task automation."
21
21
  readme = "README.md"
@@ -17,7 +17,7 @@ from versionhq.clients.workflow.model import MessagingWorkflow, MessagingCompone
17
17
  from versionhq.knowledge.model import Knowledge, KnowledgeStorage
18
18
  from versionhq.knowledge.source import PDFKnowledgeSource, CSVKnowledgeSource, JSONKnowledgeSource, TextFileKnowledgeSource, ExcelKnowledgeSource, StringKnowledgeSource
19
19
  from versionhq.knowledge.source_docling import DoclingSource
20
- from versionhq.task_graph.model import TaskStatus, TaskGraph, Node, Edge, DependencyType
20
+ from versionhq.task_graph.model import TaskStatus, TaskGraph, Node, Edge, DependencyType, Condition, ConditionType
21
21
  from versionhq.task.model import Task, TaskOutput, ResponseField, TaskExecutionType
22
22
  from versionhq.task.evaluation import Evaluation, EvaluationItem
23
23
  from versionhq.tool.model import Tool, ToolSet
@@ -31,7 +31,7 @@ from versionhq.agent_network.formation import form_agent_network
31
31
  from versionhq.task_graph.draft import workflow
32
32
 
33
33
 
34
- __version__ = "1.2.2.1"
34
+ __version__ = "1.2.2.3"
35
35
  __all__ = [
36
36
  "Agent",
37
37
 
@@ -67,6 +67,8 @@ __all__ = [
67
67
  "Node",
68
68
  "Edge",
69
69
  "DependencyType",
70
+ "Condition",
71
+ "ConditionType",
70
72
 
71
73
  "Task",
72
74
  "TaskOutput",
@@ -13,7 +13,7 @@ from pydantic_core import PydanticCustomError, core_schema
13
13
 
14
14
  from versionhq.agent.model import Agent
15
15
  from versionhq.task.model import Task, TaskOutput, TaskExecutionType, ResponseField
16
- from versionhq.task_graph.model import TaskGraph, Node, Edge, TaskStatus, DependencyType
16
+ from versionhq.task_graph.model import TaskGraph, Node, Edge, TaskStatus, DependencyType, Condition
17
17
  from versionhq._utils.logger import Logger
18
18
  # from versionhq.recording.usage_metrics import UsageMetrics
19
19
 
@@ -81,7 +81,7 @@ class AgentNetwork(BaseModel):
81
81
  # task execution rules
82
82
  prompt_file: str = Field(default="", description="absolute file path to the prompt file that stores jsonified prompts")
83
83
  process: TaskHandlingProcess = Field(default=TaskHandlingProcess.SEQUENTIAL)
84
- consent_trigger: Optional[Callable] = Field(default=None, description="returns bool")
84
+ consent_trigger: Optional[Callable | Condition] = Field(default=None, description="returns bool")
85
85
 
86
86
  # callbacks
87
87
  pre_launch_callbacks: List[Callable[..., Any]]= Field(default_factory=list, description="list of callback funcs called before the network launch")
@@ -111,6 +111,9 @@ class AgentNetwork(BaseModel):
111
111
  Logger().log(level="error", message="Need to define the consent trigger function that returns bool", color="red")
112
112
  raise PydanticCustomError("invalid_process", "Need to define the consent trigger function that returns bool", {})
113
113
 
114
+
115
+ if self.consent_trigger and isinstance(self.consent_trigger, Callable):
116
+ self.consent_trigger = Condition(methods={"0": self.consent_trigger})
114
117
  return self
115
118
 
116
119
 
@@ -263,6 +266,8 @@ class AgentNetwork(BaseModel):
263
266
  task = self.tasks[0]
264
267
  responsible_agent = self._get_responsible_agent(task=task)
265
268
  res = task.execute(agent=responsible_agent)
269
+ node = Node(task=task)
270
+ task_graph = TaskGraph(nodes={ node.identifier: node, }, concl=res if res else None)
266
271
  return res, task_graph
267
272
 
268
273
  nodes = [
@@ -275,6 +280,7 @@ class AgentNetwork(BaseModel):
275
280
  task_graph = TaskGraph(nodes={node.identifier: node for node in nodes})
276
281
 
277
282
  for i in range(0, len(nodes) - 1):
283
+ condition = self.consent_trigger if isinstance(self.consent_trigger, Condition) else Condition(methods={"0": self.consent_trigger }) if self.consent_trigger else None
278
284
  task_graph.add_edge(
279
285
  source=nodes[i].identifier,
280
286
  target=nodes[i+1].identifier,
@@ -282,7 +288,7 @@ class AgentNetwork(BaseModel):
282
288
  weight=3 if nodes[i].task in self.manager_tasks else 1,
283
289
  dependency_type=DependencyType.FINISH_TO_START if self.process == TaskHandlingProcess.HIERARCHY else DependencyType.START_TO_START,
284
290
  required=bool(self.process == TaskHandlingProcess.CONSENSUAL),
285
- condition=self.consent_trigger,
291
+ condition=condition,
286
292
  data_transfer=bool(self.process == TaskHandlingProcess.HIERARCHY),
287
293
  )
288
294
  )
@@ -9,10 +9,10 @@ try:
9
9
  from docling_core.transforms.chunker.hierarchical_chunker import HierarchicalChunker
10
10
  from docling_core.types.doc.document import DoclingDocument
11
11
  DOCLING_AVAILABLE = True
12
- except ImportError:
13
- import envoy
14
- envoy.run("uv add docling --optional docling")
15
- DOCLING_AVAILABLE = True
12
+ # except ImportError:
13
+ # import envoy
14
+ # envoy.run("uv add docling --optional docling")
15
+ # DOCLING_AVAILABLE = True
16
16
  except:
17
17
  DOCLING_AVAILABLE = False
18
18
 
@@ -23,25 +23,25 @@ class ContextualMemory:
23
23
  self.um = um
24
24
 
25
25
 
26
- def build_context_for_task(self, query: str = None) -> str:
27
- """
28
- Automatically builds a minimal, highly relevant set of contextual information for a given task.
29
- """
26
+ def _sanitize_query(self, query: str = None) -> str:
30
27
  if not query:
31
28
  return ""
32
29
 
33
- context = []
34
- context.append(self._fetch_stm_context(query))
35
- if self.memory_provider == "mem0":
36
- context.append(self._fetch_user_context(query))
37
- return "\n".join(filter(None, context))
30
+ return query.replace("{", "").replace("}", "")
38
31
 
39
32
 
40
- def _fetch_stm_context(self, query) -> str:
33
+ def _fetch_stm_context(self, query: str = None) -> str:
41
34
  """
42
- Fetches recent relevant insights from STM related to the task's description and expected_output, formatted as bullet points.
35
+ Fetches recent relevant insights from STM
43
36
  """
37
+ if not query:
38
+ return ""
39
+
40
+
44
41
  stm_results = self.stm.search(query)
42
+ if not stm_results:
43
+ return ""
44
+
45
45
  formatted_results = "\n".join(
46
46
  [
47
47
  f"- {result['memory'] if self.memory_provider == 'mem0' else result['context']}"
@@ -51,13 +51,16 @@ class ContextualMemory:
51
51
  return f"Recent Insights:\n{formatted_results}" if stm_results else ""
52
52
 
53
53
 
54
- def _fetch_ltm_context(self, task) -> Optional[str]:
54
+ def _fetch_ltm_context(self, query: str = None) -> Optional[str]:
55
55
  """
56
56
  Fetches historical data or insights from LTM that are relevant to the task's description and expected_output, formatted as bullet points.
57
57
  """
58
- ltm_results = self.ltm.search(task, latest_n=2)
58
+ if not query:
59
+ return ""
60
+
61
+ ltm_results = self.ltm.search(query, latest_n=2)
59
62
  if not ltm_results:
60
- return None
63
+ return ""
61
64
 
62
65
  formatted_results = [suggestion for result in ltm_results for suggestion in result["metadata"]["suggestions"]]
63
66
  formatted_results = list(dict.fromkeys(formatted_results))
@@ -65,10 +68,12 @@ class ContextualMemory:
65
68
  return f"Historical Data:\n{formatted_results}" if ltm_results else ""
66
69
 
67
70
 
68
- def _fetch_user_context(self, query: str) -> str:
71
+ def _fetch_user_context(self, query: str = None) -> str:
69
72
  """
70
73
  Fetches and formats relevant user information from User Memory.
71
74
  """
75
+ if not query:
76
+ return ""
72
77
 
73
78
  user_memories = self.um.search(query)
74
79
  if not user_memories:
@@ -78,6 +83,23 @@ class ContextualMemory:
78
83
  return f"User memories/preferences:\n{formatted_memories}"
79
84
 
80
85
 
86
+ def build_context_for_task(self, query: str = None) -> str:
87
+ """
88
+ Automatically builds a minimal, highly relevant set of contextual information for a given task.
89
+ """
90
+ if not query:
91
+ return ""
92
+
93
+ query = self._sanitize_query(query=query)
94
+
95
+ context = []
96
+ context.append(self._fetch_stm_context(query))
97
+ context.append(self._fetch_ltm_context(query))
98
+ if self.memory_provider == "mem0":
99
+ context.append(self._fetch_user_context(query))
100
+ return "\n".join(filter(None, context))
101
+
102
+
81
103
  # def _fetch_entity_context(self, query) -> str:
82
104
  # """
83
105
  # Fetches relevant entity information from Entity Memory related to the task's description and expected_output,
@@ -97,7 +97,7 @@ class LTMSQLiteStorage:
97
97
  ]
98
98
 
99
99
  except sqlite3.Error as e:
100
- self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred while querying LTM: {e}",color="red")
100
+ self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred while querying LTM: {str(e)}",color="red")
101
101
  return None
102
102
 
103
103
 
@@ -103,7 +103,7 @@ class Evaluation(BaseModel):
103
103
  new_res = filter(lambda x: "score" in x["metadata"], res)
104
104
  new_res = list(new_res)
105
105
  new_res.sort(key=lambda x: x["metadata"]["score"], reverse=True)
106
- if new_res[0]['data']:
106
+ if new_res and new_res[0]['data']:
107
107
  c = new_res[0]['data']['task_output']
108
108
  w = new_res[len(new_res)-1]['data']['task_output'] if new_res[len(new_res)-1]['metadata']['score'] < new_res[0]['metadata']['score'] else ""
109
109
  shot_prompt = SHOTS.format(c=c, w=w)
@@ -8,14 +8,63 @@ import matplotlib.pyplot as plt
8
8
  from abc import ABC
9
9
  from concurrent.futures import Future
10
10
  from typing import List, Any, Optional, Callable, Dict, Type, Tuple
11
+ from typing_extensions import Self
11
12
 
12
- from pydantic import BaseModel, InstanceOf, Field, UUID4, field_validator
13
+ from pydantic import BaseModel, InstanceOf, Field, UUID4, field_validator, model_validator
13
14
  from pydantic_core import PydanticCustomError
14
15
 
15
16
  from versionhq.agent.model import Agent
16
- from versionhq.task.model import Task, TaskOutput
17
+ from versionhq.task.model import Task, TaskOutput, Evaluation
17
18
  from versionhq._utils.logger import Logger
18
19
 
20
+ class ConditionType(enum.Enum):
21
+ AND = 1
22
+ OR = 2
23
+
24
+
25
+ class Condition(BaseModel):
26
+ """
27
+ A Pydantic class to store edge conditions and their args and types.
28
+ """
29
+ # edge_id: UUID4 = uuid.uuid4()
30
+ methods: Dict[str, Callable | "Condition"] = dict()
31
+ args: Dict[str, Dict[str, Any]] = dict()
32
+ type: ConditionType = None
33
+
34
+ @model_validator(mode="after")
35
+ def validate_type(self) -> Self:
36
+ if len(self.methods.keys()) > 1 and self.type is None:
37
+ raise PydanticCustomError("missing_type", "Missing type", {})
38
+ return self
39
+
40
+ def _execute_method(self, key: str, method: Callable | "Condition") -> bool:
41
+ match method:
42
+ case Condition():
43
+ return method.condition_met()
44
+ case _:
45
+ args = self.args[key] if key in self.args else None
46
+ res = method(**args) if args else method()
47
+ return res
48
+
49
+
50
+ def condition_met(self) -> bool:
51
+ if not self.methods:
52
+ return True
53
+
54
+ if len(self.methods) == 1:
55
+ for k, v in self.methods.items():
56
+ return self._execute_method(key=k, method=v)
57
+
58
+ else:
59
+ cond_list = []
60
+ for k, v in self.methods.items():
61
+ res = self._execute_method(key=k, method=v)
62
+ if self.type == ConditionType.OR and res == True:
63
+ return True
64
+ elif self.type == ConditionType.AND and res == False:
65
+ return False
66
+ return bool(len([item for item in cond_list if item == True]) == len(cond_list))
67
+
19
68
 
20
69
  class TaskStatus(enum.Enum):
21
70
  """
@@ -52,7 +101,6 @@ class Node(BaseModel):
52
101
  assigned_to: InstanceOf[Agent] = Field(default=None)
53
102
  status: TaskStatus = Field(default=TaskStatus.NOT_STARTED)
54
103
 
55
-
56
104
  @field_validator("id", mode="before")
57
105
  @classmethod
58
106
  def _deny_user_set_id(cls, v: Optional[UUID4]) -> None:
@@ -83,7 +131,6 @@ class Node(BaseModel):
83
131
  self.status = TaskStatus.COMPLETED if res else TaskStatus.ERROR
84
132
  return res
85
133
 
86
-
87
134
  @property
88
135
  def in_degrees(self) -> int:
89
136
  return len(self.in_degree_nodes) if self.in_degree_nodes else 0
@@ -101,6 +148,11 @@ class Node(BaseModel):
101
148
  """Unique identifier of the node"""
102
149
  return f"{str(self.id)}"
103
150
 
151
+ @property
152
+ def label(self) -> str:
153
+ """Human friendly label for visualization"""
154
+ return self.task.name if self.task.name else self.task.description[0: 8]
155
+
104
156
  def __str__(self):
105
157
  if self.task:
106
158
  return f"{self.identifier}: {self.task.name if self.task.name else self.task.description[0: 12]}"
@@ -110,19 +162,17 @@ class Node(BaseModel):
110
162
 
111
163
  class Edge(BaseModel):
112
164
  """
113
- A class to store an edge object that connects source and target nodes.
165
+ A Pydantic class to store an edge object that connects source and target nodes.
114
166
  """
115
167
 
116
168
  source: Node = Field(default=None)
117
169
  target: Node = Field(default=None)
118
170
 
119
171
  description: Optional[str] = Field(default=None)
120
- weight: Optional[float | int] = Field(default=1, description="est. duration for the task execution or respective weight of the target node (1 low - 10 high priority)")
121
-
172
+ weight: Optional[float | int] = Field(default=1, description="est. duration of the task execution or respective weight of the target node at any scale i.e., 1 low - 10 high priority")
122
173
  dependency_type: DependencyType = Field(default=DependencyType.FINISH_TO_START)
123
174
  required: bool = Field(default=True, description="whether to consider the source's status")
124
- condition: Optional[Callable] = Field(default=None, description="conditional function to start executing the dependency")
125
- condition_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict)
175
+ condition: Optional[Condition] = Field(default=None)
126
176
 
127
177
  lag: Optional[float | int] = Field(default=None, description="lag time (sec) from the dependency met to the task execution")
128
178
  data_transfer: bool = Field(default=True, description="whether the data transfer is required. by default transfer plane text output from in-degree nodes as context")
@@ -150,35 +200,36 @@ class Edge(BaseModel):
150
200
  False Given Condition True (predecessor status irrelevant) Yes
151
201
  """
152
202
 
153
- if not self.required:
154
- return self.condition(**self.condition_kwargs) if self.condition else True
203
+ if self.required == False:
204
+ return self.condition.condition_met() if self.condition else True
205
+ # return self.condition(**self.condition_kwargs) if self.condition else True
155
206
 
156
207
  match self.dependency_type:
157
208
  case DependencyType.FINISH_TO_START:
158
209
  """target starts after source finishes"""
159
210
  if not self.source or self.source.status == TaskStatus.COMPLETED:
160
- return self.condition(**self.conditon_kwargs) if self.condition else True
211
+ return self.condition.condition_met() if self.condition else True
161
212
  else:
162
213
  return False
163
214
 
164
215
  case DependencyType.START_TO_START:
165
216
  """target starts when source starts"""
166
217
  if not self.source or self.source.status != TaskStatus.NOT_STARTED:
167
- return self.condition(**self.conditon_kwargs) if self.condition else True
218
+ return self.condition.condition_met() if self.condition else True
168
219
  else:
169
220
  return False
170
221
 
171
222
  case DependencyType.FINISH_TO_FINISH:
172
223
  """target finish when source start"""
173
224
  if not self.source or self.source.status != TaskStatus.COMPLETED:
174
- return self.condition(**self.conditon_kwargs) if self.condition else True
225
+ return self.condition.condition_met() if self.condition else True
175
226
  else:
176
227
  return False
177
228
 
178
229
  case DependencyType.START_TO_FINISH:
179
230
  """target finishes when source start"""
180
231
  if not self.source or self.source.status == TaskStatus.IN_PROGRESS:
181
- return self.condition(**self.conditon_kwargs) if self.condition else True
232
+ return self.condition.condition_met() if self.condition else True
182
233
  else:
183
234
  return False
184
235
 
@@ -204,6 +255,11 @@ class Edge(BaseModel):
204
255
  res = self.target.handle_task_execution(context=context, response_format=response_format)
205
256
  return res
206
257
 
258
+ @property
259
+ def label(self):
260
+ """Human friendly label for visualization."""
261
+ return f"e{self.source.label}-{self.target.label}"
262
+
207
263
 
208
264
  class Graph(ABC, BaseModel):
209
265
  """
@@ -383,7 +439,7 @@ class TaskGraph(Graph):
383
439
  edge = Edge()
384
440
  for k in Edge.model_fields.keys():
385
441
  v = edge_attributes.get(k, None)
386
- if v:
442
+ if v is not None:
387
443
  setattr(edge, k, v)
388
444
  else:
389
445
  pass
@@ -564,7 +620,7 @@ class TaskGraph(Graph):
564
620
  return res, self.outputs
565
621
 
566
622
 
567
- def evaluate(self, eval_criteria: List[str] = None):
623
+ def evaluate(self, eval_criteria: List[str] = None) -> Evaluation | None:
568
624
  """Evaluates the conclusion based on the given eval criteria."""
569
625
 
570
626
  if not isinstance(self.concl, TaskOutput):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: versionhq
3
- Version: 1.2.2.1
3
+ Version: 1.2.2.3
4
4
  Summary: An agentic orchestration framework for building agent networks that handle task automation.
5
5
  Author-email: Kuriko Iwai <kuriko@versi0n.io>
6
6
  License: MIT License
@@ -0,0 +1,88 @@
1
+ from unittest.mock import patch
2
+
3
+
4
+ def test_draft():
5
+ import versionhq as vhq
6
+ from pydantic import BaseModel
7
+
8
+ class Trip(BaseModel):
9
+ name: str
10
+ location: str
11
+ description: str
12
+ date: str
13
+ cousine: str
14
+ why_its_suitable: str
15
+
16
+
17
+ with patch('builtins.input', return_value='Y'):
18
+ task_graph = vhq.workflow(
19
+ Trip,
20
+ context="Planning a suprise day trip for my friend to celebrate her birthday. We live in CA and we like to have Korean food.",
21
+ human=True
22
+ )
23
+
24
+ assert task_graph
25
+ assert [k == node.identifier and node.task and isinstance(node, vhq.Node) for k, node in task_graph.nodes.items()]
26
+ assert [isinstance(edge.dependency_type, vhq.DependencyType) and isinstance(edge, vhq.Edge) for k, edge in task_graph.edges.items()]
27
+ assert [k in task_graph.nodes.keys() and v.status == vhq.TaskStatus.NOT_STARTED for k, v in task_graph.nodes.items()]
28
+
29
+
30
+ def test_condition():
31
+ import versionhq as vhq
32
+
33
+ task_a = vhq.Task(description="draft a message")
34
+ task_b = vhq.Task(description="ask human for feedback")
35
+ task_c = vhq.Task(description="send a message")
36
+
37
+ tg = vhq.TaskGraph(directed=True)
38
+
39
+ node_a = tg.add_task(task=task_a)
40
+ node_b = tg.add_task(task=task_b)
41
+ node_c = tg.add_task(task=task_c)
42
+
43
+ def cond_method(): return True
44
+
45
+ def cond_method_with_args(**kwargs):
46
+ item = kwargs.get("item", None)
47
+ return True if item else False
48
+
49
+ complex_condition = vhq.Condition(
50
+ methods={"0": cond_method, "1": cond_method_with_args, "2": cond_method_with_args},
51
+ args={"2": dict(item="Hi!"), },
52
+ type=vhq.ConditionType.AND
53
+ )
54
+
55
+ tg.add_dependency(
56
+ node_a.identifier, node_b.identifier,
57
+ dependency_type=vhq.DependencyType.FINISH_TO_START,
58
+ required=False,
59
+ condition=vhq.Condition(methods={ "0": cond_method, }),
60
+ )
61
+
62
+ edge = [v for v in tg.edges.values()][0]
63
+ assert isinstance(edge, vhq.Edge)
64
+ assert edge.required == False
65
+ assert edge.dependency_type == vhq.DependencyType.FINISH_TO_START
66
+ assert edge.condition == vhq.Condition(methods={ "0": cond_method, })
67
+ assert edge.dependency_met() == True
68
+
69
+
70
+ tg.add_dependency(
71
+ node_b.identifier, node_c.identifier,
72
+ dependency_type=vhq.DependencyType.FINISH_TO_START,
73
+ required=False,
74
+ condition=vhq.Condition(
75
+ methods={ "0": cond_method, "1": cond_method_with_args, "2": complex_condition },
76
+ type=vhq.ConditionType.AND
77
+ ),
78
+ )
79
+
80
+ edge = [v for v in tg.edges.values()][1]
81
+ assert isinstance(edge, vhq.Edge)
82
+ assert edge.required == False
83
+ assert edge.dependency_type == vhq.DependencyType.FINISH_TO_START
84
+ assert edge.condition == vhq.Condition(
85
+ methods={ "0": cond_method, "1": cond_method_with_args, "2": complex_condition },
86
+ type=vhq.ConditionType.AND
87
+ )
88
+ assert edge.dependency_met() == False
@@ -55,3 +55,18 @@ def test_demo_agent_customization():
55
55
  assert agent.llm.temperature == 1 and agent.llm.top_p == 0.1 and agent.llm.n == 1 and agent.llm.stop == "test"
56
56
  assert agent.knowledge_sources == ['https://business.linkedin.com', f'{current_path}/demo.csv',]
57
57
  assert agent.with_memory == True
58
+
59
+
60
+ def test_solo_tg_eval():
61
+ import versionhq as vhq
62
+
63
+ network = vhq.form_agent_network(
64
+ task="test",
65
+ expected_outcome="test"
66
+ )
67
+
68
+ res, tg = network.launch()
69
+ eval = tg.evaluate(
70
+ eval_criteria=["cost", "", "{criteria_3}"]
71
+ )
72
+ assert isinstance(eval, vhq.Evaluation)