versionhq 1.2.4.3__py3-none-any.whl → 1.2.4.6__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 +12 -3
- versionhq/_prompt/auto_feedback.py +2 -2
- versionhq/_prompt/model.py +24 -29
- versionhq/_utils/__init__.py +2 -0
- versionhq/_utils/convert_img_url.py +15 -0
- versionhq/_utils/is_valid_enum.py +25 -0
- versionhq/_utils/llm_as_a_judge.py +0 -1
- versionhq/_utils/usage_metrics.py +35 -14
- versionhq/agent/inhouse_agents.py +2 -2
- versionhq/agent/model.py +100 -29
- versionhq/agent_network/formation.py +6 -12
- versionhq/agent_network/model.py +4 -5
- versionhq/clients/customer/__init__.py +2 -2
- versionhq/clients/product/model.py +4 -4
- versionhq/clients/workflow/model.py +1 -1
- versionhq/llm/llm_vars.py +7 -6
- versionhq/llm/model.py +3 -1
- versionhq/storage/task_output_storage.py +2 -2
- versionhq/task/model.py +112 -100
- versionhq/task_graph/draft.py +4 -4
- versionhq/task_graph/model.py +34 -30
- versionhq/tool/composio/__init__.py +0 -0
- versionhq/tool/{composio_tool.py → composio/model.py} +4 -5
- versionhq/tool/gpt/__init__.py +6 -0
- versionhq/tool/gpt/_enum.py +28 -0
- versionhq/tool/gpt/cup.py +145 -0
- versionhq/tool/gpt/file_search.py +163 -0
- versionhq/tool/gpt/web_search.py +89 -0
- {versionhq-1.2.4.3.dist-info → versionhq-1.2.4.6.dist-info}/METADATA +4 -4
- {versionhq-1.2.4.3.dist-info → versionhq-1.2.4.6.dist-info}/RECORD +34 -26
- /versionhq/tool/{composio_tool_vars.py → composio/params.py} +0 -0
- {versionhq-1.2.4.3.dist-info → versionhq-1.2.4.6.dist-info}/LICENSE +0 -0
- {versionhq-1.2.4.3.dist-info → versionhq-1.2.4.6.dist-info}/WHEEL +0 -0
- {versionhq-1.2.4.3.dist-info → versionhq-1.2.4.6.dist-info}/top_level.txt +0 -0
versionhq/__init__.py
CHANGED
@@ -24,7 +24,10 @@ from versionhq.tool.model import Tool, ToolSet
|
|
24
24
|
from versionhq.tool.rag_tool import RagTool
|
25
25
|
from versionhq.tool.cache_handler import CacheHandler
|
26
26
|
from versionhq.tool.tool_handler import ToolHandler
|
27
|
-
from versionhq.tool.
|
27
|
+
from versionhq.tool.composio.model import ComposioBaseTool
|
28
|
+
from versionhq.tool.gpt.cup import GPTToolCUP, CUPToolSchema
|
29
|
+
from versionhq.tool.gpt.file_search import GPTToolFileSearch, FilterSchema
|
30
|
+
from versionhq.tool.gpt.web_search import GPTToolWebSearch
|
28
31
|
from versionhq.memory.contextual_memory import ContextualMemory
|
29
32
|
from versionhq.memory.model import ShortTermMemory,LongTermMemory, UserMemory, MemoryItem
|
30
33
|
|
@@ -32,7 +35,7 @@ from versionhq.agent_network.formation import form_agent_network
|
|
32
35
|
from versionhq.task_graph.draft import workflow
|
33
36
|
|
34
37
|
|
35
|
-
__version__ = "1.2.4.
|
38
|
+
__version__ = "1.2.4.6"
|
36
39
|
__all__ = [
|
37
40
|
"Agent",
|
38
41
|
|
@@ -85,7 +88,13 @@ __all__ = [
|
|
85
88
|
"RagTool",
|
86
89
|
"CacheHandler",
|
87
90
|
"ToolHandler",
|
88
|
-
"
|
91
|
+
"ComposioBaseTool",
|
92
|
+
|
93
|
+
"GPTToolCUP",
|
94
|
+
"CUPToolSchema",
|
95
|
+
"GPTToolFileSearch",
|
96
|
+
"FilterSchema",
|
97
|
+
"GPTToolWebSearch",
|
89
98
|
|
90
99
|
"ContextualMemory",
|
91
100
|
"ShortTermMemory",
|
@@ -5,7 +5,7 @@ from pydantic import InstanceOf, Field
|
|
5
5
|
|
6
6
|
from versionhq.agent.model import Agent
|
7
7
|
from versionhq.task.model import Task
|
8
|
-
from versionhq.task_graph.model import TaskGraph, Node, DependencyType
|
8
|
+
from versionhq.task_graph.model import TaskGraph, Node, DependencyType
|
9
9
|
from versionhq._prompt.model import Prompt
|
10
10
|
from versionhq._prompt.constants import REFLECT, INTEGRATE, parameter_sets
|
11
11
|
|
@@ -67,7 +67,7 @@ class PromptFeedbackGraph(TaskGraph):
|
|
67
67
|
if not agents:
|
68
68
|
return None
|
69
69
|
|
70
|
-
self.
|
70
|
+
self.concl_response_schema = base_task.response_schema
|
71
71
|
base_agent.callbacks.append(self._reflect)
|
72
72
|
init_node = Node(task=base_task, assigned_to=base_agent)
|
73
73
|
self.add_node(init_node)
|
versionhq/_prompt/model.py
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
from typing import Dict, List, Tuple, Any
|
3
3
|
from textwrap import dedent
|
4
4
|
|
5
|
-
from pydantic import InstanceOf
|
5
|
+
from pydantic import InstanceOf, BaseModel
|
6
6
|
|
7
|
-
from versionhq._utils import is_valid_url
|
7
|
+
from versionhq._utils import is_valid_url, convert_img_url
|
8
8
|
|
9
9
|
|
10
10
|
class Prompt:
|
@@ -25,34 +25,26 @@ class Prompt:
|
|
25
25
|
|
26
26
|
|
27
27
|
def _draft_output_prompt(self) -> str:
|
28
|
-
"""Drafts prompt for output
|
28
|
+
"""Drafts prompt for output format using `response_schema`."""
|
29
29
|
|
30
|
-
from versionhq.
|
30
|
+
from versionhq.task.model import ResponseField
|
31
31
|
|
32
32
|
output_prompt = ""
|
33
|
-
|
33
|
+
output_formats_to_follow = dict()
|
34
34
|
|
35
|
-
if self.task.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
if self.task.response_schema:
|
36
|
+
if isinstance(self.task.response_schema, list):
|
37
|
+
for item in self.task.response_schema:
|
38
|
+
if isinstance(item, ResponseField):
|
39
|
+
output_formats_to_follow[item.title] = f"<Return your answer in {item.data_type.__name__}>"
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
"""
|
45
|
-
elif self.task.response_fields:
|
46
|
-
output_prompt, output_formats_to_follow = "", dict()
|
47
|
-
response_format = str(self.task._structure_response_format(model_provider=model_provider))
|
48
|
-
for item in self.task.response_fields:
|
49
|
-
if item:
|
50
|
-
output_formats_to_follow[item.title] = f"<Return your answer in {item.data_type.__name__}>"
|
41
|
+
elif issubclass(self.task.response_schema, BaseModel):
|
42
|
+
for k, v in self.task.response_schema.model_fields.items():
|
43
|
+
output_formats_to_follow[k] = f"<Return your answer in {v.annotation}>"
|
51
44
|
|
52
45
|
output_prompt = f"""Your response MUST be a valid JSON string that strictly follows the response format. Use double quotes for all keys and string values. Do not use single quotes, trailing commas, or any other non-standard JSON syntax.
|
53
|
-
Response format: {response_format}
|
54
46
|
Ref. Output image: {output_formats_to_follow}
|
55
|
-
"""
|
47
|
+
"""
|
56
48
|
else:
|
57
49
|
output_prompt = "You MUST return your response as a valid JSON serializable string, enclosed in double quotes. Use double quotes for all keys and string values. Do NOT use single quotes, trailing commas, or other non-standard JSON syntax."
|
58
50
|
|
@@ -107,12 +99,9 @@ Ref. Output image: {output_formats_to_follow}
|
|
107
99
|
content_messages = {}
|
108
100
|
|
109
101
|
if self.task.image:
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
encoded_file = base64.b64encode(content).decode("utf-8")
|
114
|
-
img_url = f"data:image/jpeg;base64,{encoded_file}"
|
115
|
-
content_messages.update({ "type": "image_url", "image_url": { "url": img_url }})
|
102
|
+
img_url = convert_img_url(self.task.image)
|
103
|
+
if img_url:
|
104
|
+
content_messages.update({ "type": "image_url", "image_url": { "url": img_url }})
|
116
105
|
|
117
106
|
if self.task.file:
|
118
107
|
if is_valid_url(self.task.file):
|
@@ -154,7 +143,7 @@ Ref. Output image: {output_formats_to_follow}
|
|
154
143
|
return "\n".join(task_slices)
|
155
144
|
|
156
145
|
|
157
|
-
def format_core(self, rag_tools: List[Any] = None) -> Tuple[str, str, List[Dict[str, str]]]:
|
146
|
+
def format_core(self, rag_tools: List[Any] = None, gpt_tools: List[Any] = None) -> Tuple[str, str, List[Dict[str, str]]]:
|
158
147
|
"""Formats prompt messages sent to the LLM, then returns task prompt, developer prompt, and messages."""
|
159
148
|
|
160
149
|
from versionhq.knowledge._utils import extract_knowledge_context
|
@@ -176,6 +165,12 @@ Ref. Output image: {output_formats_to_follow}
|
|
176
165
|
if rag_tool_context:
|
177
166
|
user_prompt += ",".join(rag_tool_context) if isinstance(rag_tool_context, list) else str(rag_tool_context)
|
178
167
|
|
168
|
+
if gpt_tools:
|
169
|
+
for item in gpt_tools:
|
170
|
+
raw, _, _ = item.run()
|
171
|
+
if raw:
|
172
|
+
user_prompt += str(raw)
|
173
|
+
|
179
174
|
if self.agent.with_memory == True:
|
180
175
|
contextual_memory = ContextualMemory(
|
181
176
|
memory_config=self.agent.memory_config, stm=self.agent.short_term_memory, ltm=self.agent.long_term_memory, um=self.agent.user_memory
|
versionhq/_utils/__init__.py
CHANGED
@@ -3,3 +3,5 @@ from versionhq._utils.process_config import process_config
|
|
3
3
|
from versionhq._utils.vars import KNOWLEDGE_DIRECTORY, MAX_FILE_NAME_LENGTH
|
4
4
|
from versionhq._utils.is_valid_url import is_valid_url
|
5
5
|
from versionhq._utils.usage_metrics import UsageMetrics, ErrorType
|
6
|
+
from versionhq._utils.convert_img_url import convert_img_url
|
7
|
+
from versionhq._utils.is_valid_enum import is_valid_enum
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import base64
|
2
|
+
|
3
|
+
def convert_img_url(img_url: str) -> str | None:
|
4
|
+
try:
|
5
|
+
with open(img_url, "rb") as file:
|
6
|
+
content = file.read()
|
7
|
+
if content:
|
8
|
+
encoded_file = base64.b64encode(content).decode("utf-8")
|
9
|
+
img_url = f"data:image/jpeg;base64,{encoded_file}"
|
10
|
+
return img_url
|
11
|
+
|
12
|
+
else: return None
|
13
|
+
|
14
|
+
except:
|
15
|
+
return None
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from enum import Enum, IntEnum
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
|
5
|
+
def is_valid_enum(enum: Enum | IntEnum, key: str = None, val: str | Enum | IntEnum = None) -> bool:
|
6
|
+
if not enum: return False
|
7
|
+
|
8
|
+
if key:
|
9
|
+
key = key.upper()
|
10
|
+
matched = [k for k in enum._member_map_.keys() if hasattr(enum, "_member_map_") and k == key]
|
11
|
+
return bool(matched)
|
12
|
+
|
13
|
+
elif val:
|
14
|
+
match val:
|
15
|
+
case str():
|
16
|
+
matched = [k for k in enum._value2member_map_.keys() if hasattr(enum, "_value2member_map_") and k == val]
|
17
|
+
return bool(matched)
|
18
|
+
|
19
|
+
case Enum() | IntEnum():
|
20
|
+
return val in enum
|
21
|
+
|
22
|
+
case _:
|
23
|
+
return False
|
24
|
+
|
25
|
+
else: return False
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import uuid
|
2
|
-
import enum
|
3
2
|
import datetime
|
3
|
+
from enum import IntEnum
|
4
4
|
from typing import Dict, List
|
5
5
|
from typing_extensions import Self
|
6
6
|
|
7
7
|
from pydantic import BaseModel, UUID4, InstanceOf
|
8
8
|
|
9
9
|
|
10
|
-
class ErrorType(
|
10
|
+
class ErrorType(IntEnum):
|
11
11
|
FORMAT = 1
|
12
12
|
TOOL = 2
|
13
13
|
API = 3
|
@@ -22,19 +22,38 @@ class UsageMetrics(BaseModel):
|
|
22
22
|
total_tokens: int = 0
|
23
23
|
prompt_tokens: int = 0
|
24
24
|
completion_tokens: int = 0
|
25
|
+
input_tokens: int = 0
|
26
|
+
output_tokens: int = 0
|
25
27
|
successful_requests: int = 0
|
26
28
|
total_errors: int = 0
|
27
29
|
error_breakdown: Dict[ErrorType, int] = dict()
|
28
30
|
latency: float = 0.0 # in ms
|
29
31
|
|
30
|
-
|
32
|
+
|
33
|
+
def record_token_usage(self, *args, **kwargs) -> None:
|
31
34
|
"""Records usage metrics from the raw response of the model."""
|
32
35
|
|
33
|
-
if
|
34
|
-
for item in
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
if args:
|
37
|
+
for item in args:
|
38
|
+
match item:
|
39
|
+
case dict():
|
40
|
+
if hasattr(self, k):
|
41
|
+
setattr(self, k, int(getattr(self, k)) + int(v))
|
42
|
+
case UsageMetrics():
|
43
|
+
self = self.aggregate(metrics=item)
|
44
|
+
case _:
|
45
|
+
try:
|
46
|
+
self.completion_tokens += item.completion_tokens if hasattr(item, "completion_tokens") else 0
|
47
|
+
self.prompt_tokens += item.prompt_tokens if hasattr(item, "prompt_tokens") else 0
|
48
|
+
self.total_tokens += item.total_tokens if hasattr(item, "total_tokens") else 0
|
49
|
+
self.input_tokens += item.input_tokens if hasattr(item, "input_tokens") else 0
|
50
|
+
self.output_tokens += item.output_tokens if hasattr(item, "output_tokens") else 0
|
51
|
+
except:
|
52
|
+
pass
|
53
|
+
if kwargs:
|
54
|
+
for k, v in kwargs.items():
|
55
|
+
if hasattr(self, k):
|
56
|
+
setattr(self, k, int(getattr(self, k)) + int(v))
|
38
57
|
|
39
58
|
|
40
59
|
def record_errors(self, type: ErrorType = None) -> None:
|
@@ -54,12 +73,14 @@ class UsageMetrics(BaseModel):
|
|
54
73
|
if not metrics:
|
55
74
|
return self
|
56
75
|
|
57
|
-
self.total_tokens += metrics.total_tokens
|
58
|
-
self.prompt_tokens += metrics.prompt_tokens
|
59
|
-
self.completion_tokens += metrics.completion_tokens
|
60
|
-
self.
|
61
|
-
self.
|
62
|
-
self.
|
76
|
+
self.total_tokens += metrics.total_tokens
|
77
|
+
self.prompt_tokens += metrics.prompt_tokens
|
78
|
+
self.completion_tokens += metrics.completion_tokens
|
79
|
+
self.input_tokens += metrics.input_tokens
|
80
|
+
self.output_tokens += metrics.output_tokens
|
81
|
+
self.successful_requests += metrics.successful_requests
|
82
|
+
self.total_errors += metrics.total_errors
|
83
|
+
self.latency += metrics.latency
|
63
84
|
self.latency = round(self.latency, 3)
|
64
85
|
|
65
86
|
if metrics.error_breakdown:
|
@@ -30,7 +30,7 @@ vhq_task_evaluator = Agent(
|
|
30
30
|
vhq_formation_planner = Agent(
|
31
31
|
role="vhq-Formation Planner",
|
32
32
|
goal="Plan a formation of agents based on the given task descirption.",
|
33
|
-
llm="gemini/gemini-2.0-flash
|
33
|
+
llm="gemini/gemini-2.0-flash",
|
34
34
|
llm_config=dict(top_p=0.8, topK=40, temperature=0.9),
|
35
35
|
maxit=1,
|
36
36
|
max_retry_limit=1,
|
@@ -46,7 +46,7 @@ vhq_formation_planner = Agent(
|
|
46
46
|
vhq_agent_creator = Agent(
|
47
47
|
role="vhq-Agent Creator",
|
48
48
|
goal="build an agent that can handle the given task",
|
49
|
-
llm="gemini/gemini-2.0-flash
|
49
|
+
llm="gemini/gemini-2.0-flash",
|
50
50
|
maxit=1,
|
51
51
|
max_retry_limit=1,
|
52
52
|
)
|
versionhq/agent/model.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import uuid
|
3
|
-
from typing import Any, Dict, List, Optional, TypeVar, Callable, Type
|
3
|
+
from typing import Any, Dict, List, Optional, TypeVar, Callable, Type, Tuple
|
4
4
|
from typing_extensions import Self
|
5
5
|
from dotenv import load_dotenv
|
6
6
|
|
@@ -11,7 +11,7 @@ from versionhq.agent.rpm_controller import RPMController
|
|
11
11
|
from versionhq.tool.model import Tool, ToolSet, BaseTool
|
12
12
|
from versionhq.knowledge.model import BaseKnowledgeSource, Knowledge
|
13
13
|
from versionhq.memory.model import ShortTermMemory, LongTermMemory, UserMemory
|
14
|
-
from versionhq._utils import Logger, process_config, is_valid_url, ErrorType
|
14
|
+
from versionhq._utils import Logger, process_config, is_valid_url, ErrorType, UsageMetrics
|
15
15
|
|
16
16
|
|
17
17
|
load_dotenv(override=True)
|
@@ -124,6 +124,9 @@ class Agent(BaseModel):
|
|
124
124
|
Similar to the LLM set up, when the agent has tools, we will declare them using the Tool class.
|
125
125
|
"""
|
126
126
|
from versionhq.tool.rag_tool import RagTool
|
127
|
+
from versionhq.tool.gpt.web_search import GPTToolWebSearch
|
128
|
+
from versionhq.tool.gpt.file_search import GPTToolFileSearch
|
129
|
+
from versionhq.tool.gpt.cup import GPTToolCUP
|
127
130
|
|
128
131
|
if not self.tools:
|
129
132
|
return self
|
@@ -131,7 +134,7 @@ class Agent(BaseModel):
|
|
131
134
|
tool_list = []
|
132
135
|
for item in self.tools:
|
133
136
|
match item:
|
134
|
-
case RagTool() | BaseTool():
|
137
|
+
case RagTool() | BaseTool() | GPTToolCUP() | GPTToolFileSearch() | GPTToolWebSearch():
|
135
138
|
tool_list.append(item)
|
136
139
|
|
137
140
|
case Tool():
|
@@ -353,8 +356,8 @@ class Agent(BaseModel):
|
|
353
356
|
response_format: Optional[Dict[str, Any]] = None,
|
354
357
|
tools: Optional[List[InstanceOf[Tool]| InstanceOf[ToolSet] | Type[Tool]]] = None,
|
355
358
|
tool_res_as_final: bool = False,
|
356
|
-
task: Any = None
|
357
|
-
) ->
|
359
|
+
# task: Any = None
|
360
|
+
) -> Tuple[str, UsageMetrics]:
|
358
361
|
"""
|
359
362
|
Create formatted prompts using the developer prompt and the agent's backstory, then call the base model.
|
360
363
|
- Execute the task up to `self.max_retry_limit` times in case of receiving an error or empty response.
|
@@ -364,6 +367,7 @@ class Agent(BaseModel):
|
|
364
367
|
task_execution_counter = 0
|
365
368
|
iterations = 0
|
366
369
|
raw_response = None
|
370
|
+
usage = UsageMetrics()
|
367
371
|
|
368
372
|
try:
|
369
373
|
if self._rpm_controller and self.max_rpm:
|
@@ -373,17 +377,17 @@ class Agent(BaseModel):
|
|
373
377
|
|
374
378
|
if tool_res_as_final:
|
375
379
|
raw_response = self.func_calling_llm.call(messages=messages, tools=tools, tool_res_as_final=True)
|
376
|
-
|
380
|
+
usage.record_token_usage(*self.func_calling_llm._usages)
|
377
381
|
else:
|
378
382
|
raw_response = self.llm.call(messages=messages, response_format=response_format, tools=tools)
|
379
|
-
|
383
|
+
usage.record_token_usage(*self.llm._usages)
|
380
384
|
|
381
385
|
task_execution_counter += 1
|
382
386
|
Logger(**self._logger_config, filename=self.key).log(level="info", message=f"Agent response: {raw_response}", color="green")
|
383
|
-
return raw_response
|
387
|
+
return raw_response, usage
|
384
388
|
|
385
389
|
except Exception as e:
|
386
|
-
|
390
|
+
usage.record_errors(type=ErrorType.API)
|
387
391
|
Logger(**self._logger_config, filename=self.key).log(level="error", message=f"An error occured. The agent will retry: {str(e)}", color="red")
|
388
392
|
|
389
393
|
while not raw_response and task_execution_counter <= self.max_retry_limit:
|
@@ -392,12 +396,12 @@ class Agent(BaseModel):
|
|
392
396
|
self._rpm_controller.check_or_wait()
|
393
397
|
|
394
398
|
raw_response = self.llm.call(messages=messages, response_format=response_format, tools=tools)
|
395
|
-
|
399
|
+
usage.record_token_usage(*self.llm._usages)
|
396
400
|
iterations += 1
|
397
401
|
|
398
402
|
task_execution_counter += 1
|
399
403
|
Logger(**self._logger_config, filename=self.key).log(level="info", message=f"Agent #{task_execution_counter} response: {raw_response}", color="green")
|
400
|
-
return raw_response
|
404
|
+
return raw_response, usage
|
401
405
|
|
402
406
|
if not raw_response:
|
403
407
|
Logger(**self._logger_config, filename=self.key).log(level="error", message="Received None or empty response from the model", color="red")
|
@@ -423,6 +427,57 @@ class Agent(BaseModel):
|
|
423
427
|
return self.set_up_llm()
|
424
428
|
|
425
429
|
|
430
|
+
def _sort_tools(self, task = None) -> Tuple[List[Any], List[Any], List[Any]]:
|
431
|
+
"""Sorts agent and task tools by class."""
|
432
|
+
|
433
|
+
from versionhq.tool.rag_tool import RagTool
|
434
|
+
from versionhq.tool.gpt.web_search import GPTToolWebSearch
|
435
|
+
from versionhq.tool.gpt.file_search import GPTToolFileSearch
|
436
|
+
from versionhq.tool.gpt.cup import GPTToolCUP
|
437
|
+
|
438
|
+
all_tools = []
|
439
|
+
if task: all_tools = task.tools + self.tools if task.can_use_agent_tools else task.tools
|
440
|
+
else: all_tools = self.tools
|
441
|
+
|
442
|
+
rag_tools, gpt_tools, tools = [], [], []
|
443
|
+
if all_tools:
|
444
|
+
for item in all_tools:
|
445
|
+
match item:
|
446
|
+
case RagTool():
|
447
|
+
rag_tools.append(item)
|
448
|
+
|
449
|
+
case GPTToolCUP() | GPTToolFileSearch() | GPTToolWebSearch():
|
450
|
+
gpt_tools.append(item)
|
451
|
+
|
452
|
+
case Tool() | BaseTool() | ToolSet():
|
453
|
+
tools.append(item)
|
454
|
+
|
455
|
+
return rag_tools, gpt_tools, tools
|
456
|
+
|
457
|
+
|
458
|
+
def _handle_gpt_tools(self, gpt_tools: list[Any] = None) -> Any: # TaskOutput
|
459
|
+
"""Generates k, v pairs from multiple GPT tool results and stores them in TaskOutput class."""
|
460
|
+
|
461
|
+
from versionhq.task.model import TaskOutput
|
462
|
+
from versionhq._utils import UsageMetrics
|
463
|
+
|
464
|
+
if not gpt_tools:
|
465
|
+
return
|
466
|
+
|
467
|
+
tool_res = dict()
|
468
|
+
annotation_set = dict()
|
469
|
+
total_usage = UsageMetrics()
|
470
|
+
|
471
|
+
for i, item in enumerate(gpt_tools):
|
472
|
+
raw, annotations, usage = item.run()
|
473
|
+
tool_res.update({ str(i): raw })
|
474
|
+
annotation_set.update({ str(i): annotations })
|
475
|
+
total_usage.aggregate(metrics=usage)
|
476
|
+
|
477
|
+
res = TaskOutput(raw=str(tool_res), tool_output=tool_res, usage=total_usage, annotations=annotation_set)
|
478
|
+
return res
|
479
|
+
|
480
|
+
|
426
481
|
def update(self, **kwargs) -> Self:
|
427
482
|
"""
|
428
483
|
Update the existing agent. Address variables that require runnning set_up_x methods first, then update remaining variables.
|
@@ -475,15 +530,28 @@ class Agent(BaseModel):
|
|
475
530
|
return self
|
476
531
|
|
477
532
|
|
478
|
-
def start(
|
533
|
+
def start(
|
534
|
+
self,
|
535
|
+
context: Any = None,
|
536
|
+
tool_res_as_final: bool = False,
|
537
|
+
image: str = None,
|
538
|
+
file: str = None,
|
539
|
+
audio: str = None
|
540
|
+
) -> Any:
|
479
541
|
"""
|
480
|
-
Defines and executes a task
|
542
|
+
Defines and executes a task, then returns TaskOutput object with the generated task.
|
481
543
|
"""
|
482
544
|
|
545
|
+
from versionhq.task.model import Task
|
546
|
+
|
483
547
|
if not self.role:
|
484
|
-
return None
|
548
|
+
return None, None
|
485
549
|
|
486
|
-
|
550
|
+
_, gpt_tools, _ = self._sort_tools()
|
551
|
+
|
552
|
+
if gpt_tools and tool_res_as_final == True:
|
553
|
+
res = self._handle_gpt_tools(gpt_tools=gpt_tools)
|
554
|
+
return res
|
487
555
|
|
488
556
|
class Output(BaseModel):
|
489
557
|
result: str
|
@@ -491,49 +559,52 @@ class Agent(BaseModel):
|
|
491
559
|
|
492
560
|
task = Task(
|
493
561
|
description=f"Generate a simple result in a sentence to achieve the goal: {self.goal if self.goal else self.role}. If needed, list up necessary steps in concise manner.",
|
494
|
-
|
562
|
+
response_schema=Output,
|
495
563
|
tool_res_as_final=tool_res_as_final,
|
496
564
|
image=image, #REFINEME - query memory/knowledge or self create
|
497
565
|
file=file,
|
498
566
|
audio=audio,
|
567
|
+
can_use_agent_tools=True if self.tools else False,
|
499
568
|
)
|
500
569
|
res = task.execute(agent=self, context=context)
|
501
570
|
return res
|
502
571
|
|
503
572
|
|
504
|
-
def execute_task(self, task, context: Optional[Any] = None
|
573
|
+
def execute_task(self, task, context: Optional[Any] = None) -> Tuple[str, str, Any, UsageMetrics]:
|
505
574
|
"""Handling task execution."""
|
506
575
|
|
507
|
-
from versionhq.task.model import Task
|
508
|
-
from versionhq.tool.rag_tool import RagTool
|
509
576
|
from versionhq._prompt.model import Prompt
|
577
|
+
from versionhq.task.model import Task
|
510
578
|
|
511
579
|
task: InstanceOf[Task] = task
|
512
|
-
|
513
|
-
|
514
|
-
|
580
|
+
rag_tools, gpt_tools, tools = self._sort_tools(task=task)
|
581
|
+
raw_response = ""
|
582
|
+
user_prompt, dev_prompt = "", ""
|
583
|
+
usage = UsageMetrics(id=task.id)
|
515
584
|
|
516
585
|
if self.max_rpm and self._rpm_controller:
|
517
586
|
self._rpm_controller._reset_request_count()
|
518
587
|
|
519
|
-
|
588
|
+
if task.tool_res_as_final == True and gpt_tools:
|
589
|
+
self._times_executed += 1
|
590
|
+
res = self._handle_gpt_tools(gpt_tools=gpt_tools)
|
591
|
+
return user_prompt, dev_prompt, res, res.usage
|
592
|
+
|
593
|
+
user_prompt, dev_prompt, messages = Prompt(task=task, agent=self, context=context).format_core(rag_tools=rag_tools, gpt_tools=gpt_tools)
|
520
594
|
|
521
595
|
try:
|
522
596
|
self._times_executed += 1
|
523
|
-
raw_response = self._invoke(
|
597
|
+
raw_response, usage = self._invoke(
|
524
598
|
messages=messages,
|
525
599
|
response_format=task._structure_response_format(model_provider=self.llm.provider),
|
526
600
|
tools=tools,
|
527
601
|
tool_res_as_final=task.tool_res_as_final,
|
528
|
-
task=task
|
529
602
|
)
|
530
|
-
if raw_response:
|
531
|
-
task._usage.successful_requests += 1
|
532
603
|
|
533
604
|
except Exception as e:
|
534
605
|
self._times_executed += 1
|
535
606
|
Logger(**self._logger_config, filename=self.key).log(level="error", message=f"The agent failed to execute the task. Error: {str(e)}", color="red")
|
536
|
-
user_prompt, dev_prompt, raw_response = self.execute_task(task, context
|
607
|
+
user_prompt, dev_prompt, raw_response, usage = self.execute_task(task, context)
|
537
608
|
|
538
609
|
if self._times_executed > self.max_retry_limit:
|
539
610
|
Logger(**self._logger_config, filename=self.key).log(level="error", message=f"Max retry limit has exceeded.", color="red")
|
@@ -542,7 +613,7 @@ class Agent(BaseModel):
|
|
542
613
|
if self.max_rpm and self._rpm_controller:
|
543
614
|
self._rpm_controller.stop_rpm_counter()
|
544
615
|
|
545
|
-
return user_prompt, dev_prompt, raw_response
|
616
|
+
return user_prompt, dev_prompt, raw_response, usage
|
546
617
|
|
547
618
|
|
548
619
|
@property
|
@@ -7,7 +7,7 @@ from versionhq.task.model import Task
|
|
7
7
|
from versionhq.agent.model import Agent
|
8
8
|
from versionhq.agent_network.model import AgentNetwork, Member, Formation
|
9
9
|
from versionhq.agent.inhouse_agents import vhq_formation_planner
|
10
|
-
from versionhq._utils import Logger
|
10
|
+
from versionhq._utils import Logger, is_valid_enum
|
11
11
|
|
12
12
|
import chromadb
|
13
13
|
chromadb.api.client.SharedSystemClient.clear_system_cache()
|
@@ -74,8 +74,8 @@ def form_agent_network(
|
|
74
74
|
leader_agent: str
|
75
75
|
|
76
76
|
vhq_task = Task(
|
77
|
-
description=f"Design a team of specialized agents to fully automate the following task and
|
78
|
-
|
77
|
+
description=f"Design a team of specialized agents to fully automate the following task and deliver the expected outcome. For each agent, define its role, task description, and expected outputs via the task with items in a list. Then specify the formation if the formation is not given. If you think SUPERVISING or HYBRID is the best formation, include a leader_agent role, else leave the leader_agent role blank.\nTask: {str(task)}\nExpected outcome: {prompt_expected_outcome}\nFormation: {prompt_formation}",
|
78
|
+
response_schema=Outcome
|
79
79
|
)
|
80
80
|
|
81
81
|
if agents:
|
@@ -83,14 +83,6 @@ def form_agent_network(
|
|
83
83
|
|
84
84
|
res = vhq_task.execute(agent=vhq_formation_planner, context=context)
|
85
85
|
|
86
|
-
formation_keys = []
|
87
|
-
if hasattr(res.pydantic, "formation"):
|
88
|
-
formation_keys = [k for k in Formation._member_map_.keys() if k == res.pydantic.formation.upper()]
|
89
|
-
elif "formation" in res.json_dict:
|
90
|
-
formation_keys = [k for k in Formation._member_map_.keys() if k == res.json_dict["formation"].upper()]
|
91
|
-
|
92
|
-
_formation = Formation[formation_keys[0]] if formation_keys else Formation.SUPERVISING
|
93
|
-
|
94
86
|
network_tasks = []
|
95
87
|
members = []
|
96
88
|
leader = res._fetch_value_of(key="leader_agent")
|
@@ -98,6 +90,8 @@ def form_agent_network(
|
|
98
90
|
created_agents = [Agent(role=str(item), goal=str(item)) for item in agent_roles] if agent_roles else []
|
99
91
|
task_descriptions = res._fetch_value_of(key="task_descriptions")
|
100
92
|
task_outcomes = res._fetch_value_of(key="task_outcomes")
|
93
|
+
formation_key = res.json_dict["formation"] if "formation" in res.json_dict else None
|
94
|
+
_formation = Formation[formation_key] if is_valid_enum(key=formation_key, enum=Formation) else Formation.SUPERVISING
|
101
95
|
|
102
96
|
if agents:
|
103
97
|
for i, agent in enumerate(created_agents):
|
@@ -120,7 +114,7 @@ def form_agent_network(
|
|
120
114
|
except:
|
121
115
|
pass
|
122
116
|
output = create_model("Output", **fields) if fields else None
|
123
|
-
_task = Task(description=task_descriptions[i],
|
117
|
+
_task = Task(description=task_descriptions[i], response_schema=output)
|
124
118
|
created_tasks.append(_task)
|
125
119
|
|
126
120
|
if len(created_tasks) <= len(created_agents):
|
versionhq/agent_network/model.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import uuid
|
2
2
|
import warnings
|
3
|
-
from enum import
|
4
|
-
from concurrent.futures import Future
|
3
|
+
from enum import IntEnum
|
5
4
|
from hashlib import md5
|
6
5
|
from typing import Any, Dict, List, Callable, Optional, Tuple
|
7
6
|
from typing_extensions import Self
|
@@ -30,7 +29,7 @@ GenerateSchema.match_type = match_type
|
|
30
29
|
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
31
30
|
|
32
31
|
|
33
|
-
class Formation(
|
32
|
+
class Formation(IntEnum):
|
34
33
|
SOLO = 1
|
35
34
|
SUPERVISING = 2
|
36
35
|
SQUAD = 3
|
@@ -38,7 +37,7 @@ class Formation(str, Enum):
|
|
38
37
|
HYBRID = 10
|
39
38
|
|
40
39
|
|
41
|
-
class TaskHandlingProcess(
|
40
|
+
class TaskHandlingProcess(IntEnum):
|
42
41
|
"""
|
43
42
|
A class representing task handling processes to tackle multiple tasks.
|
44
43
|
When the agent network has multiple tasks that connect with edges, follow the edge conditions.
|
@@ -206,7 +205,7 @@ class AgentNetwork(BaseModel):
|
|
206
205
|
for unassgined_task in unassigned_tasks:
|
207
206
|
task = Task(
|
208
207
|
description=f"Based on the following task summary, draft an agent's role and goal in concise manner. Task summary: {unassgined_task.summary}",
|
209
|
-
|
208
|
+
response_schema=[
|
210
209
|
ResponseField(title="goal", data_type=str, required=True),
|
211
210
|
ResponseField(title="role", data_type=str, required=True),
|
212
211
|
],
|