amrita_core 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {amrita_core-0.1.0/src/amrita_core.egg-info → amrita_core-0.2.0}/PKG-INFO +3 -1
- {amrita_core-0.1.0 → amrita_core-0.2.0}/README.md +3 -1
- {amrita_core-0.1.0 → amrita_core-0.2.0}/pyproject.toml +1 -1
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/__init__.py +2 -1
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/chatmanager.py +26 -20
- amrita_core-0.2.0/src/amrita_core/tools/manager.py +368 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0/src/amrita_core.egg-info}/PKG-INFO +3 -1
- amrita_core-0.1.0/src/amrita_core/tools/manager.py +0 -163
- {amrita_core-0.1.0 → amrita_core-0.2.0}/LICENSE +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/setup.cfg +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/builtins/__init__.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/builtins/adapter.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/builtins/agent.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/builtins/tools.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/config.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/hook/event.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/hook/exception.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/hook/matcher.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/hook/on.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/libchat.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/logging.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/preset.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/protocol.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/tokenizer.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/tools/mcp.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/tools/models.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/types.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core/utils.py +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core.egg-info/SOURCES.txt +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core.egg-info/dependency_links.txt +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core.egg-info/requires.txt +0 -0
- {amrita_core-0.1.0 → amrita_core-0.2.0}/src/amrita_core.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amrita_core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Agent core of Project Amrita
|
|
5
5
|
Project-URL: Homepage, https://github.com/AmritaBot/AmritaCore
|
|
6
6
|
Project-URL: Source, https://github.com/AmritaBot/AmritaCore
|
|
@@ -60,6 +60,8 @@ This repository contains documentation organized as follows:
|
|
|
60
60
|
|
|
61
61
|
Documentation is currently under construction. For quick start, please refer to the examples in the `demo/` folder.
|
|
62
62
|
|
|
63
|
+
Please view [Docs](https://amrita-core.suggar.top) for more information.
|
|
64
|
+
|
|
63
65
|
## 🛠️ Quick Start
|
|
64
66
|
|
|
65
67
|
To quickly start using AmritaCore, check out the examples in the [demo](./demo/) directory. The basic example demonstrates how to initialize the core, configure settings, and run a simple chat session with the AI assistant.
|
|
@@ -42,6 +42,8 @@ This repository contains documentation organized as follows:
|
|
|
42
42
|
|
|
43
43
|
Documentation is currently under construction. For quick start, please refer to the examples in the `demo/` folder.
|
|
44
44
|
|
|
45
|
+
Please view [Docs](https://amrita-core.suggar.top) for more information.
|
|
46
|
+
|
|
45
47
|
## 🛠️ Quick Start
|
|
46
48
|
|
|
47
49
|
To quickly start using AmritaCore, check out the examples in the [demo](./demo/) directory. The basic example demonstrates how to initialize the core, configure settings, and run a simple chat session with the AI assistant.
|
|
@@ -52,4 +54,4 @@ We welcome contributions! Please see our contribution guidelines for more inform
|
|
|
52
54
|
|
|
53
55
|
## 📄 License
|
|
54
56
|
|
|
55
|
-
This project is licensed under the AGPL-3.0 License - see the [LICENSE](./LICENSE) file for details.
|
|
57
|
+
This project is licensed under the AGPL-3.0 License - see the [LICENSE](./LICENSE) file for details.
|
|
@@ -13,7 +13,7 @@ from .libchat import (
|
|
|
13
13
|
from .logging import debug_log, logger
|
|
14
14
|
from .preset import PresetManager, PresetReport
|
|
15
15
|
from .tools import mcp
|
|
16
|
-
from .tools.manager import ToolsManager, on_tools
|
|
16
|
+
from .tools.manager import ToolsManager, on_tools, simple_tool
|
|
17
17
|
from .tools.models import (
|
|
18
18
|
FunctionDefinitionSchema,
|
|
19
19
|
FunctionParametersSchema,
|
|
@@ -73,6 +73,7 @@ __all__ = [
|
|
|
73
73
|
"on_precompletion",
|
|
74
74
|
"on_tools",
|
|
75
75
|
"set_config",
|
|
76
|
+
"simple_tool",
|
|
76
77
|
"text_generator",
|
|
77
78
|
"tools_caller",
|
|
78
79
|
]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import copy
|
|
3
5
|
from abc import ABC, abstractmethod
|
|
@@ -434,10 +436,9 @@ class ChatObject:
|
|
|
434
436
|
_is_running: bool = False # Whether it is running
|
|
435
437
|
_is_done: bool = False # Whether it has completed
|
|
436
438
|
_task: Task[None]
|
|
437
|
-
_has_task: bool = False
|
|
438
439
|
_err: BaseException | None = None
|
|
439
|
-
_wait: bool = True
|
|
440
440
|
_queue_done: bool = False
|
|
441
|
+
_has_consumer: bool = False
|
|
441
442
|
__done_marker = object()
|
|
442
443
|
|
|
443
444
|
def __init__(
|
|
@@ -446,7 +447,6 @@ class ChatObject:
|
|
|
446
447
|
user_input: USER_INPUT,
|
|
447
448
|
context: Memory,
|
|
448
449
|
session_id: str,
|
|
449
|
-
run_blocking: bool = True,
|
|
450
450
|
queue_size: int = 25,
|
|
451
451
|
overflow_queue_size: int = 45,
|
|
452
452
|
) -> None:
|
|
@@ -468,7 +468,6 @@ class ChatObject:
|
|
|
468
468
|
self.time = datetime.now(utc)
|
|
469
469
|
self.config: AmritaConfig = get_config()
|
|
470
470
|
self.last_call = datetime.now(utc)
|
|
471
|
-
self._wait = run_blocking
|
|
472
471
|
|
|
473
472
|
# Initialize async queue for streaming responses
|
|
474
473
|
self.response_queue = asyncio.Queue(queue_size)
|
|
@@ -484,15 +483,6 @@ class ChatObject:
|
|
|
484
483
|
"""
|
|
485
484
|
return self._err
|
|
486
485
|
|
|
487
|
-
def call(self):
|
|
488
|
-
"""
|
|
489
|
-
Get callable object
|
|
490
|
-
|
|
491
|
-
Returns:
|
|
492
|
-
Callable object (usually the class's __call__ method)
|
|
493
|
-
"""
|
|
494
|
-
return self.__call__()
|
|
495
|
-
|
|
496
486
|
def is_running(self) -> bool:
|
|
497
487
|
"""
|
|
498
488
|
Check if the task is running
|
|
@@ -518,14 +508,31 @@ class ChatObject:
|
|
|
518
508
|
"""
|
|
519
509
|
self._is_done = True
|
|
520
510
|
self._is_running = False
|
|
521
|
-
self._task.
|
|
511
|
+
if hasattr(self, "_task") and not self._task.done():
|
|
512
|
+
self._task.cancel()
|
|
522
513
|
|
|
523
514
|
def __await__(self):
|
|
524
515
|
if not hasattr(self, "_task"):
|
|
525
516
|
raise RuntimeError("ChatObject not running")
|
|
526
517
|
return self._task.__await__()
|
|
527
518
|
|
|
528
|
-
async def
|
|
519
|
+
async def __aenter__(self) -> Self:
|
|
520
|
+
if not hasattr(self, "_task"):
|
|
521
|
+
raise RuntimeError("ChatObject not running")
|
|
522
|
+
if self._has_consumer:
|
|
523
|
+
raise RuntimeError("ChatObject already has a consumer")
|
|
524
|
+
return self
|
|
525
|
+
|
|
526
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
527
|
+
self.terminate()
|
|
528
|
+
|
|
529
|
+
def begin(self) -> Self:
|
|
530
|
+
if not hasattr(self, "_task"):
|
|
531
|
+
logger.debug("Starting chat object task...")
|
|
532
|
+
self._task = asyncio.create_task(self._entry())
|
|
533
|
+
return self
|
|
534
|
+
|
|
535
|
+
async def _entry(self) -> None:
|
|
529
536
|
"""Call chat object to process messages
|
|
530
537
|
|
|
531
538
|
Args:
|
|
@@ -533,11 +540,6 @@ class ChatObject:
|
|
|
533
540
|
matcher: Matcher
|
|
534
541
|
bot: Bot instance
|
|
535
542
|
"""
|
|
536
|
-
if not self._has_task:
|
|
537
|
-
logger.debug("Starting chat object task...")
|
|
538
|
-
self._has_task = True
|
|
539
|
-
self._task = asyncio.create_task(self.__call__())
|
|
540
|
-
return await self._task if self._wait else None
|
|
541
543
|
if not self._is_running and not self._is_done:
|
|
542
544
|
self.stream_id = uuid4().hex
|
|
543
545
|
logger.debug(f"Starting chat processing, stream ID:{self.stream_id}")
|
|
@@ -553,6 +555,7 @@ class ChatObject:
|
|
|
553
555
|
self._is_done = True
|
|
554
556
|
self.end_at = datetime.now(utc)
|
|
555
557
|
chat_manager.running_chat_object_id2map.pop(self.stream_id, None)
|
|
558
|
+
chat_manager.clean_obj(self.session_id, 1000) # To avoid memory leaks
|
|
556
559
|
logger.debug("Chat event processing completed")
|
|
557
560
|
|
|
558
561
|
else:
|
|
@@ -686,6 +689,9 @@ class ChatObject:
|
|
|
686
689
|
Yields:
|
|
687
690
|
Either a string or MessageContent object from the response queue
|
|
688
691
|
"""
|
|
692
|
+
if self._has_consumer:
|
|
693
|
+
raise RuntimeError("Queue is already being consumed.")
|
|
694
|
+
self._has_consumer = True
|
|
689
695
|
return self._response_generator()
|
|
690
696
|
|
|
691
697
|
async def full_response(self) -> str:
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import re
|
|
3
|
+
import typing
|
|
4
|
+
from asyncio import iscoroutinefunction
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Any, ClassVar, get_args, get_origin, get_type_hints, overload
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
11
|
+
from .models import (
|
|
12
|
+
JSON_OBJECT_TYPE,
|
|
13
|
+
FunctionDefinitionSchema,
|
|
14
|
+
FunctionParametersSchema,
|
|
15
|
+
FunctionPropertySchema,
|
|
16
|
+
ToolContext,
|
|
17
|
+
ToolData,
|
|
18
|
+
ToolFunctionSchema,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
T = typing.TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ToolsManager:
|
|
25
|
+
_instance = None
|
|
26
|
+
_models: ClassVar[dict[str, ToolData]] = {}
|
|
27
|
+
_disabled_tools: ClassVar[set[str]] = (
|
|
28
|
+
set()
|
|
29
|
+
) # Disabled tools, has_tool and get_tool will not return disabled tools
|
|
30
|
+
|
|
31
|
+
def __new__(cls) -> Self:
|
|
32
|
+
if cls._instance is None:
|
|
33
|
+
cls._instance = super().__new__(cls)
|
|
34
|
+
return cls._instance
|
|
35
|
+
|
|
36
|
+
def has_tool(self, name: str) -> bool:
|
|
37
|
+
return False if name in self._disabled_tools else name in self._models
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def get_tool(self, name: str) -> ToolData | None: ...
|
|
41
|
+
@overload
|
|
42
|
+
def get_tool(self, name: str, default: T) -> ToolData | T: ...
|
|
43
|
+
def get_tool(self, name: str, default: T = None) -> ToolData | T | None:
|
|
44
|
+
if not self.has_tool(name):
|
|
45
|
+
return default
|
|
46
|
+
tool: ToolData = self._models[name]
|
|
47
|
+
return tool if tool.enable_if() else default
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
def get_tool_meta(self, name: str) -> ToolFunctionSchema | None: ...
|
|
51
|
+
@overload
|
|
52
|
+
def get_tool_meta(self, name: str, default: T) -> ToolFunctionSchema | T: ...
|
|
53
|
+
def get_tool_meta(
|
|
54
|
+
self, name: str, default: T | None = None
|
|
55
|
+
) -> ToolFunctionSchema | None | T:
|
|
56
|
+
func_data = self.get_tool(name)
|
|
57
|
+
if func_data is None:
|
|
58
|
+
return default
|
|
59
|
+
if isinstance(func_data, ToolData):
|
|
60
|
+
return func_data.data
|
|
61
|
+
return default
|
|
62
|
+
|
|
63
|
+
@overload
|
|
64
|
+
def get_tool_func(
|
|
65
|
+
self, name: str, default: T
|
|
66
|
+
) -> (
|
|
67
|
+
Callable[[dict[str, Any]], Awaitable[str]]
|
|
68
|
+
| Callable[[ToolContext], Awaitable[str | None]]
|
|
69
|
+
| T
|
|
70
|
+
): ...
|
|
71
|
+
@overload
|
|
72
|
+
def get_tool_func(
|
|
73
|
+
self,
|
|
74
|
+
name: str,
|
|
75
|
+
) -> (
|
|
76
|
+
Callable[[dict[str, Any]], Awaitable[str]]
|
|
77
|
+
| Callable[[ToolContext], Awaitable[str | None]]
|
|
78
|
+
| None
|
|
79
|
+
): ...
|
|
80
|
+
def get_tool_func(
|
|
81
|
+
self, name: str, default: T | None = None
|
|
82
|
+
) -> (
|
|
83
|
+
Callable[[dict[str, Any]], Awaitable[str]]
|
|
84
|
+
| Callable[[ToolContext], Awaitable[str | None]]
|
|
85
|
+
| None
|
|
86
|
+
| T
|
|
87
|
+
):
|
|
88
|
+
func_data = self.get_tool(name)
|
|
89
|
+
if func_data is None:
|
|
90
|
+
return default
|
|
91
|
+
if isinstance(func_data, ToolData):
|
|
92
|
+
return func_data.func
|
|
93
|
+
return default
|
|
94
|
+
|
|
95
|
+
def get_tools(self) -> dict[str, ToolData]:
|
|
96
|
+
return {
|
|
97
|
+
name: data
|
|
98
|
+
for name, data in self._models.items()
|
|
99
|
+
if (name not in self._disabled_tools and data.enable_if())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def tools_meta(self) -> dict[str, ToolFunctionSchema]:
|
|
103
|
+
return {
|
|
104
|
+
k: v.data
|
|
105
|
+
for k, v in self._models.items()
|
|
106
|
+
if (k not in self._disabled_tools and v.enable_if())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def tools_meta_dict(self, **kwargs) -> dict[str, dict[str, Any]]:
|
|
110
|
+
return {
|
|
111
|
+
k: v.data.model_dump(**kwargs)
|
|
112
|
+
for k, v in self._models.items()
|
|
113
|
+
if (k not in self._disabled_tools and v.enable_if())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def register_tool(self, tool: ToolData) -> None:
|
|
117
|
+
if tool.data.function.name not in self._models:
|
|
118
|
+
self._models[tool.data.function.name] = tool
|
|
119
|
+
else:
|
|
120
|
+
raise ValueError(f"Tool {tool.data.function.name} already exists")
|
|
121
|
+
|
|
122
|
+
def remove_tool(self, name: str) -> None:
|
|
123
|
+
self._models.pop(name, None)
|
|
124
|
+
if name in self._disabled_tools:
|
|
125
|
+
self._disabled_tools.remove(name)
|
|
126
|
+
|
|
127
|
+
def enable_tool(self, name: str) -> None:
|
|
128
|
+
if name in self._disabled_tools:
|
|
129
|
+
self._disabled_tools.remove(name)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"Tool {name} is not disabled")
|
|
132
|
+
|
|
133
|
+
def disable_tool(self, name: str) -> None:
|
|
134
|
+
if self.has_tool(name):
|
|
135
|
+
self._disabled_tools.add(name)
|
|
136
|
+
else:
|
|
137
|
+
raise ValueError(f"Tool {name} does not exist or has been disabled")
|
|
138
|
+
|
|
139
|
+
def get_disabled_tools(self) -> list[str]:
|
|
140
|
+
return list(self._disabled_tools)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _parse_google_docstring(docstring: str | None) -> tuple[str, dict[str, str]]:
|
|
144
|
+
"""
|
|
145
|
+
Parse Google-style docstring to extract function description and parameter descriptions.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
docstring: The docstring to parse
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
A tuple containing (function_description, parameter_descriptions_dict)
|
|
152
|
+
"""
|
|
153
|
+
if not docstring:
|
|
154
|
+
return "(no description provided for this tool)", {}
|
|
155
|
+
|
|
156
|
+
# Clean up the docstring
|
|
157
|
+
lines = [line.strip() for line in docstring.split("\n") if line.strip()]
|
|
158
|
+
|
|
159
|
+
# Find the index where Args section starts
|
|
160
|
+
args_start_idx = -1
|
|
161
|
+
for i, line in enumerate(lines):
|
|
162
|
+
if line.lower().startswith("args:"):
|
|
163
|
+
args_start_idx = i
|
|
164
|
+
break
|
|
165
|
+
|
|
166
|
+
# Extract function description (everything before Args section)
|
|
167
|
+
if args_start_idx != -1:
|
|
168
|
+
func_desc_lines = lines[:args_start_idx]
|
|
169
|
+
func_desc = " ".join(func_desc_lines).strip()
|
|
170
|
+
|
|
171
|
+
# Extract Args section
|
|
172
|
+
args_lines = lines[args_start_idx + 1 :]
|
|
173
|
+
else:
|
|
174
|
+
# No Args section found
|
|
175
|
+
func_desc = " ".join(lines).strip()
|
|
176
|
+
args_lines = []
|
|
177
|
+
|
|
178
|
+
# Process Args section
|
|
179
|
+
param_descriptions = {}
|
|
180
|
+
|
|
181
|
+
# Pattern to match parameter descriptions in the format:
|
|
182
|
+
# param_name (type): description
|
|
183
|
+
# or
|
|
184
|
+
# param_name: description
|
|
185
|
+
param_pattern = r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\(([^)]+)\))?\s*:\s*(.*)"
|
|
186
|
+
|
|
187
|
+
for line in args_lines:
|
|
188
|
+
# Look for parameter pattern at the beginning of the line or after whitespace
|
|
189
|
+
match = re.match(param_pattern, line)
|
|
190
|
+
if match:
|
|
191
|
+
param_name = match.group(1)
|
|
192
|
+
# param_type = match.group(2) # We don't need the type since it's in the annotation
|
|
193
|
+
param_desc = match.group(3).strip()
|
|
194
|
+
|
|
195
|
+
if param_desc:
|
|
196
|
+
param_descriptions[param_name] = param_desc
|
|
197
|
+
else:
|
|
198
|
+
param_descriptions[param_name] = f"Parameter {param_name}"
|
|
199
|
+
|
|
200
|
+
if not func_desc:
|
|
201
|
+
func_desc = "(no description provided for this tool)"
|
|
202
|
+
|
|
203
|
+
return func_desc, param_descriptions
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def simple_tool(func: Callable[..., Any | Awaitable[Any]]):
|
|
207
|
+
"""
|
|
208
|
+
A decorator that creates a ToolData object based on the function signature and annotations.
|
|
209
|
+
It automatically generates parameter descriptions and metadata.
|
|
210
|
+
|
|
211
|
+
Here is an example of how to use this decorator:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
@simple_tool
|
|
215
|
+
def add(a: int, b: int) -> int:
|
|
216
|
+
\"""Add two numbers together.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
a (int): The first number.
|
|
220
|
+
b (int): The second number.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
int: The sum of the two numbers.
|
|
224
|
+
\"""
|
|
225
|
+
return a + b
|
|
226
|
+
```
|
|
227
|
+
"""
|
|
228
|
+
signature: inspect.Signature = inspect.signature(func)
|
|
229
|
+
|
|
230
|
+
# Parse Google-style docstring to get function and parameter descriptions
|
|
231
|
+
func_desc, param_descriptions = _parse_google_docstring(func.__doc__)
|
|
232
|
+
|
|
233
|
+
# Get the type hints for the function
|
|
234
|
+
type_hints: dict[str, Any] = get_type_hints(
|
|
235
|
+
func, globalns=globals(), localns=locals()
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Prepare parameters schema
|
|
239
|
+
properties = {}
|
|
240
|
+
required = []
|
|
241
|
+
|
|
242
|
+
for param_name, param in signature.parameters.items():
|
|
243
|
+
# Skip 'self' parameter for methods
|
|
244
|
+
if param_name == "self":
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
# Determine parameter type from type hints
|
|
248
|
+
param_type = type_hints.get(param_name)
|
|
249
|
+
|
|
250
|
+
# If parameter has no type hint, default to string
|
|
251
|
+
json_type: JSON_OBJECT_TYPE = "string"
|
|
252
|
+
if param_type:
|
|
253
|
+
# Map Python types to JSON schema types
|
|
254
|
+
if hasattr(param_type, "__origin__"):
|
|
255
|
+
origin = get_origin(param_type)
|
|
256
|
+
if origin is not None:
|
|
257
|
+
if issubclass(origin, list):
|
|
258
|
+
json_type = "array"
|
|
259
|
+
elif issubclass(origin, dict):
|
|
260
|
+
json_type = "object"
|
|
261
|
+
elif origin is typing.Union:
|
|
262
|
+
args = get_args(param_type)
|
|
263
|
+
if type(None) in args:
|
|
264
|
+
# Handle Optional types - don't add to required
|
|
265
|
+
pass
|
|
266
|
+
else:
|
|
267
|
+
# For Union types, use the first non-None type if available
|
|
268
|
+
non_none_types = [
|
|
269
|
+
arg for arg in args if arg is not type(None)
|
|
270
|
+
]
|
|
271
|
+
if non_none_types:
|
|
272
|
+
param_type = non_none_types[0]
|
|
273
|
+
json_type = _python_type_to_json_type(param_type)
|
|
274
|
+
else:
|
|
275
|
+
json_type = _python_type_to_json_type(param_type)
|
|
276
|
+
else:
|
|
277
|
+
json_type = _python_type_to_json_type(param_type)
|
|
278
|
+
|
|
279
|
+
# Get parameter description from parsed docstring if available
|
|
280
|
+
param_desc = param_descriptions.get(param_name, f"Parameter {param_name}")
|
|
281
|
+
|
|
282
|
+
# Check if parameter is required (no default value)
|
|
283
|
+
is_required = param.default == inspect.Parameter.empty
|
|
284
|
+
if is_required:
|
|
285
|
+
required.append(param_name)
|
|
286
|
+
property_schema = FunctionPropertySchema(type=json_type, description=param_desc)
|
|
287
|
+
|
|
288
|
+
properties[param_name] = property_schema
|
|
289
|
+
|
|
290
|
+
parameters_schema = FunctionParametersSchema(
|
|
291
|
+
type="object", properties=properties if properties else None, required=required
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
function_def = FunctionDefinitionSchema(
|
|
295
|
+
name=func.__name__, description=func_desc, parameters=parameters_schema
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Create a wrapper function that accepts a dictionary of parameters
|
|
299
|
+
@on_tools(function_def, strict=True)
|
|
300
|
+
@wraps(func)
|
|
301
|
+
async def tool_wrapper(params: dict[str, Any]) -> str:
|
|
302
|
+
bound_args: inspect.BoundArguments = signature.bind(**params)
|
|
303
|
+
bound_args.apply_defaults()
|
|
304
|
+
|
|
305
|
+
result = (
|
|
306
|
+
await func(**bound_args.arguments)
|
|
307
|
+
if iscoroutinefunction(func)
|
|
308
|
+
else func(**bound_args.arguments)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Convert result to string as expected by the schema
|
|
312
|
+
return str(result)
|
|
313
|
+
|
|
314
|
+
return tool_wrapper
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _python_type_to_json_type(python_type: type[Any]) -> JSON_OBJECT_TYPE:
|
|
318
|
+
"""Convert Python type to JSON schema type."""
|
|
319
|
+
if python_type is str:
|
|
320
|
+
return "string"
|
|
321
|
+
elif python_type is int:
|
|
322
|
+
return "integer"
|
|
323
|
+
elif python_type is float:
|
|
324
|
+
return "number"
|
|
325
|
+
elif python_type is bool:
|
|
326
|
+
return "boolean"
|
|
327
|
+
elif python_type is list:
|
|
328
|
+
return "array"
|
|
329
|
+
elif python_type is dict:
|
|
330
|
+
return "object"
|
|
331
|
+
else:
|
|
332
|
+
# Default to string for unrecognized types
|
|
333
|
+
return "string"
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def on_tools(
|
|
337
|
+
data: FunctionDefinitionSchema,
|
|
338
|
+
custom_run: bool = False,
|
|
339
|
+
strict: bool = False,
|
|
340
|
+
enable_if: Callable[[], bool] = lambda: True,
|
|
341
|
+
) -> Callable[
|
|
342
|
+
...,
|
|
343
|
+
Callable[[dict[str, Any]], Awaitable[str]]
|
|
344
|
+
| Callable[[ToolContext], Awaitable[str | None]],
|
|
345
|
+
]:
|
|
346
|
+
"""Tool registration decorator
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
data (FunctionDefinitionSchema): Function metadata
|
|
350
|
+
custom_run (bool, optional): Whether to enable custom run mode. Defaults to False.
|
|
351
|
+
strict (bool, optional): Whether to enable strict mode. Defaults to False.
|
|
352
|
+
show_call (bool, optional): Whether to show tool call. Defaults to True.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def decorator(
|
|
356
|
+
func: Callable[[dict[str, Any]], Awaitable[str]]
|
|
357
|
+
| Callable[[ToolContext], Awaitable[str | None]],
|
|
358
|
+
):
|
|
359
|
+
tool_data = ToolData(
|
|
360
|
+
func=func,
|
|
361
|
+
data=ToolFunctionSchema(function=data, type="function", strict=strict),
|
|
362
|
+
custom_run=custom_run,
|
|
363
|
+
enable_if=enable_if,
|
|
364
|
+
)
|
|
365
|
+
ToolsManager().register_tool(tool_data)
|
|
366
|
+
return func
|
|
367
|
+
|
|
368
|
+
return decorator
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amrita_core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Agent core of Project Amrita
|
|
5
5
|
Project-URL: Homepage, https://github.com/AmritaBot/AmritaCore
|
|
6
6
|
Project-URL: Source, https://github.com/AmritaBot/AmritaCore
|
|
@@ -60,6 +60,8 @@ This repository contains documentation organized as follows:
|
|
|
60
60
|
|
|
61
61
|
Documentation is currently under construction. For quick start, please refer to the examples in the `demo/` folder.
|
|
62
62
|
|
|
63
|
+
Please view [Docs](https://amrita-core.suggar.top) for more information.
|
|
64
|
+
|
|
63
65
|
## 🛠️ Quick Start
|
|
64
66
|
|
|
65
67
|
To quickly start using AmritaCore, check out the examples in the [demo](./demo/) directory. The basic example demonstrates how to initialize the core, configure settings, and run a simple chat session with the AI assistant.
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import typing
|
|
2
|
-
from collections.abc import Awaitable, Callable
|
|
3
|
-
from typing import Any, ClassVar, overload
|
|
4
|
-
|
|
5
|
-
from typing_extensions import Self
|
|
6
|
-
|
|
7
|
-
from .models import FunctionDefinitionSchema, ToolContext, ToolData, ToolFunctionSchema
|
|
8
|
-
|
|
9
|
-
T = typing.TypeVar("T")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ToolsManager:
|
|
13
|
-
_instance = None
|
|
14
|
-
_models: ClassVar[dict[str, ToolData]] = {}
|
|
15
|
-
_disabled_tools: ClassVar[set[str]] = (
|
|
16
|
-
set()
|
|
17
|
-
) # Disabled tools, has_tool and get_tool will not return disabled tools
|
|
18
|
-
|
|
19
|
-
def __new__(cls) -> Self:
|
|
20
|
-
if cls._instance is None:
|
|
21
|
-
cls._instance = super().__new__(cls)
|
|
22
|
-
return cls._instance
|
|
23
|
-
|
|
24
|
-
def has_tool(self, name: str) -> bool:
|
|
25
|
-
return False if name in self._disabled_tools else name in self._models
|
|
26
|
-
|
|
27
|
-
@overload
|
|
28
|
-
def get_tool(self, name: str) -> ToolData | None: ...
|
|
29
|
-
@overload
|
|
30
|
-
def get_tool(self, name: str, default: T) -> ToolData | T: ...
|
|
31
|
-
def get_tool(self, name: str, default: T = None) -> ToolData | T | None:
|
|
32
|
-
if not self.has_tool(name):
|
|
33
|
-
return default
|
|
34
|
-
tool: ToolData = self._models[name]
|
|
35
|
-
return tool if tool.enable_if() else default
|
|
36
|
-
|
|
37
|
-
@overload
|
|
38
|
-
def get_tool_meta(self, name: str) -> ToolFunctionSchema | None: ...
|
|
39
|
-
@overload
|
|
40
|
-
def get_tool_meta(self, name: str, default: T) -> ToolFunctionSchema | T: ...
|
|
41
|
-
def get_tool_meta(
|
|
42
|
-
self, name: str, default: T | None = None
|
|
43
|
-
) -> ToolFunctionSchema | None | T:
|
|
44
|
-
func_data = self.get_tool(name)
|
|
45
|
-
if func_data is None:
|
|
46
|
-
return default
|
|
47
|
-
if isinstance(func_data, ToolData):
|
|
48
|
-
return func_data.data
|
|
49
|
-
return default
|
|
50
|
-
|
|
51
|
-
@overload
|
|
52
|
-
def get_tool_func(
|
|
53
|
-
self, name: str, default: T
|
|
54
|
-
) -> (
|
|
55
|
-
Callable[[dict[str, Any]], Awaitable[str]]
|
|
56
|
-
| Callable[[ToolContext], Awaitable[str | None]]
|
|
57
|
-
| T
|
|
58
|
-
): ...
|
|
59
|
-
@overload
|
|
60
|
-
def get_tool_func(
|
|
61
|
-
self,
|
|
62
|
-
name: str,
|
|
63
|
-
) -> (
|
|
64
|
-
Callable[[dict[str, Any]], Awaitable[str]]
|
|
65
|
-
| Callable[[ToolContext], Awaitable[str | None]]
|
|
66
|
-
| None
|
|
67
|
-
): ...
|
|
68
|
-
def get_tool_func(
|
|
69
|
-
self, name: str, default: T | None = None
|
|
70
|
-
) -> (
|
|
71
|
-
Callable[[dict[str, Any]], Awaitable[str]]
|
|
72
|
-
| Callable[[ToolContext], Awaitable[str | None]]
|
|
73
|
-
| None
|
|
74
|
-
| T
|
|
75
|
-
):
|
|
76
|
-
func_data = self.get_tool(name)
|
|
77
|
-
if func_data is None:
|
|
78
|
-
return default
|
|
79
|
-
if isinstance(func_data, ToolData):
|
|
80
|
-
return func_data.func
|
|
81
|
-
return default
|
|
82
|
-
|
|
83
|
-
def get_tools(self) -> dict[str, ToolData]:
|
|
84
|
-
return {
|
|
85
|
-
name: data
|
|
86
|
-
for name, data in self._models.items()
|
|
87
|
-
if (name not in self._disabled_tools and data.enable_if())
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
def tools_meta(self) -> dict[str, ToolFunctionSchema]:
|
|
91
|
-
return {
|
|
92
|
-
k: v.data
|
|
93
|
-
for k, v in self._models.items()
|
|
94
|
-
if (k not in self._disabled_tools and v.enable_if())
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
def tools_meta_dict(self, **kwargs) -> dict[str, dict[str, Any]]:
|
|
98
|
-
return {
|
|
99
|
-
k: v.data.model_dump(**kwargs)
|
|
100
|
-
for k, v in self._models.items()
|
|
101
|
-
if (k not in self._disabled_tools and v.enable_if())
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
def register_tool(self, tool: ToolData) -> None:
|
|
105
|
-
if tool.data.function.name not in self._models:
|
|
106
|
-
self._models[tool.data.function.name] = tool
|
|
107
|
-
else:
|
|
108
|
-
raise ValueError(f"Tool {tool.data.function.name} already exists")
|
|
109
|
-
|
|
110
|
-
def remove_tool(self, name: str) -> None:
|
|
111
|
-
self._models.pop(name, None)
|
|
112
|
-
if name in self._disabled_tools:
|
|
113
|
-
self._disabled_tools.remove(name)
|
|
114
|
-
|
|
115
|
-
def enable_tool(self, name: str) -> None:
|
|
116
|
-
if name in self._disabled_tools:
|
|
117
|
-
self._disabled_tools.remove(name)
|
|
118
|
-
else:
|
|
119
|
-
raise ValueError(f"Tool {name} is not disabled")
|
|
120
|
-
|
|
121
|
-
def disable_tool(self, name: str) -> None:
|
|
122
|
-
if self.has_tool(name):
|
|
123
|
-
self._disabled_tools.add(name)
|
|
124
|
-
else:
|
|
125
|
-
raise ValueError(f"Tool {name} does not exist or has been disabled")
|
|
126
|
-
|
|
127
|
-
def get_disabled_tools(self) -> list[str]:
|
|
128
|
-
return list(self._disabled_tools)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def on_tools(
|
|
132
|
-
data: FunctionDefinitionSchema,
|
|
133
|
-
custom_run: bool = False,
|
|
134
|
-
strict: bool = False,
|
|
135
|
-
enable_if: Callable[[], bool] = lambda: True,
|
|
136
|
-
) -> Callable[
|
|
137
|
-
...,
|
|
138
|
-
Callable[[dict[str, Any]], Awaitable[str]]
|
|
139
|
-
| Callable[[ToolContext], Awaitable[str | None]],
|
|
140
|
-
]:
|
|
141
|
-
"""Tool registration decorator
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
data (FunctionDefinitionSchema): Function metadata
|
|
145
|
-
custom_run (bool, optional): Whether to enable custom run mode. Defaults to False.
|
|
146
|
-
strict (bool, optional): Whether to enable strict mode. Defaults to False.
|
|
147
|
-
show_call (bool, optional): Whether to show tool call. Defaults to True.
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
def decorator(
|
|
151
|
-
func: Callable[[dict[str, Any]], Awaitable[str]]
|
|
152
|
-
| Callable[[ToolContext], Awaitable[str | None]],
|
|
153
|
-
):
|
|
154
|
-
tool_data = ToolData(
|
|
155
|
-
func=func,
|
|
156
|
-
data=ToolFunctionSchema(function=data, type="function", strict=strict),
|
|
157
|
-
custom_run=custom_run,
|
|
158
|
-
enable_if=enable_if,
|
|
159
|
-
)
|
|
160
|
-
ToolsManager().register_tool(tool_data)
|
|
161
|
-
return func
|
|
162
|
-
|
|
163
|
-
return decorator
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|