versionhq 1.1.11.8__py3-none-any.whl → 1.1.12.2__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 +49 -13
- versionhq/_utils/__init__.py +3 -0
- versionhq/_utils/logger.py +6 -1
- versionhq/agent/inhouse_agents.py +41 -0
- versionhq/agent/model.py +10 -7
- versionhq/agent/rpm_controller.py +1 -1
- versionhq/clients/product/model.py +0 -1
- versionhq/knowledge/__init__.py +22 -0
- versionhq/knowledge/model.py +0 -2
- versionhq/knowledge/source.py +0 -1
- versionhq/llm/llm_vars.py +14 -124
- versionhq/llm/model.py +35 -37
- versionhq/memory/model.py +109 -47
- versionhq/storage/ltm_sqlite_storage.py +29 -43
- versionhq/storage/mem0_storage.py +1 -1
- versionhq/storage/rag_storage.py +23 -22
- versionhq/storage/task_output_storage.py +6 -6
- versionhq/task/TEMPLATES/Description.py +3 -3
- versionhq/task/__init__.py +0 -9
- versionhq/task/evaluate.py +19 -8
- versionhq/task/formation.py +123 -0
- versionhq/task/model.py +88 -110
- versionhq/task/structured_response.py +1 -1
- versionhq/team/model.py +43 -62
- versionhq/team/team_planner.py +5 -2
- versionhq/tool/model.py +1 -1
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/LICENSE +1 -1
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/METADATA +15 -16
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/RECORD +31 -30
- versionhq/agent/default_agents.py +0 -15
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/WHEEL +0 -0
- {versionhq-1.1.11.8.dist-info → versionhq-1.1.12.2.dist-info}/top_level.txt +0 -0
versionhq/memory/model.py
CHANGED
@@ -1,7 +1,53 @@
|
|
1
|
+
import datetime
|
1
2
|
from typing import Any, Dict, List, Optional
|
2
3
|
|
3
4
|
from versionhq.storage.rag_storage import RAGStorage
|
4
5
|
from versionhq.storage.ltm_sqlite_storage import LTMSQLiteStorage
|
6
|
+
from versionhq._utils.logger import Logger
|
7
|
+
|
8
|
+
|
9
|
+
class MemoryData:
|
10
|
+
"""
|
11
|
+
A class to store structured data to store in the memory.
|
12
|
+
"""
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
agent: Optional[str] = None, # task execution agent (core)
|
16
|
+
task_description: Optional[str] = None,
|
17
|
+
task_output: Optional[str] = None,
|
18
|
+
config: Optional[Dict[str, Any]] = None
|
19
|
+
):
|
20
|
+
self.agent = agent
|
21
|
+
self.task_description = task_description
|
22
|
+
self.task_output = task_output
|
23
|
+
|
24
|
+
if config:
|
25
|
+
for k, v in config.items():
|
26
|
+
setattr(self, k, str(v))
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
class MemoryMetadata:
|
31
|
+
"""
|
32
|
+
A class to store structured metadata to store in the memory.
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
eval_criteria: Optional[str] = None,
|
38
|
+
score: Optional[int | float] = None,
|
39
|
+
suggestion: Optional[str] = None,
|
40
|
+
eval_by: Optional[str] = None, # task evaluator agent
|
41
|
+
config: Optional[Dict[str, Any]] = None
|
42
|
+
):
|
43
|
+
self.eval_criteria = eval_criteria
|
44
|
+
self.score = score
|
45
|
+
self.suggestion = suggestion
|
46
|
+
self.eval_by = eval_by
|
47
|
+
|
48
|
+
if config:
|
49
|
+
for k, v in config.items():
|
50
|
+
setattr(self, k, str(v))
|
5
51
|
|
6
52
|
|
7
53
|
class Memory:
|
@@ -13,28 +59,46 @@ class Memory:
|
|
13
59
|
self.storage = storage
|
14
60
|
|
15
61
|
|
16
|
-
def save(
|
17
|
-
|
62
|
+
def save(
|
63
|
+
self,
|
64
|
+
data: MemoryData | Dict[str, Any],
|
65
|
+
metadata: Optional[MemoryMetadata | Dict[str, Any]] = None,
|
66
|
+
agent: Optional[str] = None
|
67
|
+
) -> None:
|
68
|
+
|
69
|
+
"""
|
70
|
+
Create a dict for data and metadata without empty values before storing them in the given storage.
|
71
|
+
"""
|
72
|
+
|
73
|
+
if not data:
|
74
|
+
Logger(verbose=True).log(level="error", message="Missing data to store. Add either dict or MemoryData object", color="red")
|
75
|
+
return None
|
18
76
|
|
19
|
-
if
|
20
|
-
|
21
|
-
|
77
|
+
metadata_dict = metadata if isinstance(metadata, dict) else metadata.__dict__ if isinstance(metadata, MemoryMetadata) else dict()
|
78
|
+
metadata_dict = {k: v for k, v in metadata_dict.items() if v} # remove empty values
|
79
|
+
data_dict = data if isinstance(data, dict) else data.__dict__ if isinstance(data, MemoryData) else dict()
|
80
|
+
data_dict = {k: v for k, v in data_dict.items() if v}
|
81
|
+
|
82
|
+
if agent and data_dict["agent"] is None:
|
83
|
+
data_dict["agent"] = agent
|
84
|
+
|
85
|
+
if metadata_dict:
|
86
|
+
self.storage.save(data=data_dict, metadata=metadata_dict)
|
87
|
+
else:
|
88
|
+
self.storage.save(data=data_dict)
|
22
89
|
|
23
90
|
|
24
91
|
def search(self, query: str, limit: int = 3, score_threshold: float = 0.35) -> List[Any]:
|
25
92
|
return self.storage.search(query=query, limit=limit, score_threshold=score_threshold)
|
26
93
|
|
27
94
|
|
95
|
+
class MemoryItem:
|
96
|
+
"""
|
97
|
+
A class to store item to be saved in either long term memory or short term memory.
|
98
|
+
"""
|
28
99
|
|
29
|
-
|
30
|
-
def __init__(
|
31
|
-
self,
|
32
|
-
data: Any,
|
33
|
-
agent: Optional[str] = None,
|
34
|
-
metadata: Optional[Dict[str, Any]] = None,
|
35
|
-
):
|
100
|
+
def __init__(self, data: MemoryData = None, metadata: Optional[MemoryMetadata] = None):
|
36
101
|
self.data = data
|
37
|
-
self.agent = agent
|
38
102
|
self.metadata = metadata if metadata is not None else {}
|
39
103
|
|
40
104
|
|
@@ -70,12 +134,22 @@ class ShortTermMemory(Memory):
|
|
70
134
|
super().__init__(storage)
|
71
135
|
|
72
136
|
|
73
|
-
def save(
|
74
|
-
|
137
|
+
def save(
|
138
|
+
self,
|
139
|
+
task_description: str = None,
|
140
|
+
task_output: str = None,
|
141
|
+
agent: Optional[str] = None,
|
142
|
+
data: Optional[MemoryData] = None,
|
143
|
+
metadata: Optional[MemoryMetadata] = None
|
144
|
+
) -> None:
|
145
|
+
|
146
|
+
data = data if data else MemoryData(task_description=task_description, task_output=task_output, agent=agent)
|
147
|
+
item = MemoryItem(data=data, metadata=metadata)
|
148
|
+
|
75
149
|
if self.memory_provider == "mem0":
|
76
|
-
item.data = f"Remember the following insights from Agent run: {item.data}"
|
150
|
+
item.data.task_output = f"Remember the following insights from Agent run: {item.data.task_output}"
|
77
151
|
|
78
|
-
super().save(
|
152
|
+
super().save(data=item.data.__dict__, metadata=item.metadata.__dict__ if item.metadata else {})
|
79
153
|
|
80
154
|
|
81
155
|
def search(self, query: str, limit: int = 3, score_threshold: float = 0.35,):
|
@@ -89,29 +163,11 @@ class ShortTermMemory(Memory):
|
|
89
163
|
raise Exception(f"An error occurred while resetting the short-term memory: {str(e)}")
|
90
164
|
|
91
165
|
|
92
|
-
|
93
|
-
class LongTermMemoryItem:
|
94
|
-
def __init__(
|
95
|
-
self,
|
96
|
-
agent: str,
|
97
|
-
task: str,
|
98
|
-
datetime: str,
|
99
|
-
quality: Optional[int | float] = None,
|
100
|
-
metadata: Optional[Dict[str, Any]] = None,
|
101
|
-
):
|
102
|
-
self.task = task
|
103
|
-
self.agent = agent
|
104
|
-
self.quality = quality
|
105
|
-
self.datetime = datetime
|
106
|
-
self.metadata = metadata if metadata is not None else {}
|
107
|
-
|
108
|
-
|
109
|
-
|
110
166
|
class LongTermMemory(Memory):
|
111
167
|
"""
|
112
168
|
A class for managing cross runs data related to overall task executions.
|
113
169
|
- Type: ltm
|
114
|
-
- Storage: LTMSQLiteStorage
|
170
|
+
- Storage: LTMSQLiteStorage | RAGStorage
|
115
171
|
"""
|
116
172
|
|
117
173
|
def __init__(self, storage=None, path=None):
|
@@ -121,19 +177,25 @@ class LongTermMemory(Memory):
|
|
121
177
|
super().__init__(storage)
|
122
178
|
|
123
179
|
|
124
|
-
def save(
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
metadata=
|
131
|
-
|
132
|
-
|
180
|
+
def save(
|
181
|
+
self,
|
182
|
+
task_description: str = None,
|
183
|
+
task_output: str = None,
|
184
|
+
agent: Optional[str] = None,
|
185
|
+
data: Optional[MemoryData] = None,
|
186
|
+
metadata: Optional[MemoryMetadata] = None
|
187
|
+
) -> None:
|
188
|
+
|
189
|
+
data = data if data else MemoryData(task_description=task_description, task_output=task_output, agent=agent)
|
190
|
+
item = MemoryItem(data=data, metadata=metadata)
|
191
|
+
super().save(data=item.data, metadata=item.metadata)
|
133
192
|
|
134
193
|
|
135
|
-
def search(self,
|
136
|
-
|
194
|
+
def search(self, query: str, latest_n: int = 3) -> List[Dict[str, Any]]:
|
195
|
+
"""
|
196
|
+
Query the storage and return the results up to latest_n.
|
197
|
+
"""
|
198
|
+
return self.storage.load(query=query, latest_n=latest_n)
|
137
199
|
|
138
200
|
|
139
201
|
def reset(self) -> None:
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import json
|
2
|
+
import datetime
|
2
3
|
import sqlite3
|
3
4
|
from pathlib import Path
|
4
5
|
from typing import Any, Dict, List, Optional
|
@@ -27,88 +28,77 @@ class LTMSQLiteStorage:
|
|
27
28
|
"""
|
28
29
|
Initializes the SQLite database and creates LTM table
|
29
30
|
"""
|
31
|
+
|
30
32
|
try:
|
31
33
|
with sqlite3.connect(self.db_path) as conn:
|
32
34
|
cursor = conn.cursor()
|
33
35
|
cursor.execute(
|
34
|
-
|
36
|
+
"""
|
35
37
|
CREATE TABLE IF NOT EXISTS long_term_memories (
|
36
38
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
score REAL
|
39
|
+
datetime REAL,
|
40
|
+
data TEXT,
|
41
|
+
metadata TEXT
|
41
42
|
)
|
42
43
|
"""
|
43
44
|
)
|
44
|
-
|
45
45
|
conn.commit()
|
46
46
|
|
47
47
|
except sqlite3.Error as e:
|
48
|
-
self._logger.log(
|
49
|
-
|
50
|
-
message=f"MEMORY ERROR: An error occurred during database initialization: {str(e)}",
|
51
|
-
color="red",
|
52
|
-
)
|
48
|
+
self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred during database initialization: {str(e)}", color="red")
|
49
|
+
|
53
50
|
|
54
|
-
def save(self,
|
51
|
+
def save(self, data: Dict[str, Any] | str, metadata: Optional[Dict[str, Any]] = {}) -> None:
|
55
52
|
"""
|
56
53
|
Saves data to the LTM table with error handling.
|
57
54
|
"""
|
55
|
+
data = data if isinstance(data, dict) else dict(data=data)
|
56
|
+
|
58
57
|
try:
|
59
58
|
with sqlite3.connect(self.db_path) as conn:
|
60
59
|
cursor = conn.cursor()
|
61
60
|
cursor.execute(
|
62
|
-
|
63
|
-
INSERT INTO long_term_memories (
|
64
|
-
VALUES (?, ?,
|
61
|
+
"""
|
62
|
+
INSERT INTO long_term_memories (datetime, data, metadata)
|
63
|
+
VALUES (?, ?, ?)
|
65
64
|
""",
|
66
|
-
(
|
65
|
+
(datetime.datetime.now().timestamp(), json.dumps(data), json.dumps(metadata)),
|
67
66
|
)
|
68
67
|
conn.commit()
|
69
68
|
except sqlite3.Error as e:
|
70
|
-
self._logger.log(
|
71
|
-
level="error",
|
72
|
-
message=f"MEMORY ERROR: An error occurred while saving to LTM: {str(e)}",
|
73
|
-
color="red",
|
74
|
-
)
|
69
|
+
self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred while saving to LTM: {str(e)}", color="red")
|
75
70
|
|
76
71
|
|
77
|
-
def load(self,
|
72
|
+
def load(self, query: str, latest_n: int) -> Optional[List[Dict[str, Any]]]:
|
78
73
|
"""
|
79
|
-
Queries the
|
74
|
+
Queries the data row in the storage with error handling.
|
80
75
|
"""
|
81
76
|
try:
|
82
77
|
with sqlite3.connect(self.db_path) as conn:
|
83
78
|
cursor = conn.cursor()
|
84
79
|
cursor.execute(
|
85
|
-
|
86
|
-
SELECT
|
80
|
+
f"""
|
81
|
+
SELECT datetime, data, metadata
|
87
82
|
FROM long_term_memories
|
88
|
-
WHERE
|
89
|
-
ORDER BY datetime
|
83
|
+
WHERE data LIKE '%{query}%'
|
84
|
+
ORDER BY datetime
|
90
85
|
LIMIT {latest_n}
|
91
|
-
"""
|
92
|
-
(task_description,),
|
86
|
+
"""
|
93
87
|
)
|
94
88
|
rows = cursor.fetchall()
|
95
89
|
if rows:
|
96
90
|
return [
|
97
91
|
{
|
98
|
-
"
|
99
|
-
"
|
100
|
-
"
|
92
|
+
"datetime": row[0],
|
93
|
+
"data": json.loads(row[1]),
|
94
|
+
"metadata": json.loads(row[2]),
|
101
95
|
}
|
102
96
|
for row in rows
|
103
97
|
]
|
104
98
|
|
105
99
|
except sqlite3.Error as e:
|
106
|
-
self._logger.log(
|
107
|
-
|
108
|
-
message=f"MEMORY ERROR: An error occurred while querying LTM: {e}",
|
109
|
-
color="red",
|
110
|
-
)
|
111
|
-
return None
|
100
|
+
self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred while querying LTM: {e}",color="red")
|
101
|
+
return None
|
112
102
|
|
113
103
|
|
114
104
|
def reset(self) -> None:
|
@@ -123,9 +113,5 @@ class LTMSQLiteStorage:
|
|
123
113
|
conn.commit()
|
124
114
|
|
125
115
|
except sqlite3.Error as e:
|
126
|
-
self._logger.log(
|
127
|
-
level="error",
|
128
|
-
message=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {str(e)}",
|
129
|
-
color="red",
|
130
|
-
)
|
116
|
+
self._logger.log(level="error", message=f"MEMORY ERROR: An error occurred while deleting all rows in LTM: {str(e)}", color="red")
|
131
117
|
return None
|
@@ -47,7 +47,7 @@ class Mem0Storage(Storage):
|
|
47
47
|
return role.replace("\n", "").replace(" ", "_").replace("/", "_")
|
48
48
|
|
49
49
|
|
50
|
-
def save(self, value: Any, metadata: Dict[str, Any]) -> None:
|
50
|
+
def save(self, value: Dict[str, Any] | str, metadata: Dict[str, Any]) -> None:
|
51
51
|
user_id = self._get_user_id()
|
52
52
|
agent_name = self._get_agent_name()
|
53
53
|
|
versionhq/storage/rag_storage.py
CHANGED
@@ -163,11 +163,32 @@ class RAGStorage(BaseRAGStorage):
|
|
163
163
|
return f"{base_path}/{file_name}"
|
164
164
|
|
165
165
|
|
166
|
-
def
|
166
|
+
def _create_default_embedding_function(self):
|
167
|
+
from chromadb.utils.embedding_functions.openai_embedding_function import (
|
168
|
+
OpenAIEmbeddingFunction,
|
169
|
+
)
|
170
|
+
|
171
|
+
return OpenAIEmbeddingFunction(
|
172
|
+
api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"
|
173
|
+
)
|
174
|
+
|
175
|
+
|
176
|
+
def _generate_embedding(self, text: str, metadata: Optional[Dict[str, Any]]) -> None:
|
177
|
+
if not hasattr(self, "app") or not hasattr(self, "collection"):
|
178
|
+
self._initialize_app()
|
179
|
+
|
180
|
+
if metadata:
|
181
|
+
self.collection.add(documents=[text], metadatas=[metadata, ], ids=[str(uuid.uuid4())])
|
182
|
+
|
183
|
+
else:
|
184
|
+
self.collection.add(documents=[text], ids=[str(uuid.uuid4())])
|
185
|
+
|
186
|
+
|
187
|
+
def save(self, data: Dict[str, Any] | str, metadata: Optional[Dict[str, Any]] = dict()) -> None:
|
167
188
|
if not hasattr(self, "app") or not hasattr(self, "collection"):
|
168
189
|
self._initialize_app()
|
169
190
|
try:
|
170
|
-
self._generate_embedding(
|
191
|
+
self._generate_embedding(text=str(data), metadata=metadata)
|
171
192
|
except Exception as e:
|
172
193
|
logging.error(f"Error during {self.type} save: {str(e)}")
|
173
194
|
|
@@ -197,17 +218,6 @@ class RAGStorage(BaseRAGStorage):
|
|
197
218
|
return []
|
198
219
|
|
199
220
|
|
200
|
-
def _generate_embedding(self, text: str, metadata: Dict[str, Any]) -> None:
|
201
|
-
if not hasattr(self, "app") or not hasattr(self, "collection"):
|
202
|
-
self._initialize_app()
|
203
|
-
|
204
|
-
self.collection.add(
|
205
|
-
documents=[text],
|
206
|
-
metadatas=[metadata or {}],
|
207
|
-
ids=[str(uuid.uuid4())],
|
208
|
-
)
|
209
|
-
|
210
|
-
|
211
221
|
def reset(self) -> None:
|
212
222
|
try:
|
213
223
|
if self.app:
|
@@ -220,12 +230,3 @@ class RAGStorage(BaseRAGStorage):
|
|
220
230
|
pass
|
221
231
|
else:
|
222
232
|
raise Exception(f"An error occurred while resetting the {self.type} memory: {e}")
|
223
|
-
|
224
|
-
def _create_default_embedding_function(self):
|
225
|
-
from chromadb.utils.embedding_functions.openai_embedding_function import (
|
226
|
-
OpenAIEmbeddingFunction,
|
227
|
-
)
|
228
|
-
|
229
|
-
return OpenAIEmbeddingFunction(
|
230
|
-
api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"
|
231
|
-
)
|
@@ -7,7 +7,7 @@ from versionhq._utils.logger import Logger
|
|
7
7
|
from versionhq.storage.utils import fetch_db_storage_path
|
8
8
|
|
9
9
|
storage_path = fetch_db_storage_path()
|
10
|
-
default_db_name = "
|
10
|
+
default_db_name = "task_output"
|
11
11
|
|
12
12
|
|
13
13
|
class TaskOutputSQLiteStorage:
|
@@ -31,7 +31,7 @@ class TaskOutputSQLiteStorage:
|
|
31
31
|
cursor = conn.cursor()
|
32
32
|
cursor.execute(
|
33
33
|
"""
|
34
|
-
CREATE TABLE IF NOT EXISTS
|
34
|
+
CREATE TABLE IF NOT EXISTS task_output (
|
35
35
|
task_id TEXT PRIMARY KEY,
|
36
36
|
output JSON,
|
37
37
|
task_index INTEGER,
|
@@ -52,7 +52,7 @@ class TaskOutputSQLiteStorage:
|
|
52
52
|
with sqlite3.connect(self.db_path) as conn:
|
53
53
|
cursor = conn.cursor()
|
54
54
|
cursor.execute(
|
55
|
-
"""INSERT OR REPLACE INTO
|
55
|
+
"""INSERT OR REPLACE INTO task_output
|
56
56
|
(task_id, output, task_index, inputs, was_replayed, timestamp)
|
57
57
|
VALUES (?, ?, ?, ?, ?, ?)
|
58
58
|
""",
|
@@ -73,7 +73,7 @@ class TaskOutputSQLiteStorage:
|
|
73
73
|
fields.append(f"{k} = ?")
|
74
74
|
values.append(json.dumps(v) if isinstance(v, dict) else v)
|
75
75
|
|
76
|
-
query = f"UPDATE
|
76
|
+
query = f"UPDATE latest_kickoff_task_output SET {', '.join(fields)} WHERE task_index = ?"
|
77
77
|
values.append(task_index)
|
78
78
|
cursor.execute(query, tuple(values))
|
79
79
|
conn.commit()
|
@@ -93,7 +93,7 @@ class TaskOutputSQLiteStorage:
|
|
93
93
|
cursor = conn.cursor()
|
94
94
|
cursor.execute("""
|
95
95
|
SELECT *
|
96
|
-
FROM
|
96
|
+
FROM task_output
|
97
97
|
ORDER BY task_index
|
98
98
|
""")
|
99
99
|
|
@@ -120,7 +120,7 @@ class TaskOutputSQLiteStorage:
|
|
120
120
|
try:
|
121
121
|
with sqlite3.connect(self.db_path) as conn:
|
122
122
|
cursor = conn.cursor()
|
123
|
-
cursor.execute("DELETE FROM
|
123
|
+
cursor.execute("DELETE FROM task_output")
|
124
124
|
conn.commit()
|
125
125
|
|
126
126
|
except sqlite3.Error as e:
|
@@ -1,5 +1,5 @@
|
|
1
|
-
EVALUATE="""
|
2
|
-
Task: {
|
3
|
-
Task
|
1
|
+
EVALUATE="""Evaluate the provided task output against the given task description, assigning a score between 0 (worst) and 1 (best) based on the specified criteria. Scores should be numerical (integers or decimals). Provide specific suggestions for improvement. Do not assign identical scores to different criteria:
|
2
|
+
Task output: {task_output}
|
3
|
+
Task description: {task_description}
|
4
4
|
Evaluation criteria: {eval_criteria}
|
5
5
|
"""
|
versionhq/task/__init__.py
CHANGED
versionhq/task/evaluate.py
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
from typing import List, Optional, Dict, Any
|
2
2
|
from typing_extensions import Self
|
3
3
|
|
4
|
-
from pydantic import BaseModel, Field,
|
4
|
+
from pydantic import BaseModel, Field, model_validator
|
5
|
+
|
6
|
+
from versionhq.memory.model import MemoryMetadata
|
5
7
|
|
6
8
|
"""
|
7
9
|
Evaluate task output from accuracy, token consumption, latency perspectives, and mark the score from 0 to 1.
|
8
10
|
"""
|
9
11
|
|
10
12
|
|
11
|
-
|
12
|
-
|
13
13
|
class ScoreFormat:
|
14
14
|
def __init__(self, rate: float | int = 0, weight: int = 1):
|
15
15
|
self.rate = rate
|
@@ -61,10 +61,10 @@ class EvaluationItem(BaseModel):
|
|
61
61
|
"""
|
62
62
|
criteria: str
|
63
63
|
suggestion: str
|
64
|
-
score:
|
64
|
+
score: float
|
65
65
|
|
66
66
|
def _convert_score_to_score_format(self, weight: int = 1) -> ScoreFormat | None:
|
67
|
-
if self.score and isinstance(self.score,
|
67
|
+
if self.score and isinstance(self.score, float):
|
68
68
|
return ScoreFormat(rate=self.score, weight=weight)
|
69
69
|
|
70
70
|
else: return None
|
@@ -72,7 +72,6 @@ class EvaluationItem(BaseModel):
|
|
72
72
|
|
73
73
|
|
74
74
|
class Evaluation(BaseModel):
|
75
|
-
# expected_outcome: Optional[str] = Field(default=None, description="human input on expected outcome")
|
76
75
|
items: List[EvaluationItem] = []
|
77
76
|
latency: int = Field(default=None, description="seconds")
|
78
77
|
tokens: int = Field(default=None, description="tokens consumed")
|
@@ -80,11 +79,23 @@ class Evaluation(BaseModel):
|
|
80
79
|
|
81
80
|
@model_validator(mode="after")
|
82
81
|
def set_up_responsible_agent(self) -> Self:
|
83
|
-
from versionhq.agent.
|
84
|
-
self.responsible_agent =
|
82
|
+
from versionhq.agent.inhouse_agents import vhq_task_evaluator
|
83
|
+
self.responsible_agent = vhq_task_evaluator
|
85
84
|
return self
|
86
85
|
|
87
86
|
|
87
|
+
def _create_memory_metadata(self) -> MemoryMetadata:
|
88
|
+
"""
|
89
|
+
Create and store evaluation results in the memory metadata
|
90
|
+
"""
|
91
|
+
eval_by = self.responsible_agent.role if self.responsible_agent else None
|
92
|
+
score = self.aggregate_score
|
93
|
+
eval_criteria = ", ".join([item.criteria for item in self.items]) if self.items else None
|
94
|
+
suggestion = self.suggestion_summary
|
95
|
+
memory_metadata = MemoryMetadata(eval_by=eval_by, score=score, eval_criteria=eval_criteria, suggestion=suggestion)
|
96
|
+
return memory_metadata
|
97
|
+
|
98
|
+
|
88
99
|
@property
|
89
100
|
def aggregate_score(self) -> float:
|
90
101
|
"""
|
@@ -0,0 +1,123 @@
|
|
1
|
+
from typing import List
|
2
|
+
from enum import Enum
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from versionhq.task.model import Task
|
7
|
+
from versionhq.agent.model import Agent
|
8
|
+
from versionhq.team.model import Team, TeamMember, Formation
|
9
|
+
from versionhq.agent.inhouse_agents import vhq_formation_planner
|
10
|
+
from versionhq._utils import Logger
|
11
|
+
|
12
|
+
|
13
|
+
def form_agent_network(
|
14
|
+
task_overview: str,
|
15
|
+
expected_outcome: str,
|
16
|
+
agents: List[Agent] = None,
|
17
|
+
context: str = None,
|
18
|
+
formation: Formation = None
|
19
|
+
) -> Team | None:
|
20
|
+
"""
|
21
|
+
Make a formation of agents from the given task description, agents (optional), context (optional), and expected outcome (optional).
|
22
|
+
"""
|
23
|
+
|
24
|
+
if not task_overview:
|
25
|
+
Logger(verbose=True).log(level="error", message="Missing task description.", color="red")
|
26
|
+
return None
|
27
|
+
|
28
|
+
if not expected_outcome:
|
29
|
+
Logger(verbose=True).log(level="error", message="Missing expected outcome.", color="red")
|
30
|
+
return None
|
31
|
+
|
32
|
+
|
33
|
+
try:
|
34
|
+
class Outcome(BaseModel):
|
35
|
+
formation: Enum
|
36
|
+
agent_roles: list[str]
|
37
|
+
task_descriptions: list[str]
|
38
|
+
leader_agent: str
|
39
|
+
|
40
|
+
vhq_task = Task(
|
41
|
+
description=f"""
|
42
|
+
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
|
+
Task: {str(task_overview)}
|
44
|
+
Expected outcome: {str(expected_outcome)}
|
45
|
+
""",
|
46
|
+
pydantic_output=Outcome
|
47
|
+
)
|
48
|
+
|
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
|
+
if agents:
|
53
|
+
vhq_task.description += "Consider adding following agents in the formation: " + ", ".join([agent.role for agent in agents if isinstance(agent, Agent)])
|
54
|
+
|
55
|
+
res = vhq_task.execute_sync(agent=vhq_formation_planner, context=context)
|
56
|
+
formation_ = Formation.SUPERVISING
|
57
|
+
|
58
|
+
if res.pydantic:
|
59
|
+
formation_keys = [k for k, v in Formation._member_map_.items() if k == res.pydantic.formation.upper()]
|
60
|
+
|
61
|
+
if formation_keys:
|
62
|
+
formation_ = Formation[formation_keys[0]]
|
63
|
+
|
64
|
+
created_agents = [Agent(role=item, goal=item) for item in res.pydantic.agent_roles]
|
65
|
+
created_tasks = [Task(description=item) for item in res.pydantic.task_descriptions]
|
66
|
+
team_tasks = []
|
67
|
+
members = []
|
68
|
+
leader = str(res.pydantic.leader_agent)
|
69
|
+
|
70
|
+
for i in range(len(created_agents)):
|
71
|
+
is_manager = bool(created_agents[i].role.lower() in leader.lower())
|
72
|
+
member = TeamMember(agent=created_agents[i], is_manager=is_manager)
|
73
|
+
|
74
|
+
if len(created_tasks) >= i:
|
75
|
+
member.task = created_tasks[i]
|
76
|
+
members.append(member)
|
77
|
+
|
78
|
+
if len(created_agents) < len(created_tasks):
|
79
|
+
team_tasks.extend(created_tasks[len(created_agents) - 1:len(created_tasks)])
|
80
|
+
|
81
|
+
members.sort(key=lambda x: x.is_manager == False)
|
82
|
+
team = Team(members=members, formation=formation_)
|
83
|
+
return team
|
84
|
+
|
85
|
+
else:
|
86
|
+
formation_keys = [k for k, v in Formation._member_map_.items() if k == res.json_dict["formation"].upper()]
|
87
|
+
|
88
|
+
if formation_keys:
|
89
|
+
formation_ = Formation[formation_keys[0]]
|
90
|
+
|
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
|
+
team_tasks = []
|
94
|
+
members = []
|
95
|
+
leader = str(res.json_dict["leader_agent"])
|
96
|
+
|
97
|
+
for i in range(len(created_agents)):
|
98
|
+
is_manager = bool(created_agents[i].role.lower() in leader.lower())
|
99
|
+
member = TeamMember(agent=created_agents[i], is_manager=is_manager)
|
100
|
+
|
101
|
+
if len(created_tasks) >= i:
|
102
|
+
member.task = created_tasks[i]
|
103
|
+
members.append(member)
|
104
|
+
|
105
|
+
if len(created_agents) < len(created_tasks):
|
106
|
+
team_tasks.extend(created_tasks[len(created_agents) - 1:len(created_tasks)])
|
107
|
+
|
108
|
+
members.sort(key=lambda x: x.is_manager == True)
|
109
|
+
team = Team(members=members, formation=formation_)
|
110
|
+
return team
|
111
|
+
|
112
|
+
except Exception as e:
|
113
|
+
Logger(verbose=True).log(level="error", message=f"Failed to create an agent network - return None. You can try with solo agent. Error: {str(e)}", color="red")
|
114
|
+
return None
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
if __name__ == "__main__":
|
119
|
+
res = form_agent_network(
|
120
|
+
task_overview="Launch an outbound campaign to attract young audience.",
|
121
|
+
expected_outcome="Best media mix of the campaign.",
|
122
|
+
context="We are selling sports wear.",
|
123
|
+
)
|