camel-ai 0.2.71a2__py3-none-any.whl → 0.2.71a4__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.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/_types.py +6 -2
- camel/agents/chat_agent.py +297 -16
- camel/interpreters/docker_interpreter.py +3 -2
- camel/loaders/base_loader.py +85 -0
- camel/messages/base.py +2 -6
- camel/services/agent_openapi_server.py +380 -0
- camel/societies/workforce/workforce.py +144 -33
- camel/toolkits/__init__.py +7 -4
- camel/toolkits/craw4ai_toolkit.py +2 -2
- camel/toolkits/file_write_toolkit.py +6 -6
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/__init__.py +2 -2
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/actions.py +47 -11
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/agent.py +21 -11
- camel/toolkits/{non_visual_browser_toolkit/nv_browser_session.py → hybrid_browser_toolkit/browser_session.py} +64 -10
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +1008 -0
- camel/toolkits/{non_visual_browser_toolkit → hybrid_browser_toolkit}/snapshot.py +16 -4
- camel/toolkits/{non_visual_browser_toolkit/snapshot.js → hybrid_browser_toolkit/unified_analyzer.js} +202 -23
- camel/toolkits/note_taking_toolkit.py +90 -0
- camel/toolkits/openai_image_toolkit.py +292 -0
- camel/toolkits/slack_toolkit.py +4 -4
- camel/toolkits/terminal_toolkit.py +223 -73
- camel/types/agents/tool_calling_record.py +4 -1
- camel/types/enums.py +24 -24
- camel/utils/mcp_client.py +37 -1
- camel/utils/tool_result.py +44 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/METADATA +58 -5
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/RECORD +30 -26
- camel/toolkits/dalle_toolkit.py +0 -175
- camel/toolkits/non_visual_browser_toolkit/browser_non_visual_toolkit.py +0 -446
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a2.dist-info → camel_ai-0.2.71a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from playwright.async_api import Page
|
|
@@ -64,7 +64,17 @@ class PageSnapshot:
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
logger.debug("Capturing page snapshot …")
|
|
67
|
-
|
|
67
|
+
snapshot_result = await self._get_snapshot_direct()
|
|
68
|
+
|
|
69
|
+
# Extract snapshot text from the unified analyzer result
|
|
70
|
+
if (
|
|
71
|
+
isinstance(snapshot_result, dict)
|
|
72
|
+
and 'snapshotText' in snapshot_result
|
|
73
|
+
):
|
|
74
|
+
snapshot_text = snapshot_result['snapshotText']
|
|
75
|
+
else:
|
|
76
|
+
snapshot_text = snapshot_result
|
|
77
|
+
|
|
68
78
|
formatted = self._format_snapshot(snapshot_text or "<empty>")
|
|
69
79
|
|
|
70
80
|
output = formatted
|
|
@@ -99,7 +109,9 @@ class PageSnapshot:
|
|
|
99
109
|
# ------------------------------------------------------------------
|
|
100
110
|
_snapshot_js_cache: Optional[str] = None # class-level cache
|
|
101
111
|
|
|
102
|
-
async def _get_snapshot_direct(
|
|
112
|
+
async def _get_snapshot_direct(
|
|
113
|
+
self,
|
|
114
|
+
) -> Optional[Union[str, Dict[str, Any]]]:
|
|
103
115
|
r"""Evaluate the snapshot-extraction JS with simple retry logic.
|
|
104
116
|
|
|
105
117
|
Playwright throws *Execution context was destroyed* when a new page
|
|
@@ -110,7 +122,7 @@ class PageSnapshot:
|
|
|
110
122
|
|
|
111
123
|
# Load JS once and cache it at class level
|
|
112
124
|
if PageSnapshot._snapshot_js_cache is None:
|
|
113
|
-
js_path = Path(__file__).parent / "
|
|
125
|
+
js_path = Path(__file__).parent / "unified_analyzer.js"
|
|
114
126
|
PageSnapshot._snapshot_js_cache = js_path.read_text(
|
|
115
127
|
encoding="utf-8"
|
|
116
128
|
)
|
camel/toolkits/{non_visual_browser_toolkit/snapshot.js → hybrid_browser_toolkit/unified_analyzer.js}
RENAMED
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Unified analyzer that combines visual and structural analysis
|
|
3
|
+
// Preserves complete snapshot.js logic while adding visual coordinate information
|
|
4
|
+
|
|
5
|
+
let refCounter = 1;
|
|
6
|
+
function generateRef() {
|
|
7
|
+
return `e${refCounter++}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// === Complete snapshot.js logic preservation ===
|
|
4
11
|
|
|
5
12
|
function isVisible(node) {
|
|
13
|
+
// Check if node is null or not a valid DOM node
|
|
14
|
+
if (!node || typeof node.nodeType === 'undefined') return false;
|
|
6
15
|
if (node.nodeType !== Node.ELEMENT_NODE) return true;
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const style = window.getComputedStyle(node);
|
|
19
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')
|
|
20
|
+
return false;
|
|
21
|
+
// An element with `display: contents` is not rendered itself, but its children are.
|
|
22
|
+
if (style.display === 'contents')
|
|
23
|
+
return true;
|
|
24
|
+
const rect = node.getBoundingClientRect();
|
|
25
|
+
return rect.width > 0 && rect.height > 0;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// If there's an error getting computed style or bounding rect, assume element is not visible
|
|
9
28
|
return false;
|
|
10
|
-
|
|
11
|
-
if (style.display === 'contents')
|
|
12
|
-
return true;
|
|
13
|
-
const rect = node.getBoundingClientRect();
|
|
14
|
-
return rect.width > 0 && rect.height > 0;
|
|
29
|
+
}
|
|
15
30
|
}
|
|
16
31
|
|
|
17
32
|
function getRole(node) {
|
|
33
|
+
// Check if node is null or doesn't have required properties
|
|
34
|
+
if (!node || !node.tagName || !node.getAttribute) {
|
|
35
|
+
return 'generic';
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
const role = node.getAttribute('role');
|
|
19
39
|
if (role) return role;
|
|
20
40
|
|
|
@@ -32,6 +52,9 @@
|
|
|
32
52
|
}
|
|
33
53
|
|
|
34
54
|
function getAccessibleName(node) {
|
|
55
|
+
// Check if node is null or doesn't have required methods
|
|
56
|
+
if (!node || !node.hasAttribute || !node.getAttribute) return '';
|
|
57
|
+
|
|
35
58
|
if (node.hasAttribute('aria-label')) return node.getAttribute('aria-label') || '';
|
|
36
59
|
if (node.hasAttribute('aria-labelledby')) {
|
|
37
60
|
const id = node.getAttribute('aria-labelledby');
|
|
@@ -48,6 +71,9 @@
|
|
|
48
71
|
|
|
49
72
|
const textCache = new Map();
|
|
50
73
|
function getVisibleTextContent(_node) {
|
|
74
|
+
// Check if node is null or doesn't have nodeType
|
|
75
|
+
if (!_node || typeof _node.nodeType === 'undefined') return '';
|
|
76
|
+
|
|
51
77
|
if (textCache.has(_node)) return textCache.get(_node);
|
|
52
78
|
|
|
53
79
|
if (_node.nodeType === Node.TEXT_NODE) {
|
|
@@ -70,18 +96,17 @@
|
|
|
70
96
|
return result;
|
|
71
97
|
}
|
|
72
98
|
|
|
73
|
-
let refCounter = 1;
|
|
74
|
-
function generateRef() {
|
|
75
|
-
return `e${refCounter++}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
99
|
/**
|
|
79
100
|
* Phase 1: Build an in-memory representation of the accessibility tree.
|
|
101
|
+
* Complete preservation of snapshot.js buildAriaTree logic
|
|
80
102
|
*/
|
|
81
103
|
function buildAriaTree(rootElement) {
|
|
82
104
|
const visited = new Set();
|
|
83
105
|
|
|
84
106
|
function toAriaNode(element) {
|
|
107
|
+
// Check if element is null or not a valid DOM element
|
|
108
|
+
if (!element || !element.tagName) return null;
|
|
109
|
+
|
|
85
110
|
// Only consider visible elements
|
|
86
111
|
if (!isVisible(element)) return null;
|
|
87
112
|
|
|
@@ -112,7 +137,8 @@
|
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
function traverse(element, parentNode) {
|
|
115
|
-
if
|
|
140
|
+
// Check if element is null or not a valid DOM element
|
|
141
|
+
if (!element || !element.tagName || visited.has(element)) return;
|
|
116
142
|
visited.add(element);
|
|
117
143
|
|
|
118
144
|
// FIX: Completely skip script and style tags and their children.
|
|
@@ -153,9 +179,34 @@
|
|
|
153
179
|
}
|
|
154
180
|
}
|
|
155
181
|
|
|
156
|
-
// FIX:
|
|
157
|
-
if (ariaNode && ariaNode.children.length
|
|
158
|
-
|
|
182
|
+
// FIX: Remove redundant text children that match the element's name
|
|
183
|
+
if (ariaNode && ariaNode.children.length > 0) {
|
|
184
|
+
// Remove text children that are the same as the parent's name or are contained in it
|
|
185
|
+
ariaNode.children = ariaNode.children.filter(child => {
|
|
186
|
+
if (typeof child === 'string') {
|
|
187
|
+
const childText = child.trim();
|
|
188
|
+
const parentName = ariaNode.name.trim();
|
|
189
|
+
|
|
190
|
+
// Remove if text child exactly matches parent name
|
|
191
|
+
if (childText === parentName) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Also remove if the child text is completely contained in parent name
|
|
196
|
+
// and represents a significant portion (to avoid removing important partial text)
|
|
197
|
+
if (childText.length > 3 && parentName.includes(childText)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// If after filtering, we have only one text child that equals the name, remove it
|
|
207
|
+
if (ariaNode.children.length === 1 && typeof ariaNode.children[0] === 'string' && ariaNode.name === ariaNode.children[0]) {
|
|
208
|
+
ariaNode.children = [];
|
|
209
|
+
}
|
|
159
210
|
}
|
|
160
211
|
}
|
|
161
212
|
|
|
@@ -166,7 +217,7 @@
|
|
|
166
217
|
|
|
167
218
|
/**
|
|
168
219
|
* Phase 2: Normalize the tree by removing redundant generic wrappers.
|
|
169
|
-
*
|
|
220
|
+
* Complete preservation of snapshot.js normalizeTree logic
|
|
170
221
|
*/
|
|
171
222
|
function normalizeTree(node) {
|
|
172
223
|
if (typeof node === 'string') return [node];
|
|
@@ -178,6 +229,24 @@
|
|
|
178
229
|
node.children = newChildren;
|
|
179
230
|
|
|
180
231
|
// Remove child elements that have the same name as their parent
|
|
232
|
+
const filteredChildren = [];
|
|
233
|
+
for (const child of node.children) {
|
|
234
|
+
if (typeof child !== 'string' && child.name && node.name) {
|
|
235
|
+
const childName = child.name.trim();
|
|
236
|
+
const parentName = node.name.trim();
|
|
237
|
+
if (childName === parentName) {
|
|
238
|
+
// If child has same name as parent, merge its children into parent
|
|
239
|
+
filteredChildren.push(...(child.children || []));
|
|
240
|
+
} else {
|
|
241
|
+
filteredChildren.push(child);
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
filteredChildren.push(child);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
node.children = filteredChildren;
|
|
248
|
+
|
|
249
|
+
// Also handle the case where we have only one child with same name
|
|
181
250
|
if (node.children.length === 1 && typeof node.children[0] !== 'string') {
|
|
182
251
|
const child = node.children[0];
|
|
183
252
|
if (child.name && node.name && child.name.trim() === node.name.trim()) {
|
|
@@ -195,9 +264,9 @@
|
|
|
195
264
|
return [node];
|
|
196
265
|
}
|
|
197
266
|
|
|
198
|
-
|
|
199
267
|
/**
|
|
200
268
|
* Phase 3: Render the normalized tree into the final string format.
|
|
269
|
+
* Complete preservation of snapshot.js renderTree logic
|
|
201
270
|
*/
|
|
202
271
|
function renderTree(node, indent = '') {
|
|
203
272
|
const lines = [];
|
|
@@ -263,6 +332,116 @@
|
|
|
263
332
|
return lines;
|
|
264
333
|
}
|
|
265
334
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
335
|
+
// === Visual analysis functions from page_script.js ===
|
|
336
|
+
|
|
337
|
+
// From page_script.js - check if element is topmost at coordinates
|
|
338
|
+
function isTopmost(element, x, y) {
|
|
339
|
+
let hit = document.elementFromPoint(x, y);
|
|
340
|
+
if (hit === null) return true;
|
|
341
|
+
|
|
342
|
+
while (hit) {
|
|
343
|
+
if (hit == element) return true;
|
|
344
|
+
hit = hit.parentNode;
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// From page_script.js - get visual coordinates
|
|
350
|
+
function getElementCoordinates(element) {
|
|
351
|
+
let rects = element.getClientRects();
|
|
352
|
+
let scale = window.devicePixelRatio || 1;
|
|
353
|
+
let validRects = [];
|
|
354
|
+
|
|
355
|
+
for (const rect of rects) {
|
|
356
|
+
let x = rect.left + rect.width / 2;
|
|
357
|
+
let y = rect.top + rect.height / 2;
|
|
358
|
+
if (isTopmost(element, x, y)) {
|
|
359
|
+
validRects.push({
|
|
360
|
+
x: rect.x * scale,
|
|
361
|
+
y: rect.y * scale,
|
|
362
|
+
width: rect.width * scale,
|
|
363
|
+
height: rect.height * scale,
|
|
364
|
+
top: rect.top * scale,
|
|
365
|
+
left: rect.left * scale,
|
|
366
|
+
right: rect.right * scale,
|
|
367
|
+
bottom: rect.bottom * scale
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return validRects;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// === Unified analysis function ===
|
|
376
|
+
|
|
377
|
+
function collectElementsFromTree(node, elementsMap) {
|
|
378
|
+
if (typeof node === 'string') return;
|
|
379
|
+
|
|
380
|
+
if (node.element && node.ref) {
|
|
381
|
+
// Get visual coordinates for this element
|
|
382
|
+
const coordinates = getElementCoordinates(node.element);
|
|
383
|
+
|
|
384
|
+
// Store comprehensive element information
|
|
385
|
+
elementsMap[node.ref] = {
|
|
386
|
+
// Structural information (preserved from snapshot.js)
|
|
387
|
+
role: node.role,
|
|
388
|
+
name: node.name,
|
|
389
|
+
tagName: node.element.tagName.toLowerCase(),
|
|
390
|
+
disabled: node.disabled,
|
|
391
|
+
checked: node.checked,
|
|
392
|
+
expanded: node.expanded,
|
|
393
|
+
|
|
394
|
+
// Visual information (from page_script.js)
|
|
395
|
+
coordinates: coordinates,
|
|
396
|
+
|
|
397
|
+
// Additional metadata
|
|
398
|
+
href: node.element.href || null,
|
|
399
|
+
value: node.element.value || null,
|
|
400
|
+
placeholder: node.element.placeholder || null,
|
|
401
|
+
scrollable: node.element.scrollHeight > node.element.clientHeight
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Recursively process children
|
|
406
|
+
if (node.children) {
|
|
407
|
+
for (const child of node.children) {
|
|
408
|
+
collectElementsFromTree(child, elementsMap);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function analyzePageElements() {
|
|
414
|
+
// Generate the complete structured snapshot using original snapshot.js logic
|
|
415
|
+
const outputLines = processDocument(document);
|
|
416
|
+
const snapshotText = outputLines.join('\n');
|
|
417
|
+
|
|
418
|
+
// Build the tree again to collect element information with visual data
|
|
419
|
+
textCache.clear();
|
|
420
|
+
refCounter = 1; // Reset counter to match snapshot generation
|
|
421
|
+
let tree = buildAriaTree(document.body);
|
|
422
|
+
[tree] = normalizeTree(tree);
|
|
423
|
+
|
|
424
|
+
const elementsMap = {};
|
|
425
|
+
collectElementsFromTree(tree, elementsMap);
|
|
426
|
+
|
|
427
|
+
const result = {
|
|
428
|
+
url: window.location.href,
|
|
429
|
+
elements: elementsMap,
|
|
430
|
+
snapshotText: snapshotText,
|
|
431
|
+
metadata: {
|
|
432
|
+
timestamp: new Date().toISOString(),
|
|
433
|
+
elementCount: Object.keys(elementsMap).length,
|
|
434
|
+
screenInfo: {
|
|
435
|
+
width: window.innerWidth,
|
|
436
|
+
height: window.innerHeight,
|
|
437
|
+
devicePixelRatio: window.devicePixelRatio || 1
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Execute analysis and return result
|
|
446
|
+
return analyzePageElements();
|
|
447
|
+
})();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import List, Optional
|
|
16
|
+
|
|
17
|
+
from camel.toolkits.base import BaseToolkit
|
|
18
|
+
from camel.toolkits.function_tool import FunctionTool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoteTakingToolkit(BaseToolkit):
|
|
22
|
+
r"""A toolkit for taking notes in a Markdown file.
|
|
23
|
+
|
|
24
|
+
This toolkit allows an agent to create, append to, and update a specific
|
|
25
|
+
Markdown file for note-taking purposes.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
note_file_path: str = "notes/notes.md",
|
|
31
|
+
timeout: Optional[float] = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
r"""Initialize the NoteTakingToolkit.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
note_file_path (str): The path to the note file.
|
|
37
|
+
(default: :obj:`notes/notes.md`)
|
|
38
|
+
timeout (Optional[float]): The timeout for the toolkit.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(timeout=timeout)
|
|
41
|
+
self.note_file_path = Path(note_file_path)
|
|
42
|
+
self.note_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
def take_note(self, content: str, update: bool = False) -> str:
|
|
45
|
+
r"""Takes a note and saves it to the note file.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
content (str): The content of the note to be saved.
|
|
49
|
+
update (bool): If True, the existing note file will be
|
|
50
|
+
overwritten with the new content. If False, the new content
|
|
51
|
+
will be appended to the end of the file.
|
|
52
|
+
(default: :obj:`False`)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: A message indicating the result of the operation.
|
|
56
|
+
"""
|
|
57
|
+
mode = "w" if update else "a"
|
|
58
|
+
try:
|
|
59
|
+
with self.note_file_path.open(mode, encoding="utf-8") as f:
|
|
60
|
+
f.write(content + "\n")
|
|
61
|
+
action = "updated" if update else "appended to"
|
|
62
|
+
return f"Note successfully {action} in {self.note_file_path}."
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return f"Error taking note: {e}"
|
|
65
|
+
|
|
66
|
+
def read_note(self) -> str:
|
|
67
|
+
r"""Reads the content of the note file.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
str: The content of the note file, or an error message if the
|
|
71
|
+
file cannot be read.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
if not self.note_file_path.exists():
|
|
75
|
+
return "Note file does not exist yet."
|
|
76
|
+
return self.note_file_path.read_text(encoding="utf-8")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return f"Error reading note: {e}"
|
|
79
|
+
|
|
80
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
81
|
+
r"""Return a list of FunctionTool objects representing the functions
|
|
82
|
+
in the toolkit.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List[FunctionTool]: A list of FunctionTool objects.
|
|
86
|
+
"""
|
|
87
|
+
return [
|
|
88
|
+
FunctionTool(self.take_note),
|
|
89
|
+
FunctionTool(self.read_note),
|
|
90
|
+
]
|