Skip to content

Commit f4f2663

Browse files
committed
fix: address review comments
Signed-off-by: kyteinsky <kyteinsky@gmail.com>
1 parent 1d997dc commit f4f2663

2 files changed

Lines changed: 23 additions & 13 deletions

File tree

ex_app/lib/all_tools/lib/task_processing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class Task(BaseModel):
1919
class Response(BaseModel):
2020
task: Task
2121

22+
23+
ACCEPTED_OUTPUT_KEYS = ["file", "output", "images", "slide_deck", "sources"]
24+
25+
2226
async def run_task(nc: AsyncNextcloudApp, type, task_input):
2327
i = 0
2428
while i < 20:
@@ -75,7 +79,7 @@ async def run_task(nc: AsyncNextcloudApp, type, task_input):
7579
if task.status != "STATUS_SUCCESSFUL":
7680
raise Exception("Nextcloud TaskProcessing Task failed")
7781

78-
if not isinstance(task.output, dict) or all(x not in ["file", "output", "images", "slide_deck", "sources"] for x in task.output):
79-
raise Exception('"output" key not found in Nextcloud TaskProcessing task result')
82+
if not isinstance(task.output, dict) or all(x not in ACCEPTED_OUTPUT_KEYS for x in task.output):
83+
raise Exception(f'Expected one of {ACCEPTED_OUTPUT_KEYS} in Nextcloud TaskProcessing task result')
8084

8185
return task

ex_app/lib/all_tools/memory.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ def parse_sources(cls, v: list) -> list:
7070
return result
7171

7272

73-
async def __is_context_chat_available(nc: AsyncNextcloudApp, memories_folder_path: str):
73+
async def __is_context_chat_available(nc: AsyncNextcloudApp):
7474
tasktypes = (await nc.ocs('GET', '/ocs/v2.php/taskprocessing/tasktypes'))['types'].keys()
7575
return CONTEXT_CHAT_SEARCH_TASK_TYPE in tasktypes
7676

7777

78-
def __validate_memory_path(path: str, memories_folder_path: str, *, allow_folder_path: bool = False) -> tuple[str, str]:
78+
def __validate_memory_path(path: str, memories_folder_path: str, *, only_allow_folder_path: bool = False) -> tuple[str, str]:
7979
"""returns tuple[full_path, memories_scoped_path]"""
8080
if not path:
8181
raise AgentFacingError('Memory path cannot be empty')
@@ -90,11 +90,15 @@ def __validate_memory_path(path: str, memories_folder_path: str, *, allow_folder
9090
if '/..' in candidate or '../' in candidate or '..\\' in candidate:
9191
raise RuntimeError('Agent tried to access directories beyond the memories folder')
9292

93-
if not allow_folder_path and (not decoded.endswith('.md') and not decoded.endswith('.markdown')):
93+
if not only_allow_folder_path and (not decoded.endswith('.md') and not decoded.endswith('.markdown')):
9494
raise AgentFacingError('Memory file should be a markdown file')
9595

96+
if only_allow_folder_path and (decoded.endswith('.md') or decoded.endswith('.markdown')):
97+
raise AgentFacingError('Memory path should point to a folder, not a memory file.')
98+
9699
path_parts = [p for p in decoded.strip('/').split('/') if p]
97-
if len(path_parts) > MAX_MEMORY_FOLDER_DEPTH + 1: # +1 for the filename itself
100+
max_parts = MAX_MEMORY_FOLDER_DEPTH if only_allow_folder_path else MAX_MEMORY_FOLDER_DEPTH + 1 # +1 for the filename itself
101+
if len(path_parts) > max_parts:
98102
raise AgentFacingError(f'Memory path exceeds maximum depth of {MAX_MEMORY_FOLDER_DEPTH}')
99103

100104
# resolve the full absolute path and verify it stays within the memory folder
@@ -143,7 +147,7 @@ async def __create_folders_if_not_exists(nc: AsyncNextcloudApp, adapter: niquest
143147
)
144148
if propfind.status_code == 404:
145149
base_parts = [p for p in base_memories_path.split('/') if p]
146-
all_parts = base_parts + scoped_folder_parts
150+
all_parts = base_parts + [quote(p, safe='') for p in scoped_folder_parts]
147151
for i in range(len(base_parts) + 1, len(all_parts) + 1):
148152
folder_path = '/'.join(all_parts[:i])
149153
r = await adapter.request('MKCOL', f"{nc.app_cfg.endpoint}/remote.php/dav/files/{user_id}/{folder_path}")
@@ -314,7 +318,7 @@ async def delete_memory_folder(path: str):
314318
:return: Status of the deletion operation.
315319
"""
316320
try:
317-
full_path, _ = __validate_memory_path(path, memories_folder_path, allow_folder_path=True)
321+
full_path, _ = __validate_memory_path(path, memories_folder_path, only_allow_folder_path=True)
318322
except AgentFacingError as e:
319323
return {'error': str(e)}
320324
except Exception as e:
@@ -344,6 +348,10 @@ async def search_memories(query: str, k: int = 5) -> list[dict[str, str]]:
344348
files_handle = AsyncFilesAPI(nc._session)
345349
memories_folder_fsnode = await files_handle.by_path(memories_folder_path)
346350

351+
if memories_folder_fsnode is None:
352+
await log(nc, logging.WARNING, f'Could not find the memories folder: {memories_folder_path}')
353+
raise RuntimeError('Could not find the memories folder, try creating a memory first.')
354+
347355
task_input = {
348356
'prompt': query,
349357
'scopeType': 'source',
@@ -367,14 +375,12 @@ async def fetch(file_id: int) -> dict[str, str]:
367375
:return: {'path': string, 'content: string}
368376
"""
369377
fsnode = await files_handle.by_id(file_id)
370-
filepath = '/' + fsnode.user_path.removeprefix(prefix).rstrip('/')
371-
372378
if fsnode is None:
373379
await log(nc, logging.WARNING, f'Could not fetch file by id: {file_id}')
374-
return {'path': filepath, 'content': ''}
380+
return {'path': '', 'content': ''}
375381

376382
return {
377-
'path': filepath,
383+
'path': '/' + fsnode.user_path.removeprefix(prefix).rstrip('/'),
378384
'content': (await files_handle.download(fsnode)).decode(errors='replace'),
379385
}
380386

@@ -387,7 +393,7 @@ async def fetch(file_id: int) -> dict[str, str]:
387393
store_memory,
388394
delete_memory,
389395
delete_memory_folder,
390-
*([search_memories] if await __is_context_chat_available(nc, memories_folder_path) else []),
396+
*([search_memories] if await __is_context_chat_available(nc) else []),
391397
] if assistant_folder_path else []),
392398
]
393399

0 commit comments

Comments
 (0)