Skip to content

Commit 460184b

Browse files
Refactor file handling prompts and improve error handling in view tool (#624)
1 parent 629b127 commit 460184b

File tree

8 files changed

+91
-538
lines changed

8 files changed

+91
-538
lines changed

assistants/document-assistant/assistant/chat.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ async def on_message_created(
221221
await archive_task_queues.enqueue_run(
222222
context=context,
223223
attachments=attachments,
224-
archive_task_config=ArchiveTaskConfig(chunk_token_count_threshold=30_000),
224+
archive_task_config=ArchiveTaskConfig(
225+
chunk_token_count_threshold=config.orchestration.prompts.token_window
226+
),
225227
archive_summarizer=construct_archive_summarizer(
226228
service_config=config.generative_ai_fast_client_config.service_config,
227229
request_config=config.generative_ai_fast_client_config.request_config,

assistants/document-assistant/assistant/context_management/file_manager.py

Lines changed: 0 additions & 511 deletions
This file was deleted.

assistants/document-assistant/assistant/filesystem/_prompts.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,25 @@
2020
Files that are read-only are known as "attachments" and are initially appended to user's message at the time they uploaded them. \
2121
Eventually they might fall out of your context window and you will need to use the `view` tool to read them again if you need it. \
2222
A summary of the file content has been provided to you to better understand what the file is about.
23+
There are more files that you can access. First call the `ls` tool to list all files available in the filesystem.
2324
2425
### Recent & Relevant Files
2526
2627
You can read the following files in again using the `view` tool if they are needed. \
27-
If they are editable you can also use the `edit_file` tool to edit them."""
28+
If they are editable you can also use the `edit_file` tool to edit them.
29+
Paths are mounted at different locations depending on the type of file and you must always use the absolute path to the file, starting with `/` for any path.
30+
- Editable files are mounted at `/editable_documents/editable_file.md`.
31+
- User uploaded files or "attachments" are mounted at `/attachments/attachment.pdf`."""
2832

29-
FILESYSTEM_ADDON_PROMPT = """### Filesystem
3033

31-
**Very important:** This current interaction with the user is long-running and due to context window limitations, the above section can only show a limited number of files. \
32-
There are more files that you can access. First call the `ls` tool to list all files available in the filesystem. \
33-
Then, you can use the `view` tool (use it multiple times if needed) to read any of the files that you find relevant to the user's request.\
34-
This is a similar concept to how you would explore a codebase in a code editor."""
34+
ARCHIVES_ADDON_PROMPT = """### Conversation Memories and Archives
3535
36+
You have a limited context window, which means that some of the earlier parts of the conversation may fall out of your context.
37+
To help you with that, below you will find summaries of older parts of the conversation that have been "archived". \
38+
You should use these summaries as "memories" to help you understand the historical context and preferences of the user. \
39+
Note that some of these archived conversation may still be visible to you in the conversation history.
40+
If the current user's task requires you to access the full content of the conversation, you can use the `view` tool to read the archived conversations. \
41+
Historical conversations are mounted at `/archives/conversation_1234567890.json`"""
3642

3743
VIEW_TOOL = {
3844
"type": "function",
@@ -45,7 +51,7 @@
4551
"properties": {
4652
"path": {
4753
"type": "string",
48-
"description": "The relative path to the file.",
54+
"description": "The absolute path to the file. Must start with `/` followed by the mount point, e.g. `/editable_documents/filename.md`.",
4955
},
5056
},
5157
"required": ["path"],
@@ -90,8 +96,9 @@
9096
EDIT_TOOL_DESCRIPTION_HOSTED = """Edits the Markdown file at the provided path, focused on the given task.
9197
The user has Markdown editor available that is side by side with this chat.
9298
Remember that the editable files are the ones that have the `-rw-` permission bits. \
99+
They also must be mounted at `/editable_documents/` and have a `.md` extension. \
93100
If you provide a new file path, it will be created for you and then the editor will start to edit it (from scratch). \
94-
Name the file with capital letters and spacing like "Weekly AI Report.md" or "Email to Boss.md" since it will be directly shown to the user in that way.
101+
Name the file with capital letters and spacing like "/editable_documents/Weekly AI Report.md" or "/editable_documents/Email to Boss.md" since it will be directly shown to the user in that way.
95102
Provide a task that you want it to do in the document. For example, if you want to have it expand on one section, \
96103
you can say "expand on the section about <topic x>". The task should be at most a few sentences. \
97104
Do not provide it any additional context outside of the task parameter. It will automatically be fetched as needed by this tool.

assistants/document-assistant/assistant/response/completion_handler.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from assistant_extensions.mcp import (
1111
ExtendedCallToolRequestParams,
1212
MCPSession,
13-
OpenAISamplingHandler,
1413
handle_mcp_tool_call,
1514
)
1615
from chat_context_toolkit.virtual_filesystem import VirtualFileSystem
@@ -29,7 +28,6 @@
2928
ConversationContext,
3029
)
3130

32-
from assistant.filesystem import AttachmentsExtension
3331
from assistant.guidance.dynamic_ui_inspector import update_dynamic_ui_state
3432
from assistant.guidance.guidance_prompts import DYNAMIC_UI_TOOL_NAME, DYNAMIC_UI_TOOL_RESULT
3533

assistants/document-assistant/assistant/response/responder.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
)
7070
from assistant.filesystem._file_sources import attachments_file_source_mount, editable_documents_file_source_mount
7171
from assistant.filesystem._filesystem import _files_drive_for_context
72-
from assistant.filesystem._prompts import FILES_PROMPT, LS_TOOL_OBJ
72+
from assistant.filesystem._prompts import ARCHIVES_ADDON_PROMPT, FILES_PROMPT, LS_TOOL_OBJ
7373
from assistant.guidance.dynamic_ui_inspector import get_dynamic_ui_state, update_dynamic_ui_state
7474
from assistant.guidance.guidance_prompts import DYNAMIC_UI_TOOL_NAME, DYNAMIC_UI_TOOL_OBJ
7575
from assistant.response.completion_handler import handle_completion
@@ -452,32 +452,43 @@ async def _construct_dynamic_ui_system_prompt(self) -> str:
452452
return system_prompt
453453

454454
async def _construct_filesystem_system_prompt(self) -> str:
455-
"""Constructs the files available to the assistant that are out of context.
455+
"""Constructs the filesystem system prompt with available files.
456+
457+
Builds a system prompt that includes:
458+
1. FILES_PROMPT with attachments and editable_documents (up to 25 files)
459+
2. ARCHIVES_ADDON_PROMPT (if archives exist)
460+
3. Archives files listing (up to 25 files)
461+
462+
Files are sorted by timestamp (newest first), limited to 25 per category,
463+
then sorted alphabetically by path.
456464
457465
This is an example of what gets added after the FILES_PROMPT:
458466
-r-- path2.pdf [File content summary: <summary>]
459467
-rw- path3.txt [File content summary: No summary available yet, use the context available to determine the use of this file]
460468
"""
461-
entries = (
462-
list(await self.virtual_filesystem.list_directory(path="/attachments"))
463-
+ list(await self.virtual_filesystem.list_directory(path="/editable_documents"))
464-
+ list(await self.virtual_filesystem.list_directory(path="/archives"))
465-
)
466-
files = [entry for entry in entries if isinstance(entry, FileEntry)]
469+
# Get all file entries
470+
attachments_entries = list(await self.virtual_filesystem.list_directory(path="/attachments"))
471+
editable_documents_entries = list(await self.virtual_filesystem.list_directory(path="/editable_documents"))
472+
archives_entries = list(await self.virtual_filesystem.list_directory(path="/archives"))
473+
474+
# Separate regular files from archives
475+
regular_files = [entry for entry in (attachments_entries + editable_documents_entries) if isinstance(entry, FileEntry)]
476+
archives_files = [entry for entry in archives_entries if isinstance(entry, FileEntry)]
467477

468478
# TODO: Better ranking algorithm
469-
# order the files by timestamp, newest first
470-
files.sort(key=lambda f: f.timestamp, reverse=True)
471-
# take the top 25 files
472-
files = files[:25]
479+
# order the regular files by timestamp, newest first
480+
regular_files.sort(key=lambda f: f.timestamp, reverse=True)
481+
# take the top 25 regular files
482+
regular_files = regular_files[:25]
473483
# order them alphabetically by path
474-
files.sort(key=lambda f: f.path.lower())
484+
regular_files.sort(key=lambda f: f.path.lower())
475485

486+
# Start with FILES_PROMPT and add attachments/editable_documents
476487
system_prompt = FILES_PROMPT + "\n"
477-
if not files:
488+
if not regular_files:
478489
system_prompt += "\nNo files are currently available."
479490

480-
for file in files:
491+
for file in regular_files:
481492
# Format permissions: -rw- for read_write, -r-- for read
482493
permissions = "-rw-" if file.permission == "read_write" else "-r--"
483494
# Use the file description as the summary, or provide a default message
@@ -487,6 +498,29 @@ async def _construct_filesystem_system_prompt(self) -> str:
487498
else "No summary available yet, use the context available to determine the use of this file"
488499
)
489500
system_prompt += f"{permissions} {file.path} [File content summary: {summary}]\n"
501+
502+
# Add ARCHIVES_ADDON_PROMPT if there are archives
503+
if archives_files:
504+
system_prompt += "\n" + ARCHIVES_ADDON_PROMPT + "\n"
505+
506+
# order the archives files by timestamp, newest first
507+
archives_files.sort(key=lambda f: f.timestamp, reverse=True)
508+
# take the top 25 archives files
509+
archives_files = archives_files[:25]
510+
# order them alphabetically by path
511+
archives_files.sort(key=lambda f: f.path.lower())
512+
513+
for file in archives_files:
514+
# Format permissions: -rw- for read_write, -r-- for read
515+
permissions = "-rw-" if file.permission == "read_write" else "-r--"
516+
# Use the file description as the summary, or provide a default message
517+
summary = (
518+
file.description
519+
if file.description
520+
else "No summary available yet, use the context available to determine the use of this file"
521+
)
522+
system_prompt += f"{permissions} {file.path} [File content summary: {summary}]\n"
523+
490524
return system_prompt
491525

492526
def _override_edit_file_description(self, tools: list[ChatCompletionToolParam]) -> list[ChatCompletionToolParam]:

libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/_virtual_filesystem.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ def mounts(self) -> Iterable[MountPoint]:
4747
return self._mounts.values()
4848

4949
def _split_path(self, path: str) -> tuple[str, str]:
50-
_, mount_path_segment, *source_path_segments = path.split("/")
50+
path_segments = path.split("/")
51+
if len(path_segments) < 2:
52+
raise ValueError(
53+
f"Invalid path format: {path}. Path must start with '/' and contain at least one directory."
54+
)
55+
56+
_, mount_path_segment, *source_path_segments = path_segments
5157
mount_path = "/" + mount_path_segment
5258
source_path = "/" + "/".join(source_path_segments)
5359
return mount_path, source_path

libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_view_tool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ async def execute(self, args: dict) -> str | Iterable[ChatCompletionContentPartT
5454
file_content = await self.virtual_filesystem.read_file(path)
5555
except FileNotFoundError:
5656
return f"Error: File at path {path} not found. Please pay attention to the available files and try again."
57+
except ValueError as e:
58+
return f"Error: {str(e)}"
5759

5860
result = f'<file path="{path}">\n{file_content}\n</file>'
5961
return result

libraries/python/chat-context-toolkit/test/virtual_filesystem/tools/test_view_tool.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,18 @@ async def test_view_tool_error_handling():
2929
result
3030
== "Error: File at path /nonexistent.txt not found. Please pay attention to the available files and try again."
3131
)
32+
33+
34+
async def test_view_tool_invalid_path_format():
35+
"""Test ViewTool.execute with invalid path formats."""
36+
mock_vfs = MagicMock(spec=VirtualFileSystem)
37+
mock_vfs.read_file.side_effect = ValueError(
38+
"Invalid path format: Bair Haiku.md. Path must start with '/' and contain at least one directory."
39+
)
40+
41+
view_tool = ViewTool(mock_vfs)
42+
result = await view_tool.execute({"path": "Bair Haiku.md"})
43+
assert (
44+
result
45+
== "Error: Invalid path format: Bair Haiku.md. Path must start with '/' and contain at least one directory."
46+
)

0 commit comments

Comments
 (0)