versionhq 1.1.12.5__py3-none-any.whl → 1.1.13.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 +6 -3
- versionhq/_utils/usage_metrics.py +6 -6
- versionhq/agent/inhouse_agents.py +1 -1
- versionhq/llm/llm_vars.py +5 -0
- versionhq/task/evaluate.py +5 -6
- versionhq/task/formation.py +64 -30
- versionhq/task/model.py +2 -5
- versionhq/team/model.py +95 -92
- versionhq/team/team_planner.py +5 -6
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.0.dist-info}/METADATA +99 -109
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.0.dist-info}/RECORD +14 -14
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.0.dist-info}/LICENSE +0 -0
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.0.dist-info}/WHEEL +0 -0
- {versionhq-1.1.12.5.dist-info → versionhq-1.1.13.0.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -4,6 +4,9 @@ warnings.filterwarnings(action="ignore", message="Pydantic serializer warnings:"
|
|
4
4
|
warnings.filterwarnings(action="ignore", category=UserWarning, module="pydantic._internal")
|
5
5
|
warnings.filterwarnings(action="ignore", module="LiteLLM:utils")
|
6
6
|
|
7
|
+
from dotenv import load_dotenv
|
8
|
+
load_dotenv(override=True)
|
9
|
+
|
7
10
|
from versionhq.agent.model import Agent
|
8
11
|
from versionhq.clients.customer.model import Customer
|
9
12
|
from versionhq.clients.product.model import Product, ProductProvider
|
@@ -13,7 +16,7 @@ from versionhq.knowledge.source import PDFKnowledgeSource, CSVKnowledgeSource, J
|
|
13
16
|
from versionhq.knowledge.source_docling import DoclingSource
|
14
17
|
from versionhq.task.model import Task, TaskOutput, ConditionalTask, ResponseField
|
15
18
|
from versionhq.task.evaluate import Evaluation, EvaluationItem
|
16
|
-
from versionhq.team.model import Team, TeamOutput, Formation,
|
19
|
+
from versionhq.team.model import Team, TeamOutput, Formation, Member, TaskHandlingProcess
|
17
20
|
from versionhq.tool.model import Tool, ToolSet
|
18
21
|
from versionhq.tool.cache_handler import CacheHandler
|
19
22
|
from versionhq.tool.tool_handler import ToolHandler
|
@@ -24,7 +27,7 @@ from versionhq.memory.model import ShortTermMemory,LongTermMemory, UserMemory, M
|
|
24
27
|
from versionhq.task.formation import form_agent_network
|
25
28
|
|
26
29
|
|
27
|
-
__version__ = "1.1.
|
30
|
+
__version__ = "1.1.13.0"
|
28
31
|
__all__ = [
|
29
32
|
"Agent",
|
30
33
|
|
@@ -55,7 +58,7 @@ __all__ = [
|
|
55
58
|
"Team",
|
56
59
|
"TeamOutput",
|
57
60
|
"Formation",
|
58
|
-
"
|
61
|
+
"Member",
|
59
62
|
"TaskHandlingProcess",
|
60
63
|
|
61
64
|
"Tool",
|
@@ -3,14 +3,14 @@ from pydantic import BaseModel, Field
|
|
3
3
|
|
4
4
|
class UsageMetrics(BaseModel):
|
5
5
|
"""
|
6
|
-
Model to track usage
|
6
|
+
Model to track usage
|
7
7
|
"""
|
8
8
|
|
9
|
-
total_tokens: int = Field(default=0, description="
|
10
|
-
prompt_tokens: int = Field(default=0, description="
|
11
|
-
cached_prompt_tokens: int = Field(default=0, description="
|
12
|
-
completion_tokens: int = Field(default=0, description="
|
13
|
-
successful_requests: int = Field(default=0, description="
|
9
|
+
total_tokens: int = Field(default=0, description="total number of tokens used")
|
10
|
+
prompt_tokens: int = Field(default=0, description="number of tokens used in prompts")
|
11
|
+
cached_prompt_tokens: int = Field(default=0, description="number of cached prompt tokens used")
|
12
|
+
completion_tokens: int = Field(default=0, description="number of tokens used in completions")
|
13
|
+
successful_requests: int = Field(default=0, description="number of successful requests made")
|
14
14
|
|
15
15
|
def add_usage_metrics(self, usage_metrics: "UsageMetrics") -> None:
|
16
16
|
"""
|
@@ -29,7 +29,7 @@ vhq_formation_planner = Agent(
|
|
29
29
|
role="vhq-Formation Planner",
|
30
30
|
goal="Plan a formation of agents based on the given task descirption.",
|
31
31
|
llm="gemini/gemini-2.0-flash-exp",
|
32
|
-
llm_config=dict(top_p=0.8,
|
32
|
+
llm_config=dict(top_p=0.8, topK=40, temperature=0.9),
|
33
33
|
maxit=1,
|
34
34
|
max_retry_limit=1,
|
35
35
|
knowledge_sources=[
|
versionhq/llm/llm_vars.py
CHANGED
@@ -55,6 +55,11 @@ MODELS = {
|
|
55
55
|
"openrouter": [
|
56
56
|
"openrouter/deepseek/deepseek-r1",
|
57
57
|
"openrouter/qwen/qwen-2.5-72b-instruct",
|
58
|
+
"openrouter/google/gemini-2.0-flash-thinking-exp:free",
|
59
|
+
"openrouter/google/gemini-2.0-flash-thinking-exp-1219:free",
|
60
|
+
"openrouter/google/gemini-2.0-flash-001",
|
61
|
+
"openrouter/meta-llama/llama-3.3-70b-instruct",
|
62
|
+
"openrouter/mistralai/mistral-large-2411",
|
58
63
|
],
|
59
64
|
"huggingface": [
|
60
65
|
"huggingface/qwen/qwen2.5-VL-72B-Instruct",
|
versionhq/task/evaluate.py
CHANGED
@@ -70,17 +70,16 @@ class EvaluationItem(BaseModel):
|
|
70
70
|
else: return None
|
71
71
|
|
72
72
|
|
73
|
-
|
74
73
|
class Evaluation(BaseModel):
|
75
74
|
items: List[EvaluationItem] = []
|
76
|
-
latency: int = Field(default=None, description="seconds")
|
75
|
+
latency: int = Field(default=None, description="job execution latency in seconds")
|
77
76
|
tokens: int = Field(default=None, description="tokens consumed")
|
78
|
-
|
77
|
+
eval_by: Any = Field(default=None, description="stores agent object that evaluates the outcome")
|
79
78
|
|
80
79
|
@model_validator(mode="after")
|
81
|
-
def
|
80
|
+
def set_up_evaluator(self) -> Self:
|
82
81
|
from versionhq.agent.inhouse_agents import vhq_task_evaluator
|
83
|
-
self.
|
82
|
+
self.eval_by = vhq_task_evaluator
|
84
83
|
return self
|
85
84
|
|
86
85
|
|
@@ -88,7 +87,7 @@ class Evaluation(BaseModel):
|
|
88
87
|
"""
|
89
88
|
Create and store evaluation results in the memory metadata
|
90
89
|
"""
|
91
|
-
eval_by = self.
|
90
|
+
eval_by = self.eval_by.role if self.eval_by else None
|
92
91
|
score = self.aggregate_score
|
93
92
|
eval_criteria = ", ".join([item.criteria for item in self.items]) if self.items else None
|
94
93
|
suggestion = self.suggestion_summary
|
versionhq/task/formation.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
from typing import List
|
1
|
+
from typing import List, Type
|
2
2
|
from enum import Enum
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
6
6
|
from versionhq.task.model import Task
|
7
7
|
from versionhq.agent.model import Agent
|
8
|
-
from versionhq.team.model import Team,
|
8
|
+
from versionhq.team.model import Team, Member, Formation
|
9
9
|
from versionhq.agent.inhouse_agents import vhq_formation_planner
|
10
10
|
from versionhq._utils import Logger
|
11
11
|
|
@@ -15,10 +15,10 @@ def form_agent_network(
|
|
15
15
|
expected_outcome: str,
|
16
16
|
agents: List[Agent] = None,
|
17
17
|
context: str = None,
|
18
|
-
formation: Formation = None
|
18
|
+
formation: Type[Formation] = None
|
19
19
|
) -> Team | None:
|
20
20
|
"""
|
21
|
-
Make a formation of agents from the given task description,
|
21
|
+
Make a formation of agents from the given task description, expected outcome, agents (optional), and context (optional).
|
22
22
|
"""
|
23
23
|
|
24
24
|
if not task:
|
@@ -29,8 +29,37 @@ def form_agent_network(
|
|
29
29
|
Logger(verbose=True).log(level="error", message="Missing expected outcome.", color="red")
|
30
30
|
return None
|
31
31
|
|
32
|
+
if formation:
|
33
|
+
try:
|
34
|
+
match formation:
|
35
|
+
case Formation():
|
36
|
+
if formation == Formation.UNDEFINED:
|
37
|
+
formation = None
|
38
|
+
else:
|
39
|
+
pass
|
40
|
+
|
41
|
+
case str():
|
42
|
+
matched = [item for item in Formation._member_names_ if item == formation.upper()]
|
43
|
+
if matched:
|
44
|
+
formation = getattr(Formation, matched[0])
|
45
|
+
else:
|
46
|
+
# Formation._generate_next_value_(name=f"CUSTOM_{formation.upper()}", start=100, count=6, last_values=Formation.HYBRID.name)
|
47
|
+
Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid. We'll recreate a valid formation.", color="yellow")
|
48
|
+
formation = None
|
49
|
+
|
50
|
+
case int() | float():
|
51
|
+
formation = Formation(int(formation))
|
52
|
+
|
53
|
+
case _:
|
54
|
+
Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid. We'll recreate a valid formation.", color="yellow")
|
55
|
+
formation = None
|
56
|
+
|
57
|
+
except Exception as e:
|
58
|
+
Logger(verbose=True).log(level="warning", message=f"The formation {formation} is invalid: {str(e)}. We'll recreate a formation.", color="yellow")
|
59
|
+
formation = None
|
32
60
|
|
33
61
|
try:
|
62
|
+
prompt_formation = formation.name if formation and isinstance(formation, Formation) else f"Select the best formation to effectively execute the tasks from the given Enum sets: {str(Formation.__dict__)}."
|
34
63
|
class Outcome(BaseModel):
|
35
64
|
formation: Enum
|
36
65
|
agent_roles: list[str]
|
@@ -42,73 +71,78 @@ def form_agent_network(
|
|
42
71
|
Create a team of specialized agents designed to automate the following task and deliver the expected outcome. Consider the necessary roles for each agent with a clear task description. If you think we neeed a leader to handle the automation, return a leader_agent role as well, but if not, leave the a leader_agent role blank.
|
43
72
|
Task: {str(task)}
|
44
73
|
Expected outcome: {str(expected_outcome)}
|
74
|
+
Formation: {prompt_formation}
|
45
75
|
""",
|
46
76
|
pydantic_output=Outcome
|
47
77
|
)
|
48
78
|
|
49
|
-
if formation:
|
50
|
-
vhq_task.description += f"Select 1 formation you think the best from the given Enum sets: {str(Formation.__dict__)}"
|
51
|
-
|
52
79
|
if agents:
|
53
80
|
vhq_task.description += "Consider adding following agents in the formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
|
54
81
|
|
55
82
|
res = vhq_task.execute_sync(agent=vhq_formation_planner, context=context)
|
56
|
-
|
83
|
+
_formation = Formation.SUPERVISING
|
57
84
|
|
58
85
|
if res.pydantic:
|
59
86
|
formation_keys = [k for k, v in Formation._member_map_.items() if k == res.pydantic.formation.upper()]
|
60
87
|
|
61
88
|
if formation_keys:
|
62
|
-
|
89
|
+
_formation = Formation[formation_keys[0]]
|
63
90
|
|
64
91
|
created_agents = [Agent(role=item, goal=item) for item in res.pydantic.agent_roles]
|
65
92
|
created_tasks = [Task(description=item) for item in res.pydantic.task_descriptions]
|
93
|
+
|
66
94
|
team_tasks = []
|
67
95
|
members = []
|
68
96
|
leader = str(res.pydantic.leader_agent)
|
69
97
|
|
70
98
|
for i in range(len(created_agents)):
|
71
|
-
is_manager = bool(created_agents[i].role.lower()
|
72
|
-
member =
|
99
|
+
is_manager = bool(created_agents[i].role.lower() == leader.lower())
|
100
|
+
member = Member(agent=created_agents[i], is_manager=is_manager)
|
101
|
+
|
102
|
+
if len(created_tasks) >= i and created_tasks[i]:
|
103
|
+
member.tasks.append(created_tasks[i])
|
104
|
+
|
105
|
+
members.append(member)
|
73
106
|
|
74
|
-
if len(created_tasks) >= i:
|
75
|
-
member.task = created_tasks[i]
|
76
|
-
members.append(member)
|
77
107
|
|
78
108
|
if len(created_agents) < len(created_tasks):
|
79
|
-
team_tasks.extend(created_tasks[len(created_agents)
|
109
|
+
team_tasks.extend(created_tasks[len(created_agents):len(created_tasks)])
|
80
110
|
|
81
111
|
members.sort(key=lambda x: x.is_manager == False)
|
82
|
-
team = Team(members=members, formation=
|
112
|
+
team = Team( members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
|
83
113
|
return team
|
84
114
|
|
85
115
|
else:
|
86
|
-
|
116
|
+
res = res.json_dict
|
117
|
+
formation_keys = [k for k, v in Formation._member_map_.items() if k == res["formation"].upper()]
|
87
118
|
|
88
119
|
if formation_keys:
|
89
|
-
|
120
|
+
_formation = Formation[formation_keys[0]]
|
121
|
+
|
122
|
+
created_agents = [Agent(role=item, goal=item) for item in res["agent_roles"]]
|
123
|
+
created_tasks = [Task(description=item) for item in res["task_descriptions"]]
|
90
124
|
|
91
|
-
created_agents = [Agent(role=item, goal=item) for item in res.json_dict["agent_roles"]]
|
92
|
-
created_tasks = [Task(description=item) for item in res.json_dict["task_descriptions"]]
|
93
125
|
team_tasks = []
|
94
126
|
members = []
|
95
|
-
leader = str(res
|
127
|
+
leader = str(res["leader_agent"])
|
96
128
|
|
97
129
|
for i in range(len(created_agents)):
|
98
|
-
is_manager = bool(created_agents[i].role.lower()
|
99
|
-
member =
|
130
|
+
is_manager = bool(created_agents[i].role.lower() == leader.lower())
|
131
|
+
member = Member(agent=created_agents[i], is_manager=is_manager)
|
132
|
+
|
133
|
+
if len(created_tasks) >= i and created_tasks[i]:
|
134
|
+
member.tasks.append(created_tasks[i])
|
100
135
|
|
101
|
-
|
102
|
-
member.task = created_tasks[i]
|
103
|
-
members.append(member)
|
136
|
+
members.append(member)
|
104
137
|
|
105
138
|
if len(created_agents) < len(created_tasks):
|
106
|
-
team_tasks.extend(created_tasks[len(created_agents)
|
139
|
+
team_tasks.extend(created_tasks[len(created_agents):len(created_tasks)])
|
107
140
|
|
108
|
-
members.sort(key=lambda x: x.is_manager ==
|
109
|
-
team = Team(members=members, formation=
|
141
|
+
members.sort(key=lambda x: x.is_manager == False)
|
142
|
+
team = Team( members=members, formation=_formation, team_tasks=team_tasks, planner_llm=vhq_formation_planner.llm)
|
110
143
|
return team
|
111
144
|
|
145
|
+
|
112
146
|
except Exception as e:
|
113
|
-
Logger(verbose=True).log(level="error", message=f"Failed to create
|
147
|
+
Logger(verbose=True).log(level="error", message=f"Failed to create a agent network - return None. You can try with solo agent. Error: {str(e)}", color="red")
|
114
148
|
return None
|
versionhq/task/model.py
CHANGED
@@ -203,7 +203,7 @@ class TaskOutput(BaseModel):
|
|
203
203
|
description=EVALUATE.format(task_description=task.description, task_output=self.raw, eval_criteria=str(item)),
|
204
204
|
pydantic_output=EvaluationItem
|
205
205
|
)
|
206
|
-
res = task_eval.execute_sync(agent=self.evaluation.
|
206
|
+
res = task_eval.execute_sync(agent=self.evaluation.eval_by)
|
207
207
|
|
208
208
|
if res.pydantic:
|
209
209
|
item = EvaluationItem(score=res.pydantic.score, suggestion=res.pydantic.suggestion, criteria=res.pydantic.criteria)
|
@@ -241,10 +241,7 @@ class TaskOutput(BaseModel):
|
|
241
241
|
|
242
242
|
class Task(BaseModel):
|
243
243
|
"""
|
244
|
-
|
245
|
-
Each task must have a description.
|
246
|
-
Default response is JSON string that strictly follows `response_fields` - and will be stored in TaskOuput.raw / json_dict.
|
247
|
-
When `pydantic_output` is provided, we prioritize them and store raw (json string), json_dict, pydantic in the TaskOutput class.
|
244
|
+
A class that stores independent task information.
|
248
245
|
"""
|
249
246
|
|
250
247
|
__hash__ = object.__hash__
|
versionhq/team/model.py
CHANGED
@@ -39,12 +39,12 @@ load_dotenv(override=True)
|
|
39
39
|
|
40
40
|
|
41
41
|
class Formation(str, Enum):
|
42
|
+
UNDEFINED = 0
|
42
43
|
SOLO = 1
|
43
44
|
SUPERVISING = 2
|
44
45
|
NETWORK = 3
|
45
46
|
RANDOM = 4
|
46
47
|
HYBRID = 10
|
47
|
-
UNDEFINED = 0
|
48
48
|
|
49
49
|
|
50
50
|
class TaskHandlingProcess(str, Enum):
|
@@ -75,6 +75,7 @@ class TeamOutput(TaskOutput):
|
|
75
75
|
def __str__(self):
|
76
76
|
return (str(self.pydantic) if self.pydantic else str(self.json_dict) if self.json_dict else self.raw)
|
77
77
|
|
78
|
+
|
78
79
|
def __getitem__(self, key):
|
79
80
|
if self.pydantic and hasattr(self.pydantic, key):
|
80
81
|
return getattr(self.pydantic, key)
|
@@ -85,25 +86,24 @@ class TeamOutput(TaskOutput):
|
|
85
86
|
|
86
87
|
|
87
88
|
|
88
|
-
class
|
89
|
+
class Member(BaseModel):
|
89
90
|
"""
|
90
|
-
A class to store a
|
91
|
+
A class to store a member in the network and connect the agent as a member with tasks and sharable settings.
|
91
92
|
"""
|
92
93
|
agent: Agent | None = Field(default=None)
|
93
94
|
is_manager: bool = Field(default=False)
|
94
95
|
can_share_knowledge: bool = Field(default=True, description="whether to share the agent's knowledge in the team")
|
95
96
|
can_share_memory: bool = Field(default=True, description="whether to share the agent's memory in the team")
|
96
|
-
|
97
|
+
tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks explicitly assigned to the agent")
|
97
98
|
|
98
99
|
@property
|
99
100
|
def is_idling(self):
|
100
|
-
return bool(self.
|
101
|
+
return bool(self.tasks)
|
101
102
|
|
102
103
|
|
103
104
|
class Team(BaseModel):
|
104
105
|
"""
|
105
|
-
A
|
106
|
-
We define strategies for task executions and overall workflow.
|
106
|
+
A class to store agent network that shares knowledge, memory and tools among the members.
|
107
107
|
"""
|
108
108
|
|
109
109
|
__hash__ = object.__hash__
|
@@ -113,31 +113,31 @@ class Team(BaseModel):
|
|
113
113
|
|
114
114
|
id: UUID4 = Field(default_factory=uuid.uuid4, frozen=True)
|
115
115
|
name: Optional[str] = Field(default=None)
|
116
|
-
members: List[
|
116
|
+
members: List[Member] = Field(default_factory=list)
|
117
117
|
formation: Optional[Formation] = Field(default=None)
|
118
|
+
should_reform: bool = Field(default=False, description="True if task exe. failed or eval scores below threshold")
|
118
119
|
|
119
120
|
# formation planning
|
120
|
-
|
121
|
-
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="
|
121
|
+
planner_llm: Optional[Any] = Field(default=None, description="llm to generate and evaluate formation")
|
122
|
+
team_tasks: Optional[List[Task]] = Field(default_factory=list, description="tasks without dedicated agents to handle")
|
122
123
|
|
123
124
|
# task execution rules
|
124
|
-
prompt_file: str = Field(default="", description="path to the prompt json file
|
125
|
+
prompt_file: str = Field(default="", description="absolute path to the prompt json file")
|
125
126
|
process: TaskHandlingProcess = Field(default=TaskHandlingProcess.sequential)
|
126
127
|
|
127
128
|
# callbacks
|
128
|
-
|
129
|
+
pre_launch_callbacks: List[Callable[[Optional[Dict[str, Any]]], Optional[Dict[str, Any]]]] = Field(
|
129
130
|
default_factory=list,
|
130
|
-
description="list of callback functions to be executed before the team
|
131
|
+
description="list of callback functions to be executed before the team launch. i.e., adjust inputs"
|
131
132
|
)
|
132
|
-
|
133
|
+
post_launch_callbacks: List[Callable[[TeamOutput], TeamOutput]] = Field(
|
133
134
|
default_factory=list,
|
134
|
-
description="list of callback functions to be executed after the team
|
135
|
+
description="list of callback functions to be executed after the team launch. i.e., store the result in repo"
|
135
136
|
)
|
136
137
|
step_callback: Optional[Any] = Field(default=None, description="callback to be executed after each step for all agents execution")
|
137
138
|
|
138
139
|
cache: bool = Field(default=True)
|
139
|
-
|
140
|
-
execution_logs: List[Dict[str, Any]] = Field(default=[], description="list of execution logs for tasks")
|
140
|
+
execution_logs: List[Dict[str, Any]] = Field(default_factory=list, description="list of execution logs of the tasks handled by members")
|
141
141
|
usage_metrics: Optional[UsageMetrics] = Field(default=None, description="usage metrics for all the llm executions")
|
142
142
|
|
143
143
|
|
@@ -163,40 +163,45 @@ class Team(BaseModel):
|
|
163
163
|
if all(task in self.tasks for task in self.team_tasks) == False:
|
164
164
|
raise PydanticCustomError("task_validation_error", "`team_tasks` needs to be recognized in the task.", {})
|
165
165
|
|
166
|
-
|
167
|
-
|
166
|
+
|
167
|
+
num_member_tasks = 0
|
168
|
+
for member in self.members:
|
169
|
+
num_member_tasks += len(member.tasks)
|
170
|
+
|
171
|
+
# if len(self.tasks) != len(self.team_tasks) + num_member_tasks:
|
172
|
+
# raise PydanticCustomError("task_validation_error", "Some tasks are missing.", {})
|
168
173
|
return self
|
169
174
|
|
170
175
|
|
171
176
|
@model_validator(mode="after")
|
172
177
|
def check_manager_llm(self):
|
173
178
|
"""
|
174
|
-
|
179
|
+
Check if the team has a manager
|
175
180
|
"""
|
176
181
|
|
177
|
-
if self.process == TaskHandlingProcess.hierarchical:
|
178
|
-
if self.managers
|
182
|
+
if self.process == TaskHandlingProcess.hierarchical or self.formation == Formation.SUPERVISING:
|
183
|
+
if not self.managers:
|
184
|
+
self._logger.log(level="error", message="The process or formation created needs at least 1 manager agent.", color="red")
|
179
185
|
raise PydanticCustomError(
|
180
|
-
"missing_manager_llm_or_manager",
|
181
|
-
"Attribute `manager_llm` or `manager` is required when using hierarchical process.",
|
182
|
-
{},
|
183
|
-
)
|
186
|
+
"missing_manager_llm_or_manager","Attribute `manager_llm` or `manager` is required when using hierarchical process.", {})
|
184
187
|
|
185
|
-
|
186
|
-
|
188
|
+
## comment out for the formation flexibilities
|
189
|
+
# if self.managers and (self.manager_tasks is None or self.team_tasks is None):
|
190
|
+
# self._logger.log(level="error", message="The manager is idling. At least 1 task needs to be assigned to the manager.", color="red")
|
191
|
+
# raise PydanticCustomError("missing_manager_task", "manager needs to have at least one manager task or team task.", {})
|
187
192
|
|
188
193
|
return self
|
189
194
|
|
190
195
|
|
191
196
|
@model_validator(mode="after")
|
192
|
-
def
|
197
|
+
def validate_task_member_paring(self):
|
193
198
|
"""
|
194
199
|
Sequential task processing without any team tasks require a task-agent pairing.
|
195
200
|
"""
|
196
|
-
|
197
201
|
if self.process == TaskHandlingProcess.sequential and self.team_tasks is None:
|
198
|
-
for
|
199
|
-
if member.task
|
202
|
+
for task in self.tasks:
|
203
|
+
if not [member.task == task for member in self.members]:
|
204
|
+
self._logger.log(level="error", message=f"The following task needs a dedicated agent to be assinged: {task.description}", color="red")
|
200
205
|
raise PydanticCustomError("missing_agent_in_task", "Sequential process error: Agent is missing the task", {})
|
201
206
|
return self
|
202
207
|
|
@@ -224,11 +229,14 @@ class Team(BaseModel):
|
|
224
229
|
if task is None:
|
225
230
|
return None
|
226
231
|
else:
|
227
|
-
|
228
|
-
|
232
|
+
for member in self.members:
|
233
|
+
if member.tasks and [item for item in member.tasks if item.id == task.id]:
|
234
|
+
return member.agent
|
235
|
+
|
236
|
+
return None
|
229
237
|
|
230
238
|
|
231
|
-
def
|
239
|
+
def _assign_tasks(self) -> None:
|
232
240
|
"""
|
233
241
|
Form a team considering agents and tasks given, and update `self.members` field:
|
234
242
|
1. Idling managers to take the team tasks.
|
@@ -236,42 +244,26 @@ class Team(BaseModel):
|
|
236
244
|
3. Create agents to handle the rest tasks.
|
237
245
|
"""
|
238
246
|
|
239
|
-
team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.
|
240
|
-
idling_managers: List[
|
241
|
-
idling_members: List[
|
242
|
-
unassigned_tasks: List[Task] = self.member_tasks_without_agent
|
243
|
-
new_team_members: List[
|
244
|
-
|
245
|
-
if self.team_tasks:
|
246
|
-
candidates = idling_managers + idling_members
|
247
|
-
if candidates:
|
248
|
-
i = 0
|
249
|
-
while i < len(candidates):
|
250
|
-
if self.team_tasks[i]:
|
251
|
-
candidates[i].task = self.team_tasks[i]
|
252
|
-
i += 1
|
253
|
-
|
254
|
-
if len(self.team_tasks) > i:
|
255
|
-
for item in self.team_tasks[i:]:
|
256
|
-
if item not in unassigned_tasks:
|
257
|
-
unassigned_tasks = [item, ] + unassigned_tasks
|
247
|
+
team_planner = TeamPlanner(tasks=self.tasks, planner_llm=self.planner_llm)
|
248
|
+
idling_managers: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == True]
|
249
|
+
idling_members: List[Member] = [member for member in self.members if member.is_idling and member.is_manager == False]
|
250
|
+
unassigned_tasks: List[Task] = self.team_tasks + self.member_tasks_without_agent if self.team_tasks else self.member_tasks_without_agent
|
251
|
+
new_team_members: List[Member] = []
|
258
252
|
|
259
|
-
|
260
|
-
|
261
|
-
if item not in unassigned_tasks:
|
262
|
-
unassigned_tasks = [item, ] + unassigned_tasks
|
253
|
+
if idling_managers:
|
254
|
+
idling_managers[0].tasks.extend(unassigned_tasks)
|
263
255
|
|
264
|
-
|
265
|
-
|
256
|
+
elif idling_members:
|
257
|
+
idling_members[0].tasks.extend(unassigned_tasks)
|
266
258
|
|
267
|
-
|
268
|
-
|
259
|
+
else:
|
260
|
+
new_team_members = team_planner._handle_assign_agents(unassigned_tasks=unassigned_tasks)
|
261
|
+
if new_team_members:
|
262
|
+
self.members += new_team_members
|
269
263
|
|
270
264
|
|
271
265
|
# task execution
|
272
|
-
def _process_async_tasks(
|
273
|
-
self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False
|
274
|
-
) -> List[TaskOutput]:
|
266
|
+
def _process_async_tasks(self, futures: List[Tuple[Task, Future[TaskOutput], int]], was_replayed: bool = False) -> List[TaskOutput]:
|
275
267
|
"""
|
276
268
|
When we have `Future` tasks, updated task outputs and task execution logs accordingly.
|
277
269
|
"""
|
@@ -292,10 +284,11 @@ class Team(BaseModel):
|
|
292
284
|
Note that `tasks` are already sorted by the importance.
|
293
285
|
"""
|
294
286
|
|
295
|
-
if
|
296
|
-
|
287
|
+
if not task_outputs:
|
288
|
+
self._logger.log(level="error", message="Missing task outcomes. Failed to launch the task.", color="red")
|
289
|
+
raise ValueError("Failed to launch tasks")
|
297
290
|
|
298
|
-
final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0]
|
291
|
+
final_task_output = lead_task_output if lead_task_output is not None else task_outputs[0] #! REFINEME
|
299
292
|
# final_string_output = final_task_output.raw
|
300
293
|
# self._finish_execution(final_string_output)
|
301
294
|
token_usage = self._calculate_usage_metrics()
|
@@ -357,7 +350,7 @@ class Team(BaseModel):
|
|
357
350
|
|
358
351
|
responsible_agent = self._get_responsible_agent(task)
|
359
352
|
if responsible_agent is None:
|
360
|
-
self.
|
353
|
+
self._assign_tasks()
|
361
354
|
|
362
355
|
if isinstance(task, ConditionalTask):
|
363
356
|
skipped_task_output = task._handle_conditional_task(task_outputs, futures, task_index, was_replayed)
|
@@ -387,29 +380,27 @@ class Team(BaseModel):
|
|
387
380
|
return self._create_team_output(task_outputs, lead_task_output)
|
388
381
|
|
389
382
|
|
390
|
-
def launch(self,
|
383
|
+
def launch(self, kwargs_pre: Optional[Dict[str, str]] = None, kwargs_post: Optional[Dict[str, Any]] = None) -> TeamOutput:
|
391
384
|
"""
|
392
385
|
Confirm and launch the formation - execute tasks and record outputs.
|
393
|
-
0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and
|
394
|
-
1. Address `
|
386
|
+
0. Assign an agent to a task - using conditions (manager prioritizes team_tasks) and planner_llm.
|
387
|
+
1. Address `pre_launch_callbacks` if any.
|
395
388
|
2. Handle team members' tasks in accordance with the process.
|
396
|
-
3. Address `
|
389
|
+
3. Address `post_launch_callbacks` if any.
|
397
390
|
"""
|
398
391
|
|
399
392
|
metrics: List[UsageMetrics] = []
|
400
393
|
|
401
394
|
if self.team_tasks or self.member_tasks_without_agent:
|
402
|
-
self.
|
395
|
+
self._assign_tasks()
|
403
396
|
|
404
|
-
if
|
405
|
-
for
|
406
|
-
|
397
|
+
if kwargs_pre is not None:
|
398
|
+
for func in self.pre_launch_callbacks:
|
399
|
+
func(**kwargs_pre)
|
407
400
|
|
408
401
|
# self._execution_span = self._telemetry.team_execution_span(self, inputs)
|
409
402
|
# self._task_output_handler.reset()
|
410
403
|
# self._logging_color = "bold_purple"
|
411
|
-
|
412
|
-
|
413
404
|
# i18n = I18N(prompt_file=self.prompt_file)
|
414
405
|
|
415
406
|
for member in self.members:
|
@@ -424,8 +415,8 @@ class Team(BaseModel):
|
|
424
415
|
|
425
416
|
result = self._execute_tasks(self.tasks)
|
426
417
|
|
427
|
-
for
|
428
|
-
result =
|
418
|
+
for func in self.post_launch_callbacks:
|
419
|
+
result = func(result, **kwargs_post)
|
429
420
|
|
430
421
|
metrics += [member.agent._token_process.get_summary() for member in self.members]
|
431
422
|
|
@@ -443,25 +434,28 @@ class Team(BaseModel):
|
|
443
434
|
|
444
435
|
|
445
436
|
@property
|
446
|
-
def managers(self) -> List[
|
437
|
+
def managers(self) -> List[Member] | None:
|
447
438
|
managers = [member for member in self.members if member.is_manager == True]
|
448
439
|
return managers if len(managers) > 0 else None
|
449
440
|
|
450
441
|
|
451
442
|
@property
|
452
|
-
def manager_tasks(self) -> List[Task]
|
443
|
+
def manager_tasks(self) -> List[Task]:
|
453
444
|
"""
|
454
445
|
Tasks (incl. team tasks) handled by managers in the team.
|
455
446
|
"""
|
447
|
+
res = list()
|
448
|
+
|
456
449
|
if self.managers:
|
457
|
-
|
458
|
-
|
450
|
+
for manager in self.managers:
|
451
|
+
if manager.tasks:
|
452
|
+
res.extend(manager.tasks)
|
459
453
|
|
460
|
-
return
|
454
|
+
return res
|
461
455
|
|
462
456
|
|
463
457
|
@property
|
464
|
-
def tasks(self):
|
458
|
+
def tasks(self) -> List[Task]:
|
465
459
|
"""
|
466
460
|
Return all the tasks that the team needs to handle in order of priority:
|
467
461
|
1. team tasks, -> assigned to the member
|
@@ -470,15 +464,24 @@ class Team(BaseModel):
|
|
470
464
|
"""
|
471
465
|
|
472
466
|
team_tasks = self.team_tasks
|
473
|
-
manager_tasks =
|
474
|
-
member_tasks = [
|
467
|
+
manager_tasks = self.manager_tasks
|
468
|
+
member_tasks = []
|
469
|
+
|
470
|
+
for member in self.members:
|
471
|
+
if member.is_manager == False and member.tasks:
|
472
|
+
a = [item for item in member.tasks if item not in team_tasks and item not in manager_tasks]
|
473
|
+
member_tasks += a
|
475
474
|
|
476
475
|
return team_tasks + manager_tasks + member_tasks
|
477
476
|
|
478
477
|
|
479
478
|
@property
|
480
|
-
def member_tasks_without_agent(self) -> List[Task]
|
479
|
+
def member_tasks_without_agent(self) -> List[Task]:
|
480
|
+
res = list()
|
481
|
+
|
481
482
|
if self.members:
|
482
|
-
|
483
|
+
for member in self.members:
|
484
|
+
if member.agent is None and member.tasks:
|
485
|
+
res.extend(member.tasks)
|
483
486
|
|
484
|
-
return
|
487
|
+
return res
|
versionhq/team/team_planner.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
import os
|
2
|
-
from dotenv import load_dotenv
|
3
2
|
from typing import Any, List, Optional, Dict
|
4
3
|
from pydantic import BaseModel, Field
|
5
4
|
|
6
|
-
|
5
|
+
|
7
6
|
|
8
7
|
|
9
8
|
class TeamPlanner:
|
@@ -26,14 +25,14 @@ class TeamPlanner:
|
|
26
25
|
|
27
26
|
def _handle_assign_agents(self, unassigned_tasks: List[Task]) -> List[Any]:
|
28
27
|
"""
|
29
|
-
Build an agent and assign it a task, then return a list of
|
28
|
+
Build an agent and assign it a task, then return a list of Member connecting the agent created and the task given.
|
30
29
|
"""
|
31
30
|
|
32
31
|
from versionhq.agent.model import Agent
|
33
32
|
from versionhq.task.model import Task, ResponseField
|
34
|
-
from versionhq.team.model import
|
33
|
+
from versionhq.team.model import Member
|
35
34
|
|
36
|
-
new_member_list: List[
|
35
|
+
new_member_list: List[Member] = []
|
37
36
|
agent_creator = Agent(
|
38
37
|
role="agent_creator",
|
39
38
|
goal="build an ai agent that can competitively handle the task given",
|
@@ -57,7 +56,7 @@ class TeamPlanner:
|
|
57
56
|
goal=res.json_dict["goal"] if "goal" in res.json_dict else task.description
|
58
57
|
)
|
59
58
|
if agent.id:
|
60
|
-
team_member =
|
59
|
+
team_member = Member(agent=agent, tasks=[unassgined_task], is_manager=False)
|
61
60
|
new_member_list.append(team_member)
|
62
61
|
|
63
62
|
return new_member_list
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: versionhq
|
3
|
-
Version: 1.1.
|
4
|
-
Summary: An agentic orchestration framework for building agent networks that handle task automation
|
3
|
+
Version: 1.1.13.0
|
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
|
7
7
|
|
@@ -40,7 +40,7 @@ Classifier: Development Status :: 3 - Alpha
|
|
40
40
|
Classifier: Intended Audience :: Developers
|
41
41
|
Classifier: Intended Audience :: Information Technology
|
42
42
|
Classifier: Topic :: Software Development :: Build Tools
|
43
|
-
Requires-Python:
|
43
|
+
Requires-Python: <3.13,>=3.11
|
44
44
|
Description-Content-Type: text/markdown
|
45
45
|
License-File: LICENSE
|
46
46
|
Requires-Dist: regex==2024.11.6
|
@@ -77,7 +77,7 @@ Requires-Dist: numpy>=1.26.4; extra == "numpy"
|
|
77
77
|
|
78
78
|
# Overview
|
79
79
|
|
80
|
-

|
80
|
+
[](https://clickpy.clickhouse.com/dashboard/versionhq)
|
81
81
|

|
82
82
|
[](https://github.com/versionHQ/multi-agent-system/actions/workflows/publish.yml)
|
83
83
|

|
@@ -85,7 +85,7 @@ Requires-Dist: numpy>=1.26.4; extra == "numpy"
|
|
85
85
|

|
86
86
|
|
87
87
|
|
88
|
-
|
88
|
+
A Python framework for agentic orchestration that handles complex task automation without human interaction.
|
89
89
|
|
90
90
|
**Visit:**
|
91
91
|
|
@@ -104,19 +104,17 @@ Agentic orchestration framework to deploy agent network and handle complex task
|
|
104
104
|
- [Key Features](#key-features)
|
105
105
|
- [Agent formation](#agent-formation)
|
106
106
|
- [Quick Start](#quick-start)
|
107
|
-
- [
|
108
|
-
- [
|
109
|
-
- [
|
110
|
-
- [Supervising
|
107
|
+
- [Package installation](#package-installation)
|
108
|
+
- [Forming a agent network](#forming-a-agent-network)
|
109
|
+
- [Customizing AI agents](#customizing-ai-agents)
|
110
|
+
- [Supervising](#supervising)
|
111
111
|
- [Technologies Used](#technologies-used)
|
112
112
|
- [Project Structure](#project-structure)
|
113
|
-
- [
|
113
|
+
- [Setting Up](#setting-up)
|
114
114
|
- [Contributing](#contributing)
|
115
|
-
- [Documentation](#documentation)
|
116
|
-
- [Customizing AI Agent](#customizing-ai-agent)
|
117
|
-
- [Modifying RAG Functionality](#modifying-rag-functionality)
|
118
115
|
- [Package Management with uv](#package-management-with-uv)
|
119
116
|
- [Pre-Commit Hooks](#pre-commit-hooks)
|
117
|
+
- [Documentation](#documentation)
|
120
118
|
- [Trouble Shooting](#trouble-shooting)
|
121
119
|
- [Frequently Asked Questions (FAQ)](#frequently-asked-questions-faq)
|
122
120
|
|
@@ -148,20 +146,20 @@ You can specify a desired formation or allow the agents to determine it autonomo
|
|
148
146
|
|
149
147
|
## Quick Start
|
150
148
|
|
151
|
-
|
149
|
+
### Package installation
|
152
150
|
|
153
151
|
```
|
154
152
|
pip install versionhq
|
155
153
|
```
|
156
154
|
|
157
|
-
(Python 3.11
|
155
|
+
(Python 3.11 / 3.12)
|
158
156
|
|
159
|
-
###
|
157
|
+
### Forming a agent network
|
160
158
|
|
161
159
|
```python
|
162
|
-
|
160
|
+
import versionhq as vhq
|
163
161
|
|
164
|
-
network = form_agent_network(
|
162
|
+
network = vhq.form_agent_network(
|
165
163
|
task="YOUR AMAZING TASK OVERVIEW",
|
166
164
|
expected_outcome="YOUR OUTCOME EXPECTATION",
|
167
165
|
)
|
@@ -171,31 +169,28 @@ You can specify a desired formation or allow the agents to determine it autonomo
|
|
171
169
|
This will form a network with multiple agents on `Formation` and return `TaskOutput` object with output in JSON, plane text, Pydantic model format with evaluation.
|
172
170
|
|
173
171
|
|
174
|
-
###
|
175
|
-
|
176
|
-
### Solo Agent:
|
172
|
+
### Customizing AI agents
|
177
173
|
|
178
174
|
You can simply build an agent using `Agent` model.
|
179
175
|
|
180
|
-
By default, the agent prioritize JSON serializable
|
176
|
+
By default, the agent prioritize JSON serializable outputs over plane texts.
|
181
177
|
|
182
|
-
But you can add a plane text summary of the structured output by using callbacks.
|
183
178
|
|
184
179
|
```python
|
180
|
+
import versionhq as vhq
|
185
181
|
from pydantic import BaseModel
|
186
|
-
from versionhq import Agent, Task
|
187
182
|
|
188
183
|
class CustomOutput(BaseModel):
|
189
184
|
test1: str
|
190
185
|
test2: list[str]
|
191
186
|
|
192
187
|
def dummy_func(message: str, test1: str, test2: list[str]) -> str:
|
193
|
-
return f"{message}: {test1}, {", ".join(test2)}"
|
188
|
+
return f"""{message}: {test1}, {", ".join(test2)}"""
|
194
189
|
|
195
190
|
|
196
|
-
agent = Agent(role="demo", goal="amazing project goal")
|
191
|
+
agent = vhq.Agent(role="demo", goal="amazing project goal")
|
197
192
|
|
198
|
-
task = Task(
|
193
|
+
task = vhq.Task(
|
199
194
|
description="Amazing task",
|
200
195
|
pydantic_output=CustomOutput,
|
201
196
|
callback=dummy_func,
|
@@ -221,33 +216,33 @@ This will return a `TaskOutput` object that stores response in plane text, JSON,
|
|
221
216
|
)
|
222
217
|
```
|
223
218
|
|
224
|
-
### Supervising
|
219
|
+
### Supervising
|
225
220
|
|
226
221
|
```python
|
227
|
-
|
222
|
+
import versionhq as vhq
|
228
223
|
|
229
|
-
agent_a = Agent(role="agent a", goal="My amazing goals", llm="llm-of-your-choice")
|
230
|
-
agent_b = Agent(role="agent b", goal="My amazing goals", llm="llm-of-your-choice")
|
224
|
+
agent_a = vhq.Agent(role="agent a", goal="My amazing goals", llm="llm-of-your-choice")
|
225
|
+
agent_b = vhq.Agent(role="agent b", goal="My amazing goals", llm="llm-of-your-choice")
|
231
226
|
|
232
|
-
task_1 = Task(
|
227
|
+
task_1 = vhq.Task(
|
233
228
|
description="Analyze the client's business model.",
|
234
|
-
response_fields=[ResponseField(title="test1", data_type=str, required=True),],
|
229
|
+
response_fields=[vhq.ResponseField(title="test1", data_type=str, required=True),],
|
235
230
|
allow_delegation=True
|
236
231
|
)
|
237
232
|
|
238
|
-
task_2 = Task(
|
233
|
+
task_2 = vhq.Task(
|
239
234
|
description="Define the cohort.",
|
240
235
|
response_fields=[ResponseField(title="test1", data_type=int, required=True),],
|
241
236
|
allow_delegation=False
|
242
237
|
)
|
243
238
|
|
244
|
-
team = Team(
|
239
|
+
team = vhq.Team(
|
245
240
|
members=[
|
246
|
-
|
247
|
-
|
241
|
+
vhq.Member(agent=agent_a, is_manager=False, task=task_1),
|
242
|
+
vhq.Member(agent=agent_b, is_manager=True, task=task_2),
|
248
243
|
],
|
249
244
|
)
|
250
|
-
res = team.
|
245
|
+
res = team.launch()
|
251
246
|
```
|
252
247
|
|
253
248
|
This will return a list with dictionaries with keys defined in the `ResponseField` of each task.
|
@@ -257,27 +252,33 @@ Tasks can be delegated to a team manager, peers in the team, or completely new a
|
|
257
252
|
<hr />
|
258
253
|
|
259
254
|
## Technologies Used
|
255
|
+
|
260
256
|
**Schema, Data Validation**
|
261
|
-
|
262
|
-
|
263
|
-
|
257
|
+
|
258
|
+
* [Pydantic](https://docs.pydantic.dev/latest/): Data validation and serialization library for Python.
|
259
|
+
* [Upstage](https://console.upstage.ai/docs/getting-started/overview): Document processer for ML tasks. (Use `Document Parser API` to extract data from documents)
|
260
|
+
* [Docling](https://ds4sd.github.io/docling/): Document parsing
|
264
261
|
|
265
262
|
**Storage**
|
266
|
-
|
267
|
-
|
268
|
-
|
263
|
+
|
264
|
+
* [mem0ai](https://docs.mem0.ai/quickstart#install-package): Agents' memory storage and management.
|
265
|
+
* [Chroma DB](https://docs.trychroma.com/): Vector database for storing and querying usage data.
|
266
|
+
* [SQLite](https://www.sqlite.org/docs.html): C-language library to implements a small SQL database engine.
|
269
267
|
|
270
268
|
**LLM-curation**
|
271
|
-
|
269
|
+
|
270
|
+
* [LiteLLM](https://docs.litellm.ai/docs/providers): Curation platform to access LLMs
|
272
271
|
|
273
272
|
**Tools**
|
274
|
-
|
273
|
+
|
274
|
+
* [Composio](https://composio.dev/): Conect RAG agents with external tools, Apps, and APIs to perform actions and receive triggers. We use [tools](https://composio.dev/tools) and [RAG tools](https://app.composio.dev/app/ragtool) from Composio toolset.
|
275
275
|
|
276
276
|
**Deployment**
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
277
|
+
|
278
|
+
* **Python**: Primary programming language. v3.12.x is recommended
|
279
|
+
* [uv](https://docs.astral.sh/uv/): Python package installer and resolver
|
280
|
+
* [pre-commit](https://pre-commit.com/): Manage and maintain pre-commit hooks
|
281
|
+
* [setuptools](https://pypi.org/project/setuptools/): Build python modules
|
281
282
|
|
282
283
|
<hr />
|
283
284
|
|
@@ -309,7 +310,7 @@ src/
|
|
309
310
|
|
310
311
|
<hr />
|
311
312
|
|
312
|
-
##
|
313
|
+
## Setting Up
|
313
314
|
|
314
315
|
1. Install `uv` package manager:
|
315
316
|
|
@@ -339,12 +340,12 @@ src/
|
|
339
340
|
pyenv install 3.12.8
|
340
341
|
pyenv global 3.12.8 (optional: `pyenv global system` to get back to the system default ver.)
|
341
342
|
uv python pin 3.12.8
|
342
|
-
echo 3.12.8
|
343
|
+
echo 3.12.8 >> .python-version
|
343
344
|
```
|
344
345
|
|
345
346
|
|
346
|
-
3.
|
347
|
-
|
347
|
+
3. Add secrets to `.env` file in the project root:
|
348
|
+
|
348
349
|
```
|
349
350
|
LITELLM_API_KEY=your-litellm-api-key
|
350
351
|
OPENAI_API_KEY=your-openai-api-key
|
@@ -361,68 +362,37 @@ src/
|
|
361
362
|
|
362
363
|
2. Create amazing features
|
363
364
|
|
364
|
-
3.
|
365
|
+
3. Add a test funcition to the `tests` directory and run **pytest**.
|
365
366
|
|
366
|
-
- Add
|
367
|
-
-
|
368
|
-
- Run a test.
|
367
|
+
- Add secret values defined in `.github/workflows/run_test.yml` to your Github `repository secrets` located at settings > secrets & variables > Actions.
|
368
|
+
- Run a following command:
|
369
369
|
```
|
370
370
|
uv run pytest tests -vv --cache-clear
|
371
371
|
```
|
372
372
|
|
373
|
-
**pytest**
|
374
|
-
|
375
|
-
* When adding a new file to `tests`, name the file ended with `_test.py`.
|
376
|
-
* When adding a new feature to the file, name the feature started with `test_`.
|
377
|
-
|
378
|
-
4. Pull the latest version of source code from the main branch (`git pull origin main`) *Address conflicts if any.
|
379
|
-
5. Commit your changes (`git add .` / `git commit -m 'Add your-amazing-feature'`)
|
380
|
-
6. Push to the branch (`git push origin feature/your-amazing-feature`)
|
381
|
-
7. Open a pull request
|
382
|
-
|
383
|
-
|
384
|
-
**Optional**
|
385
|
-
* Flag with `#! REFINEME` for any improvements needed and `#! FIXME` for any errors.
|
386
|
-
|
387
|
-
<!-- * Run a React demo app: [React demo app](https://github.com/versionHQ/test-client-app) to check it on the client endpoint.
|
388
|
-
```
|
389
|
-
npm i
|
390
|
-
npm start
|
391
|
-
```
|
392
|
-
The frontend will be available at `http://localhost:3000`. -->
|
393
|
-
|
394
|
-
* `production` use case is available at `https://versi0n.io`. Currently, we are running alpha test.
|
395
|
-
|
396
|
-
|
397
|
-
### Documentation
|
398
|
-
|
399
|
-
* To edit the documentation, see `docs` repository and edit the respective component.
|
373
|
+
**Building a new pytest function**
|
400
374
|
|
401
|
-
*
|
375
|
+
* Files added to the `tests` directory must end in `_test.py`.
|
402
376
|
|
403
|
-
|
404
|
-
uv run python3 -m mkdocs serve --clean
|
405
|
-
```
|
377
|
+
* Test functions within the files must begin with `test_`.
|
406
378
|
|
407
|
-
* To add a new page, update `mkdocs.yml` in the root. Refer to [MkDocs official docs](https://squidfunk.github.io/mkdocs-material/getting-started/) for more details.
|
408
379
|
|
380
|
+
4. Update `docs` accordingly.
|
409
381
|
|
410
|
-
|
382
|
+
5. Pull the latest version of source code from the main branch (`git pull origin main`) *Address conflicts if any.
|
411
383
|
|
412
|
-
|
384
|
+
6. Commit your changes (`git add .` / `git commit -m 'Add your-amazing-feature'`)
|
413
385
|
|
414
|
-
|
386
|
+
7. Push to the branch (`git push origin feature/your-amazing-feature`)
|
415
387
|
|
416
|
-
|
388
|
+
8. Open a pull request
|
417
389
|
|
418
390
|
|
419
|
-
|
391
|
+
**Optional**
|
420
392
|
|
421
|
-
|
393
|
+
* Flag with `#! REFINEME` for any improvements needed and `#! FIXME` for any errors.
|
422
394
|
|
423
|
-
|
424
|
-
2. Modify the `tools.py` file to update the ingestion process if necessary.
|
425
|
-
3. Run the ingestion process to update the Chroma DB.
|
395
|
+
* `Playground` is available at `https://versi0n.io`.
|
426
396
|
|
427
397
|
|
428
398
|
### Package Management with uv
|
@@ -453,19 +423,39 @@ Pre-commit hooks help maintain code quality by running checks for formatting, li
|
|
453
423
|
git commit --no-verify -m "your-commit-message"
|
454
424
|
```
|
455
425
|
|
426
|
+
### Documentation
|
427
|
+
|
428
|
+
* To edit the documentation, see `docs` repository and edit the respective component.
|
429
|
+
|
430
|
+
* We use `mkdocs` to update the docs. You can run the doc locally at http://127.0.0.1:8000/:
|
431
|
+
|
432
|
+
```
|
433
|
+
uv run python3 -m mkdocs serve --clean
|
434
|
+
```
|
435
|
+
|
436
|
+
* To add a new page, update `mkdocs.yml` in the root. Refer to [MkDocs documentation](https://squidfunk.github.io/mkdocs-material/getting-started/) for more details.
|
437
|
+
|
456
438
|
<hr />
|
457
439
|
|
458
440
|
## Trouble Shooting
|
459
441
|
|
460
442
|
Common issues and solutions:
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
443
|
+
|
444
|
+
* API key errors: Ensure all API keys in the `.env` file are correct and up to date. Make sure to add `load_dotenv()` on the top of the python file to apply the latest environment values.
|
445
|
+
|
446
|
+
* Database connection issues: Check if the Chroma DB is properly initialized and accessible.
|
447
|
+
|
448
|
+
* Memory errors: If processing large contracts, you may need to increase the available memory for the Python process.
|
449
|
+
|
450
|
+
* Issues related to the Python version: Docling/Pytorch is not ready for Python 3.13 as of Jan 2025. Use Python 3.12.x as default by running `uv venv --python 3.12.8` and `uv python pin 3.12.8`.
|
451
|
+
|
452
|
+
* Issues related to dependencies: `rm -rf uv.lock`, `uv cache clean`, `uv venv`, and run `uv pip install -r requirements.txt -v`.
|
453
|
+
|
454
|
+
* Issues related to the AI agents or RAG system: Check the `output.log` file for detailed error messages and stack traces.
|
455
|
+
|
456
|
+
* Issues related to `Python quit unexpectedly`: Check [this stackoverflow article](https://stackoverflow.com/questions/59888499/macos-catalina-python-quit-unexpectedly-error).
|
457
|
+
|
458
|
+
* `reportMissingImports` error from pyright after installing the package: This might occur when installing new libraries while VSCode is running. Open the command pallete (ctrl + shift + p) and run the Python: Restart language server task.
|
469
459
|
|
470
460
|
<hr />
|
471
461
|
|
@@ -477,5 +467,5 @@ A. Visit [playground](https://versi0n.io).
|
|
477
467
|
|
478
468
|
**Q. How do you analyze the customer?**
|
479
469
|
|
480
|
-
|
481
|
-
|
470
|
+
A. We employ soft clustering for each customer.
|
471
|
+
<img width="200" src="https://res.cloudinary.com/dfeirxlea/image/upload/v1732732628/pj_m_agents/ito937s5d5x0so8isvw6.png">
|
@@ -1,12 +1,12 @@
|
|
1
|
-
versionhq/__init__.py,sha256=
|
1
|
+
versionhq/__init__.py,sha256=lfts4IiFxMVN-gRb7eoit56jqfMgOSMG1C8803J_-UQ,2400
|
2
2
|
versionhq/_utils/__init__.py,sha256=dzoZr4cBlh-2QZuPzTdehPUCe9lP1dmRtauD7qTjUaA,158
|
3
3
|
versionhq/_utils/i18n.py,sha256=TwA_PnYfDLA6VqlUDPuybdV9lgi3Frh_ASsb_X8jJo8,1483
|
4
4
|
versionhq/_utils/logger.py,sha256=j9SlQPIefdVUlwpGfJY83E2BUt1ejWgZ2M2I8aMyQ3c,1579
|
5
5
|
versionhq/_utils/process_config.py,sha256=jbPGXK2Kb4iyCugJ3FwRJuU0wL5Trq2x4xFQz2uOyFY,746
|
6
|
-
versionhq/_utils/usage_metrics.py,sha256=
|
6
|
+
versionhq/_utils/usage_metrics.py,sha256=NXF18dn5NNvGK7EsQ4AAghpR8ppYOjMx6ABenLLHnmM,1066
|
7
7
|
versionhq/_utils/vars.py,sha256=bZ5Dx_bFKlt3hi4-NNGXqdk7B23If_WaTIju2fiTyPQ,57
|
8
8
|
versionhq/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
versionhq/agent/inhouse_agents.py,sha256=
|
9
|
+
versionhq/agent/inhouse_agents.py,sha256=vSobrH1gXDWlaNsiges3sqETeUrEssRzQvCZCY2hQZA,2374
|
10
10
|
versionhq/agent/model.py,sha256=8aJ4rdgGEph10DuB8zhJkiRWzQTZ2LGKNq1MTeQ9hM8,23342
|
11
11
|
versionhq/agent/parser.py,sha256=riG0dkdQCxH7uJ0AbdVdg7WvL0BXhUgJht0VtQvxJBc,4082
|
12
12
|
versionhq/agent/rpm_controller.py,sha256=grezIxyBci_lDlwAlgWFRyR5KOocXeOhYkgN02dNFNE,2360
|
@@ -28,7 +28,7 @@ versionhq/knowledge/source.py,sha256=30VXsl3uHdM0wK0Dik3XfFxpNpEiy539PBNBvg0Y4-g
|
|
28
28
|
versionhq/knowledge/source_docling.py,sha256=hhHn3rS4KVsFKEPWcfllM8VxSL86PckZdAHDZNQNOq8,5411
|
29
29
|
versionhq/knowledge/storage.py,sha256=7oxCg3W9mFjYH1YmuH9kFtTbNxquzYFjuUjd_TlsB9E,8170
|
30
30
|
versionhq/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
-
versionhq/llm/llm_vars.py,sha256=
|
31
|
+
versionhq/llm/llm_vars.py,sha256=3fax7EXNwCw1yapIoqRMmwgGmK3O37Wm1e8uvq8ObL4,7063
|
32
32
|
versionhq/llm/model.py,sha256=QacjThF43Vfel6LIvSt5KkOZAbzo1jYjwFgFfhrv7ms,17174
|
33
33
|
versionhq/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
34
|
versionhq/memory/contextual_memory.py,sha256=tCsOOAUnfrOL7YiakqGoi3uShzzS870TmGnlGd3z_A4,3556
|
@@ -41,16 +41,16 @@ versionhq/storage/rag_storage.py,sha256=ScWC0vH327vnGw8UGscAOoIfqrq3mhvXT3vEKzHZ
|
|
41
41
|
versionhq/storage/task_output_storage.py,sha256=E1t_Fkt78dPYIOl3MP7LfQ8oGtjlzxBuSNq_8ZXKho8,4573
|
42
42
|
versionhq/storage/utils.py,sha256=ByYXPoEIGJYLUqz-DWjbCAnneNrH1otiYbp12SCILpM,747
|
43
43
|
versionhq/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
-
versionhq/task/evaluate.py,sha256=
|
45
|
-
versionhq/task/formation.py,sha256=
|
44
|
+
versionhq/task/evaluate.py,sha256=fBLL6sm763e3Ev6uU0tMqFRUVwAcBj6dLM5N062qHuc,3952
|
45
|
+
versionhq/task/formation.py,sha256=QXFZfY604cpS79X7HEBmwFKn91H8jS3Ak6EhJGgaRIg,6355
|
46
46
|
versionhq/task/formatter.py,sha256=N8Kmk9vtrMtBdgJ8J7RmlKNMdZWSmV8O1bDexmCWgU0,643
|
47
47
|
versionhq/task/log_handler.py,sha256=LT7YnO7gcPR9IZS7eRvMjnHh8crMBFtqduxd8dxIbkk,1680
|
48
|
-
versionhq/task/model.py,sha256=
|
48
|
+
versionhq/task/model.py,sha256=qbH_fMnkDqZUp-Sgd7LB7LMsXSaytMFMVffS5q3En1c,29160
|
49
49
|
versionhq/task/structured_response.py,sha256=uVqgeUxNOACPe2hdc0RELSbtKd1vrwonfjXMOGTT0TI,4818
|
50
50
|
versionhq/task/TEMPLATES/Description.py,sha256=V-4kh8xpQTKOcDMi2xnuP-fcNk6kuoz1_5tYBlDLQWQ,420
|
51
51
|
versionhq/team/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
|
-
versionhq/team/model.py,sha256=
|
53
|
-
versionhq/team/team_planner.py,sha256=
|
52
|
+
versionhq/team/model.py,sha256=qPsV3O3O2TIOT-3h142KVVFdO587LhoOeVkK2niZHEc,19062
|
53
|
+
versionhq/team/team_planner.py,sha256=x1eLkdfQwW6w3Kyi_wVaVlA41TaNJDIYWhtEqp_OcaI,3675
|
54
54
|
versionhq/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
versionhq/tool/cache_handler.py,sha256=iL8FH7X0G-cdT0uhJwzuhLDaadTXOdfybZcDy151-es,1085
|
56
56
|
versionhq/tool/composio_tool.py,sha256=38mEiVvTkuw1BLD233Bl1Gwxbpss1yfQiZLTWwX6BdA,8648
|
@@ -58,8 +58,8 @@ versionhq/tool/composio_tool_vars.py,sha256=FvBuEXsOQUYnN7RTFxT20kAkiEYkxWKkiVtg
|
|
58
58
|
versionhq/tool/decorator.py,sha256=C4ZM7Xi2gwtEMaSeRo-geo_g_MAkY77WkSLkAuY0AyI,1205
|
59
59
|
versionhq/tool/model.py,sha256=PO4zNWBZcJhYVur381YL1dy6zqurio2jWjtbxOxZMGI,12194
|
60
60
|
versionhq/tool/tool_handler.py,sha256=2m41K8qo5bGCCbwMFferEjT-XZ-mE9F0mDUOBkgivOI,1416
|
61
|
-
versionhq-1.1.
|
62
|
-
versionhq-1.1.
|
63
|
-
versionhq-1.1.
|
64
|
-
versionhq-1.1.
|
65
|
-
versionhq-1.1.
|
61
|
+
versionhq-1.1.13.0.dist-info/LICENSE,sha256=cRoGGdM73IiDs6nDWKqPlgSv7aR4n-qBXYnJlCMHCeE,1082
|
62
|
+
versionhq-1.1.13.0.dist-info/METADATA,sha256=zNnwZLvuWsZPjXoCrsa7PqD8iR1sJ2zUjjj8wV0QkfA,17365
|
63
|
+
versionhq-1.1.13.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
64
|
+
versionhq-1.1.13.0.dist-info/top_level.txt,sha256=DClQwxDWqIUGeRJkA8vBlgeNsYZs4_nJWMonzFt5Wj0,10
|
65
|
+
versionhq-1.1.13.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|