mojentic 0.5.4__py3-none-any.whl → 0.5.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.
- _examples/broker_examples.py +12 -22
- _examples/broker_image_examples.py +41 -0
- _examples/ephemeral_task_manager_example.py +48 -0
- _examples/streaming.py +1 -1
- _examples/tell_user_example.py +43 -0
- mojentic/llm/gateways/models.py +29 -1
- mojentic/llm/llm_broker_spec.py +0 -49
- mojentic/llm/message_composers_spec.py +0 -80
- mojentic/llm/tools/ask_user_tool.py +1 -1
- mojentic/llm/tools/ephemeral_task_manager/__init__.py +27 -0
- mojentic/llm/tools/ephemeral_task_manager/append_task_tool.py +77 -0
- mojentic/llm/tools/ephemeral_task_manager/append_task_tool_spec.py +34 -0
- mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool.py +57 -0
- mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool_spec.py +32 -0
- mojentic/llm/tools/ephemeral_task_manager/complete_task_tool.py +81 -0
- mojentic/llm/tools/ephemeral_task_manager/complete_task_tool_spec.py +43 -0
- mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list.py +202 -0
- mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list_spec.py +137 -0
- mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool.py +84 -0
- mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool_spec.py +42 -0
- mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool.py +80 -0
- mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool_spec.py +38 -0
- mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool.py +77 -0
- mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool_spec.py +34 -0
- mojentic/llm/tools/ephemeral_task_manager/start_task_tool.py +81 -0
- mojentic/llm/tools/ephemeral_task_manager/start_task_tool_spec.py +43 -0
- mojentic/llm/tools/llm_tool.py +15 -0
- mojentic/llm/tools/llm_tool_spec.py +68 -0
- mojentic/llm/tools/organic_web_search.py +37 -0
- mojentic/llm/tools/tell_user_tool.py +27 -0
- {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/METADATA +1 -1
- {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/RECORD +35 -13
- {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/WHEEL +1 -1
- mojentic/llm/tools/web_search.py +0 -35
- {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
|
|
6
|
+
from mojentic.llm.tools.ephemeral_task_manager.insert_task_after_tool import InsertTaskAfterTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_task_list():
|
|
11
|
+
mock = Mock(spec=EphemeralTaskList)
|
|
12
|
+
return mock
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def insert_task_after_tool(mock_task_list):
|
|
17
|
+
return InsertTaskAfterTool(task_list=mock_task_list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DescribeInsertTaskAfterTool:
|
|
21
|
+
def should_call_insert_task_after_with_correct_parameters(self, insert_task_after_tool, mock_task_list):
|
|
22
|
+
mock_task = Task(id=2, description="Test task", status=TaskStatus.PENDING)
|
|
23
|
+
mock_task_list.insert_task_after.return_value = mock_task
|
|
24
|
+
|
|
25
|
+
insert_task_after_tool.run(existing_task_id=1, description="Test task")
|
|
26
|
+
|
|
27
|
+
mock_task_list.insert_task_after.assert_called_once_with(existing_task_id=1, description="Test task")
|
|
28
|
+
|
|
29
|
+
def should_convert_string_task_id_to_int(self, insert_task_after_tool, mock_task_list):
|
|
30
|
+
mock_task = Task(id=2, description="Test task", status=TaskStatus.PENDING)
|
|
31
|
+
mock_task_list.insert_task_after.return_value = mock_task
|
|
32
|
+
|
|
33
|
+
insert_task_after_tool.run(existing_task_id="1", description="Test task")
|
|
34
|
+
|
|
35
|
+
mock_task_list.insert_task_after.assert_called_once_with(existing_task_id=1, description="Test task")
|
|
36
|
+
|
|
37
|
+
def should_handle_error_when_insert_task_after_fails(self, insert_task_after_tool, mock_task_list):
|
|
38
|
+
mock_task_list.insert_task_after.side_effect = ValueError("No task with ID '999' exists")
|
|
39
|
+
|
|
40
|
+
insert_task_after_tool.run(existing_task_id=999, description="Test task")
|
|
41
|
+
|
|
42
|
+
mock_task_list.insert_task_after.assert_called_once_with(existing_task_id=999, description="Test task")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool for listing all tasks in the ephemeral task manager.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task
|
|
8
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ListTasksTool(LLMTool):
|
|
12
|
+
"""
|
|
13
|
+
Tool for listing all tasks in the ephemeral task manager.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, task_list: EphemeralTaskList):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the tool with a shared task list.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
task_list: The shared task list to use
|
|
22
|
+
"""
|
|
23
|
+
self._task_list = task_list
|
|
24
|
+
|
|
25
|
+
def run(self) -> Dict[str, str]:
|
|
26
|
+
"""
|
|
27
|
+
Get all tasks in the list.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A dictionary with the result of the operation
|
|
31
|
+
"""
|
|
32
|
+
tasks = self._task_list.list_tasks()
|
|
33
|
+
|
|
34
|
+
# Format tasks as a string for the summary
|
|
35
|
+
task_list_str = self._format_tasks(tasks)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
"count": str(len(tasks)),
|
|
39
|
+
"tasks": task_list_str,
|
|
40
|
+
"summary": f"Found {len(tasks)} tasks\n\n{task_list_str}"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def _format_tasks(self, tasks: List[Task]) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Format a list of tasks as a string.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
tasks: The list of tasks to format
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
A formatted string representation of the tasks
|
|
52
|
+
"""
|
|
53
|
+
if not tasks:
|
|
54
|
+
return "No tasks found."
|
|
55
|
+
|
|
56
|
+
result = [f"{task.id}. {task.description} ({task.status.value})"
|
|
57
|
+
for i, task in enumerate(tasks)]
|
|
58
|
+
|
|
59
|
+
return "\n".join(result)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def descriptor(self):
|
|
63
|
+
"""
|
|
64
|
+
Get the descriptor for the tool.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The descriptor dictionary
|
|
68
|
+
"""
|
|
69
|
+
return {
|
|
70
|
+
"type": "function",
|
|
71
|
+
"function": {
|
|
72
|
+
"name": "list_tasks",
|
|
73
|
+
"description": "List all tasks in the task list.",
|
|
74
|
+
"parameters": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {},
|
|
77
|
+
"additionalProperties": False
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
|
|
6
|
+
from mojentic.llm.tools.ephemeral_task_manager.list_tasks_tool import ListTasksTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_task_list():
|
|
11
|
+
mock = Mock(spec=EphemeralTaskList)
|
|
12
|
+
return mock
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def list_tasks_tool(mock_task_list):
|
|
17
|
+
return ListTasksTool(task_list=mock_task_list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DescribeListTasksTool:
|
|
21
|
+
def should_call_list_tasks(self, list_tasks_tool, mock_task_list):
|
|
22
|
+
mock_tasks = [
|
|
23
|
+
Task(id=1, description="Task 1", status=TaskStatus.PENDING),
|
|
24
|
+
Task(id=2, description="Task 2", status=TaskStatus.IN_PROGRESS),
|
|
25
|
+
Task(id=3, description="Task 3", status=TaskStatus.COMPLETED)
|
|
26
|
+
]
|
|
27
|
+
mock_task_list.list_tasks.return_value = mock_tasks
|
|
28
|
+
|
|
29
|
+
list_tasks_tool.run()
|
|
30
|
+
|
|
31
|
+
mock_task_list.list_tasks.assert_called_once()
|
|
32
|
+
|
|
33
|
+
def should_handle_empty_list(self, list_tasks_tool, mock_task_list):
|
|
34
|
+
mock_task_list.list_tasks.return_value = []
|
|
35
|
+
|
|
36
|
+
list_tasks_tool.run()
|
|
37
|
+
|
|
38
|
+
mock_task_list.list_tasks.assert_called_once()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool for prepending a new task to the beginning of the ephemeral task manager list.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
8
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PrependTaskTool(LLMTool):
|
|
12
|
+
"""
|
|
13
|
+
Tool for prepending a new task to the beginning of the ephemeral task manager list.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, task_list: EphemeralTaskList):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the tool with a shared task list.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
task_list: The shared task list to use
|
|
22
|
+
"""
|
|
23
|
+
self._task_list = task_list
|
|
24
|
+
|
|
25
|
+
def run(self, description: str) -> Dict[str, str]:
|
|
26
|
+
"""
|
|
27
|
+
Prepend a new task to the beginning of the list.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
description: The description of the task
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A dictionary with the result of the operation
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If there's an error adding the task
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
task = self._task_list.prepend_task(description=description)
|
|
40
|
+
return {
|
|
41
|
+
"id": task.id,
|
|
42
|
+
"description": task.description,
|
|
43
|
+
"status": task.status.value,
|
|
44
|
+
"summary": f"Task '{task.id}' prepended successfully"
|
|
45
|
+
}
|
|
46
|
+
except ValueError as e:
|
|
47
|
+
return {
|
|
48
|
+
"error": str(e),
|
|
49
|
+
"summary": f"Failed to prepend task: {str(e)}"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def descriptor(self):
|
|
54
|
+
"""
|
|
55
|
+
Get the descriptor for the tool.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The descriptor dictionary
|
|
59
|
+
"""
|
|
60
|
+
return {
|
|
61
|
+
"type": "function",
|
|
62
|
+
"function": {
|
|
63
|
+
"name": "prepend_task",
|
|
64
|
+
"description": "Prepend a new task to the beginning of the task list with a description. The task will start with 'pending' status.",
|
|
65
|
+
"parameters": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"description": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "The description of the task"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"required": ["description"],
|
|
74
|
+
"additionalProperties": False
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
|
|
6
|
+
from mojentic.llm.tools.ephemeral_task_manager.prepend_task_tool import PrependTaskTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_task_list():
|
|
11
|
+
mock = Mock(spec=EphemeralTaskList)
|
|
12
|
+
return mock
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def prepend_task_tool(mock_task_list):
|
|
17
|
+
return PrependTaskTool(task_list=mock_task_list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DescribePrependTaskTool:
|
|
21
|
+
def should_call_prepend_task_with_description(self, prepend_task_tool, mock_task_list):
|
|
22
|
+
mock_task = Task(id=1, description="Test task", status=TaskStatus.PENDING)
|
|
23
|
+
mock_task_list.prepend_task.return_value = mock_task
|
|
24
|
+
|
|
25
|
+
prepend_task_tool.run(description="Test task")
|
|
26
|
+
|
|
27
|
+
mock_task_list.prepend_task.assert_called_once_with(description="Test task")
|
|
28
|
+
|
|
29
|
+
def should_handle_error_when_prepend_task_fails(self, prepend_task_tool, mock_task_list):
|
|
30
|
+
mock_task_list.prepend_task.side_effect = ValueError("Test error")
|
|
31
|
+
|
|
32
|
+
prepend_task_tool.run(description="Test task")
|
|
33
|
+
|
|
34
|
+
mock_task_list.prepend_task.assert_called_once_with(description="Test task")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool for starting a task in the ephemeral task manager.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
8
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StartTaskTool(LLMTool):
|
|
12
|
+
"""
|
|
13
|
+
Tool for starting a task in the ephemeral task manager.
|
|
14
|
+
|
|
15
|
+
This tool changes a task's status from PENDING to IN_PROGRESS.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, task_list: EphemeralTaskList):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the tool with a shared task list.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
task_list: The shared task list to use
|
|
24
|
+
"""
|
|
25
|
+
self._task_list = task_list
|
|
26
|
+
|
|
27
|
+
def run(self, id: int) -> Dict[str, str]:
|
|
28
|
+
"""
|
|
29
|
+
Start a task by changing its status from PENDING to IN_PROGRESS.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
id: The ID of the task to start
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A dictionary with the result of the operation
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: If no task with the given ID exists or if the task is not in PENDING status
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
# Convert id to int if it's a string
|
|
42
|
+
task_id = int(id) if isinstance(id, str) else id
|
|
43
|
+
task = self._task_list.start_task(id=task_id)
|
|
44
|
+
return {
|
|
45
|
+
"id": task.id,
|
|
46
|
+
"description": task.description,
|
|
47
|
+
"status": task.status.value,
|
|
48
|
+
"summary": f"Task '{id}' started successfully"
|
|
49
|
+
}
|
|
50
|
+
except ValueError as e:
|
|
51
|
+
return {
|
|
52
|
+
"error": str(e),
|
|
53
|
+
"summary": f"Failed to start task: {str(e)}"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def descriptor(self):
|
|
58
|
+
"""
|
|
59
|
+
Get the descriptor for the tool.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The descriptor dictionary
|
|
63
|
+
"""
|
|
64
|
+
return {
|
|
65
|
+
"type": "function",
|
|
66
|
+
"function": {
|
|
67
|
+
"name": "start_task",
|
|
68
|
+
"description": "Start a task by changing its status from PENDING to IN_PROGRESS.",
|
|
69
|
+
"parameters": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"id": {
|
|
73
|
+
"type": "integer",
|
|
74
|
+
"description": "The ID of the task to start"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"required": ["id"],
|
|
78
|
+
"additionalProperties": False
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
|
|
6
|
+
from mojentic.llm.tools.ephemeral_task_manager.start_task_tool import StartTaskTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_task_list():
|
|
11
|
+
mock = Mock(spec=EphemeralTaskList)
|
|
12
|
+
return mock
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def start_task_tool(mock_task_list):
|
|
17
|
+
return StartTaskTool(task_list=mock_task_list)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DescribeStartTaskTool:
|
|
21
|
+
def should_call_start_task_with_correct_id(self, start_task_tool, mock_task_list):
|
|
22
|
+
mock_task = Task(id=1, description="Test task", status=TaskStatus.IN_PROGRESS)
|
|
23
|
+
mock_task_list.start_task.return_value = mock_task
|
|
24
|
+
|
|
25
|
+
start_task_tool.run(id=1)
|
|
26
|
+
|
|
27
|
+
mock_task_list.start_task.assert_called_once_with(id=1)
|
|
28
|
+
|
|
29
|
+
def should_convert_string_id_to_int(self, start_task_tool, mock_task_list):
|
|
30
|
+
mock_task = Task(id=1, description="Test task", status=TaskStatus.IN_PROGRESS)
|
|
31
|
+
mock_task_list.start_task.return_value = mock_task
|
|
32
|
+
|
|
33
|
+
start_task_tool.run(id="1")
|
|
34
|
+
|
|
35
|
+
mock_task_list.start_task.assert_called_once_with(id=1)
|
|
36
|
+
|
|
37
|
+
def should_handle_error_when_start_task_fails(self, start_task_tool, mock_task_list):
|
|
38
|
+
error_message = "Task '1' cannot be started because it is not in PENDING status"
|
|
39
|
+
mock_task_list.start_task.side_effect = ValueError(error_message)
|
|
40
|
+
|
|
41
|
+
start_task_tool.run(id=1)
|
|
42
|
+
|
|
43
|
+
mock_task_list.start_task.assert_called_once_with(id=1)
|
mojentic/llm/tools/llm_tool.py
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from mojentic.llm.gateways.models import TextContent
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
class LLMTool:
|
|
2
7
|
def run(self, **kwargs):
|
|
3
8
|
raise NotImplementedError
|
|
4
9
|
|
|
10
|
+
def call_tool(self, **kwargs):
|
|
11
|
+
result = self.run(**kwargs)
|
|
12
|
+
if isinstance(result, dict):
|
|
13
|
+
result = json.dumps(result)
|
|
14
|
+
return {
|
|
15
|
+
"content": [
|
|
16
|
+
TextContent(type="text", text=result),
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
@property
|
|
6
21
|
def descriptor(self):
|
|
7
22
|
raise NotImplementedError
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import Mock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from mojentic.llm.gateways.models import TextContent
|
|
7
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MockLLMTool(LLMTool):
|
|
11
|
+
"""A mock implementation of LLMTool for testing purposes."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, run_result=None):
|
|
14
|
+
self._run_result = run_result
|
|
15
|
+
self._descriptor = {
|
|
16
|
+
"function": {
|
|
17
|
+
"name": "mock_tool",
|
|
18
|
+
"description": "A mock tool for testing"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def run(self, **kwargs):
|
|
23
|
+
return self._run_result
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def descriptor(self):
|
|
27
|
+
return self._descriptor
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def mock_tool_with_dict_result():
|
|
32
|
+
return MockLLMTool(run_result={"key": "value"})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_tool_with_string_result():
|
|
37
|
+
return MockLLMTool(run_result="test result")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DescribeLLMTool:
|
|
41
|
+
class DescribeCallTool:
|
|
42
|
+
|
|
43
|
+
def should_convert_dict_result_to_json_string(self, mock_tool_with_dict_result):
|
|
44
|
+
result = mock_tool_with_dict_result.call_tool()
|
|
45
|
+
|
|
46
|
+
assert "content" in result
|
|
47
|
+
assert isinstance(result["content"], list)
|
|
48
|
+
assert len(result["content"]) == 1
|
|
49
|
+
assert isinstance(result["content"][0], TextContent)
|
|
50
|
+
assert result["content"][0].text == json.dumps({"key": "value"})
|
|
51
|
+
|
|
52
|
+
def should_handle_string_result_directly(self, mock_tool_with_string_result):
|
|
53
|
+
result = mock_tool_with_string_result.call_tool()
|
|
54
|
+
|
|
55
|
+
assert "content" in result
|
|
56
|
+
assert isinstance(result["content"], list)
|
|
57
|
+
assert len(result["content"]) == 1
|
|
58
|
+
assert isinstance(result["content"][0], TextContent)
|
|
59
|
+
assert result["content"][0].text == "test result"
|
|
60
|
+
|
|
61
|
+
def should_pass_kwargs_to_run_method(self):
|
|
62
|
+
mock_run = Mock(return_value="test result")
|
|
63
|
+
tool = MockLLMTool()
|
|
64
|
+
tool.run = mock_run
|
|
65
|
+
|
|
66
|
+
tool.call_tool(param1="value1", param2="value2")
|
|
67
|
+
|
|
68
|
+
mock_run.assert_called_once_with(param1="value1", param2="value2")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import serpapi
|
|
5
|
+
|
|
6
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OrganicWebSearchTool(LLMTool):
|
|
10
|
+
def __init__(self, api_key: str = None):
|
|
11
|
+
self.api_key = api_key or os.getenv("SERPAPI_API_KEY")
|
|
12
|
+
|
|
13
|
+
def run(self, query: str, engine: str = "google", location: str = None, hl: str = "en", gl="ca") -> str:
|
|
14
|
+
results = serpapi.search(q=query, engine=engine, location=location, hl=hl, gl=gl, api_key=self.api_key)
|
|
15
|
+
# Limiting this to the organic results cuts this from 60K to 3K tokens
|
|
16
|
+
return json.dumps(results['organic_results'], indent=2)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def descriptor(self):
|
|
20
|
+
return {
|
|
21
|
+
"type": "function",
|
|
22
|
+
"function": {
|
|
23
|
+
"name": "organic_web_search",
|
|
24
|
+
"description":
|
|
25
|
+
"Search the Internet for information matching the given query and return the organic search results.",
|
|
26
|
+
"parameters": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"query": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The search query."
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"required": ["query"]
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from mojentic.llm.tools.llm_tool import LLMTool
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TellUserTool(LLMTool):
|
|
5
|
+
def run(self, message: str) -> str:
|
|
6
|
+
print(f"\n\n\nMESSAGE FROM ASSISTANT:\n{message}")
|
|
7
|
+
return "Message delivered to user."
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def descriptor(self):
|
|
11
|
+
return {
|
|
12
|
+
"type": "function",
|
|
13
|
+
"function": {
|
|
14
|
+
"name": "tell_user",
|
|
15
|
+
"description": "Display a message to the user without expecting a response. Use this to send important intermediate information to the user as you work on completing their request.",
|
|
16
|
+
"parameters": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"message": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "The important message you want to display to the user."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"required": ["message"]
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mojentic
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
4
4
|
Summary: Mojentic is an agentic framework that aims to provide a simple and flexible way to assemble teams of agents to solve complex problems.
|
|
5
5
|
Author-email: Stacey Vetzal <stacey@vetzal.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/mojility/mojentic
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
_examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
_examples/broker_as_tool.py,sha256=axLrX9lzQAAxSTCmm1njvOk63EWfVRG3NU6pLiMyYH8,2962
|
|
3
|
-
_examples/broker_examples.py,sha256=
|
|
3
|
+
_examples/broker_examples.py,sha256=7tES2KyKc3uigBuxkwOTf5UGhp7nVeVqLhd8fEHSWEc,1901
|
|
4
|
+
_examples/broker_image_examples.py,sha256=8dpZ2RFmRYhJqoeWklzlPxmvj9m4M393452SO_D2bJY,1133
|
|
4
5
|
_examples/characterize_ollama.py,sha256=_TzPEAoTuB-saTiaypEQAsUpiBeM37l9I5wCMU3GM4E,2521
|
|
5
6
|
_examples/characterize_openai.py,sha256=JQQNjsHIEegjLAS3uzDmq_Ci_ZqXTqjcoVaC-rS_R_8,564
|
|
6
7
|
_examples/chat_session.py,sha256=7mLpH4IGQUgyyiY-fYbikDEiR5vEH-V5Z1qtB50d_qg,288
|
|
@@ -10,6 +11,7 @@ _examples/current_datetime_tool_example.py,sha256=Ihjwp9svf2rf4WUGOzs6L_rEqc5QhP
|
|
|
10
11
|
_examples/design_analysis.py,sha256=66JxvnA065QV__WXpxJX_eTBDan34nGcKvao75UPLlw,1321
|
|
11
12
|
_examples/embeddings.py,sha256=94DAMsMB35BU98hOfOeOsbqntcequFSdtaStox2hTvk,267
|
|
12
13
|
_examples/ensures_files_exist.py,sha256=LAwX9YtUZGI6NT254PY7oC5yg6q-b9Q_Dez9gGrabQM,2009
|
|
14
|
+
_examples/ephemeral_task_manager_example.py,sha256=Q1YpLPwDkB-ytIGgCh2eWOr5EJ0FE-osxJ0B0wDlQkY,1491
|
|
13
15
|
_examples/file_deduplication.py,sha256=jYayx4BCrk1uJwVBkjIXzguLQOWLREi4PBPzoK9LuOU,1665
|
|
14
16
|
_examples/file_tool.py,sha256=F8xU7JdTScNzEXyGYqK6D_xQ28MbUXXm854WJmJkZo8,2214
|
|
15
17
|
_examples/image_analysis.py,sha256=Kj49vLQD1DIpvv5P7rir4BqzsVCTgq-tfppqXcYVJkA,503
|
|
@@ -27,7 +29,8 @@ _examples/simple_llm_repl.py,sha256=bpk7S1-yQ-oZdRD_0ICV37NQxntgvgjiFD5KdIU40Ig,
|
|
|
27
29
|
_examples/simple_structured.py,sha256=9nutIoPNFdtwWCfKkUuvFmsBIzcfhBqlonRWGVYDmJo,1327
|
|
28
30
|
_examples/simple_tool.py,sha256=9hRo2YZk7NmprTIkLmEyueCbZ0Ls1Ey4l4eDvvgoWy8,1288
|
|
29
31
|
_examples/solver_chat_session.py,sha256=4mTsgQVJWwujDFX6NUIK6kKr45E09Q4yIkZF-FdoTtg,2148
|
|
30
|
-
_examples/streaming.py,sha256=
|
|
32
|
+
_examples/streaming.py,sha256=P1CGvm11yi_tojnha2Qmp_OFB5A3T-F5dWIzxEclNhM,1137
|
|
33
|
+
_examples/tell_user_example.py,sha256=gPGS_3KAHUIFsnvkDXX6u1mVqlsB67zsyNdGuEho60o,1274
|
|
31
34
|
_examples/working_memory.py,sha256=xbCHb6Y0Um6ai5T9fWAX4oQOcCQJDinsn902qm8q48E,1935
|
|
32
35
|
_examples/react/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
36
|
_examples/react/formatters.py,sha256=nbgWve0YGKaRY6P4tXV84PYw8YDN4neefiq7mR2I_tI,1065
|
|
@@ -59,16 +62,16 @@ mojentic/llm/__init__.py,sha256=mwdPpTRofw7_KSlW6ZQmcM-GSpyISWyI2bxphKpV7A0,180
|
|
|
59
62
|
mojentic/llm/chat_session.py,sha256=H2gY0mZYVym8jC69VHsmKaRZ9T87Suyw0-TW5r850nA,3992
|
|
60
63
|
mojentic/llm/chat_session_spec.py,sha256=8-jj-EHV2WwWuvo3t8I75kSEAYiG1nR-OEwkkLTi_z0,3872
|
|
61
64
|
mojentic/llm/llm_broker.py,sha256=8dEgqU-cPesPk4-jx36oPnvKxB34bLONbpyAW_2L-es,5884
|
|
62
|
-
mojentic/llm/llm_broker_spec.py,sha256=
|
|
65
|
+
mojentic/llm/llm_broker_spec.py,sha256=40lzmYm_6Zje6z5MQ7_o3gSBThLsNW_l_1mZTUVll6A,5342
|
|
63
66
|
mojentic/llm/message_composers.py,sha256=Fo9o7UGZOOIYoGI_DyOfP_oMiEiCMQz-zdWdTKtozVk,12108
|
|
64
|
-
mojentic/llm/message_composers_spec.py,sha256=
|
|
67
|
+
mojentic/llm/message_composers_spec.py,sha256=VbaDR-fXWogI5ONSpkzvgPY9Tv9HQX6eEqB8J0ev2jQ,11641
|
|
65
68
|
mojentic/llm/gateways/__init__.py,sha256=u7hXzngoRw_qbsJeiCH2NQ8vC2hF5DnqcXsfLVVPSSw,104
|
|
66
69
|
mojentic/llm/gateways/anthropic.py,sha256=SsyNjq9QaXaqiMM43C9fwLp57hpgFtwNPJUnOAYVrtc,1788
|
|
67
70
|
mojentic/llm/gateways/anthropic_messages_adapter.py,sha256=K6kEZeVt7E1endbGMLsh5l9SxC3Y5dnvbcejVqi_qUs,3003
|
|
68
71
|
mojentic/llm/gateways/embeddings_gateway.py,sha256=kcOhiyHzOyQgKgwPDQJD5oVvfwk71GsBgMYJOIDv5NU,1347
|
|
69
72
|
mojentic/llm/gateways/file_gateway.py,sha256=3bZpalSyl_R4016WzCmmjUBDtAgPsmx19eVGv6p1Ufk,1418
|
|
70
73
|
mojentic/llm/gateways/llm_gateway.py,sha256=5BayP6VuMgMHdAzCFaXLRjRCWh-IOYnaq_s4LZ0_3x4,2559
|
|
71
|
-
mojentic/llm/gateways/models.py,sha256=
|
|
74
|
+
mojentic/llm/gateways/models.py,sha256=lnGvr3E4C5n15v0aI8Cc0FMOH6GBCrk5_XaEEe3vKkM,3015
|
|
72
75
|
mojentic/llm/gateways/ollama.py,sha256=629fpZhC0zVCYqj360-PKTT4mQOLec5nzzvfMtS_mLQ,7581
|
|
73
76
|
mojentic/llm/gateways/ollama_messages_adapter.py,sha256=kUN_p2FyN88_trXMcL-Xsn9xPBU7pGKlJwTUEUCf6G4,1404
|
|
74
77
|
mojentic/llm/gateways/ollama_messages_adapter_spec.py,sha256=gVRbWDrHOa1EiZ0CkEWe0pGn-GKRqdGb-x56HBQeYSE,4981
|
|
@@ -81,19 +84,38 @@ mojentic/llm/registry/llm_registry.py,sha256=beyrgGrkXx5ZckUJzC1nQ461vra0fF6s_qR
|
|
|
81
84
|
mojentic/llm/registry/models.py,sha256=XOZ0ElTL5mEicpcUk9lrVr0GRFgR1uFmDm7GGWm7yYg,2043
|
|
82
85
|
mojentic/llm/registry/populate_registry_from_ollama.py,sha256=YFlnyXW-Htveu8frPEntV-c_84Xoh_WhHGww5IqtPIg,2539
|
|
83
86
|
mojentic/llm/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
-
mojentic/llm/tools/ask_user_tool.py,sha256=
|
|
87
|
+
mojentic/llm/tools/ask_user_tool.py,sha256=TiLVq1OttptStwxNvZkxJTRCIohf7SqweB2nT4icFYY,984
|
|
85
88
|
mojentic/llm/tools/current_datetime.py,sha256=JkIFlBBnvE4CMcgZizqITVTtNfL7zeeLxD54E7oMeC8,1585
|
|
86
89
|
mojentic/llm/tools/date_resolver.py,sha256=VDxX59PTHo0PstDxaJUo3SF7vkvQoG92bo6j8ABq8y4,2185
|
|
87
90
|
mojentic/llm/tools/date_resolver_spec.py,sha256=OaRvJyhSN8sgi75euk4E5ImaqUmvQdgZKY8u_NOiPWE,1185
|
|
88
91
|
mojentic/llm/tools/file_manager.py,sha256=X8Uw4XtdH_Ol2EB3SN11-GYlC1diJ1cAywU9_EuCjCg,3788
|
|
89
|
-
mojentic/llm/tools/llm_tool.py,sha256=
|
|
92
|
+
mojentic/llm/tools/llm_tool.py,sha256=i81TNkTiFNMtx-HAGV2bhRrTqoqzdxiPcI0lx05W3A4,745
|
|
93
|
+
mojentic/llm/tools/llm_tool_spec.py,sha256=mDRBXrrNvKh-8ZBq6_AbnU1nqdjtdkP_C2XmztllGQM,2071
|
|
94
|
+
mojentic/llm/tools/organic_web_search.py,sha256=X_WQSSFgeMp5jlm0tnqGOHDIZ2KHDvz3k6GF-XqsuN4,1301
|
|
95
|
+
mojentic/llm/tools/tell_user_tool.py,sha256=bF_PTfF0-skCi_Exw6EPpdfoNUZRJ4MWg-XCe4VYd6s,999
|
|
90
96
|
mojentic/llm/tools/tool_wrapper.py,sha256=6YZOhckgNsSUc7YK6bKjODjRTwi6wcH9bdcySUDPt3Q,1391
|
|
91
97
|
mojentic/llm/tools/tool_wrapper_spec.py,sha256=LGqtre-g8SzOy3xtpbMdgTnw2EdYutmFOsMpediE8_0,2543
|
|
92
|
-
mojentic/llm/tools/
|
|
98
|
+
mojentic/llm/tools/ephemeral_task_manager/__init__.py,sha256=npuEwAAks8IZtbQCspAtk1K2kLRVfbfs_o9k1kJIPa4,1193
|
|
99
|
+
mojentic/llm/tools/ephemeral_task_manager/append_task_tool.py,sha256=A1nNF9T6ht-B6WC1CAO1F9T-PGHafwBGvETTMI2RENU,2317
|
|
100
|
+
mojentic/llm/tools/ephemeral_task_manager/append_task_tool_spec.py,sha256=cqJ2HSAgBge3S76-hC6T8Bv1Ox3buWeSdsEpVbMjNPs,1159
|
|
101
|
+
mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool.py,sha256=GCBGVVlRnYS3llZwMUgNgsKBkZKu4FFTaKTb95F9b10,1476
|
|
102
|
+
mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool_spec.py,sha256=-MMHdu1e8CVfyDeKIpxfI5wyi26Uob3weDPR9rNPEe0,948
|
|
103
|
+
mojentic/llm/tools/ephemeral_task_manager/complete_task_tool.py,sha256=ZW_7DYRLruErdeuBUBuEfgKgE4Hlf-p5lY3W14D5Au8,2454
|
|
104
|
+
mojentic/llm/tools/ephemeral_task_manager/complete_task_tool_spec.py,sha256=qFTJHwLAASyo89kVGX2ATb-2x2hbkxz9hRALMUsZ8RE,1549
|
|
105
|
+
mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list.py,sha256=jq37585jnSgIARQ0SgB_PQylSv9qI2_igoyr8ZMHUCc,5430
|
|
106
|
+
mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list_spec.py,sha256=TmaQCsPoZ7IYSnaRiMj8Y7wuaZEzEuGK1n8aSjZQORg,4740
|
|
107
|
+
mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool.py,sha256=hoVMu9_-cDYIIrAf66rIPIj_qep1sXd5JbgT4ZfLFdk,2947
|
|
108
|
+
mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool_spec.py,sha256=8UrjnLlEjF28RLPnsg5CESuSmaVUGULMdUjtYu5KW9w,1791
|
|
109
|
+
mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool.py,sha256=jnkmmxOcytygDDvqEZT8cdqDZCedyC54FI2Gee7igrA,2103
|
|
110
|
+
mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool_spec.py,sha256=xwPAhx5ix7In8NFMm6VVbxoYG1iwLHcn_7wqMHE6HH8,1162
|
|
111
|
+
mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool.py,sha256=GGqPTdBTkjhXMyNcWAJcCkuVBxGB3xmTIyKV9eDmwNA,2349
|
|
112
|
+
mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool_spec.py,sha256=lUCnejAON9g0DDNS64EVHwapPjF8b87RsXPUVvvG0b4,1174
|
|
113
|
+
mojentic/llm/tools/ephemeral_task_manager/start_task_tool.py,sha256=VH8Wa4-ZmkMIsyLuLXJx4pGidiT0KeT55DnujFpK-0o,2414
|
|
114
|
+
mojentic/llm/tools/ephemeral_task_manager/start_task_tool_spec.py,sha256=oDeVPPfquM2ojwG0aaNnbWCB_LRgm8runtG-CumZkLg,1490
|
|
93
115
|
mojentic/utils/__init__.py,sha256=lqECkkoFvHFttDnafRE1vvh0Dmna_lwupMToP5VvX5k,115
|
|
94
116
|
mojentic/utils/formatting.py,sha256=bPrwwdluXdQ8TsFxfWtHNOeMWKNvAfABSoUnnA1g7c8,947
|
|
95
|
-
mojentic-0.5.
|
|
96
|
-
mojentic-0.5.
|
|
97
|
-
mojentic-0.5.
|
|
98
|
-
mojentic-0.5.
|
|
99
|
-
mojentic-0.5.
|
|
117
|
+
mojentic-0.5.6.dist-info/licenses/LICENSE.md,sha256=txSgV8n5zY1W3NiF5HHsCwlaW0e8We1cSC6TuJUqxXA,1060
|
|
118
|
+
mojentic-0.5.6.dist-info/METADATA,sha256=4kNiXBHxU0wqwBFCBWww1rmoggWOh1om23j8NMAgei8,4956
|
|
119
|
+
mojentic-0.5.6.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
120
|
+
mojentic-0.5.6.dist-info/top_level.txt,sha256=Q-BvPQ8Eu1jnEqK8Xkr6A9C8Xa1z38oPZRHuA5MCTqg,19
|
|
121
|
+
mojentic-0.5.6.dist-info/RECORD,,
|