From 012703d8e962b5d67237f7b57a28be3ab3e96e2b Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Mon, 30 Jun 2025 15:26:09 -0700 Subject: [PATCH 1/3] Updates doc assistant To use new APIs --- .../document-assistant/assistant/chat.py | 18 ++++++++++-- .../assistant/filesystem/_file_sources.py | 12 +------- .../assistant/response/completion_handler.py | 3 -- .../assistant/response/responder.py | 3 -- .../virtual_filesystem/tools/__init__.py | 4 +-- .../virtual_filesystem/tools/_ls_tool.py | 23 +++++++++++---- .../virtual_filesystem/tools/_view_tool.py | 29 ++++++++++++++----- 7 files changed, 57 insertions(+), 35 deletions(-) diff --git a/assistants/document-assistant/assistant/chat.py b/assistants/document-assistant/assistant/chat.py index 1fdcd738..7fc4ce77 100644 --- a/assistants/document-assistant/assistant/chat.py +++ b/assistants/document-assistant/assistant/chat.py @@ -11,7 +11,9 @@ import deepmerge from assistant_extensions import dashboard_card, navigator -from assistant_extensions.chat_context_toolkit.archive import ArchiveTaskQueues +from assistant_extensions.attachments import get_attachments +from assistant_extensions.chat_context_toolkit.archive import ArchiveTaskQueues, construct_archive_summarizer +from assistant_extensions.chat_context_toolkit.message_history import construct_attachment_summarizer from assistant_extensions.mcp import MCPServerConfig from chat_context_toolkit.archive import ArchiveTaskConfig from content_safety.evaluators import CombinedContentSafetyEvaluator @@ -209,11 +211,21 @@ async def on_message_created( ) ) + attachments = await get_attachments( + context=context, + summarizer=construct_attachment_summarizer( + service_config=config.generative_ai_fast_client_config.service_config, + request_config=config.generative_ai_fast_client_config.request_config, + ), + ) await archive_task_queues.enqueue_run( context=context, - service_config=config.generative_ai_client_config.service_config, - request_config=config.generative_ai_client_config.request_config, + attachments=attachments, archive_task_config=ArchiveTaskConfig(chunk_token_count_threshold=30_000), + archive_summarizer=construct_archive_summarizer( + service_config=config.generative_ai_fast_client_config.service_config, + request_config=config.generative_ai_fast_client_config.request_config, + ), ) diff --git a/assistants/document-assistant/assistant/filesystem/_file_sources.py b/assistants/document-assistant/assistant/filesystem/_file_sources.py index a380a4ae..41998cd3 100644 --- a/assistants/document-assistant/assistant/filesystem/_file_sources.py +++ b/assistants/document-assistant/assistant/filesystem/_file_sources.py @@ -2,7 +2,7 @@ from typing import Callable, Iterable from assistant_drive import Drive -from chat_context_toolkit.virtual_filesystem import DirectoryEntry, FileEntry, MountPoint, WriteToolDefinition +from chat_context_toolkit.virtual_filesystem import DirectoryEntry, FileEntry, MountPoint from semantic_workbench_assistant.assistant_app import ConversationContext from assistant.filesystem._filesystem import AttachmentsExtension, _get_attachments, log_and_send_message_on_error @@ -17,11 +17,6 @@ def __init__(self, context: ConversationContext, attachments_extension: Attachme self.context = context self.attachments_extension = attachments_extension - @property - def write_tools(self) -> Iterable[WriteToolDefinition]: - """Get the list of write tools provided by this file system provider.""" - return [] - async def list_directory(self, path: str) -> Iterable[DirectoryEntry | FileEntry]: """ List files and directories at the specified path. @@ -86,11 +81,6 @@ def __init__(self, context: ConversationContext, drive_provider: Callable[[Conve self.context = context self.drive_provider = drive_provider - @property - def write_tools(self) -> Iterable[WriteToolDefinition]: - """Get the list of write tools provided by this file system provider.""" - return [] - async def list_directory(self, path: str) -> Iterable[DirectoryEntry | FileEntry]: """ List files and directories at the specified path. diff --git a/assistants/document-assistant/assistant/response/completion_handler.py b/assistants/document-assistant/assistant/response/completion_handler.py index 619051c7..45da0833 100644 --- a/assistants/document-assistant/assistant/response/completion_handler.py +++ b/assistants/document-assistant/assistant/response/completion_handler.py @@ -44,16 +44,13 @@ async def handle_completion( - sampling_handler: OpenAISamplingHandler, step_result: StepResult, completion: ParsedChatCompletion | ChatCompletion, mcp_sessions: List[MCPSession], context: ConversationContext, request_config: OpenAIRequestConfig, - silence_token: str, metadata_key: str, response_start_time: float, - attachments_extension: AttachmentsExtension, guidance_enabled: bool, virtual_filesystem: VirtualFileSystem, ) -> StepResult: diff --git a/assistants/document-assistant/assistant/response/responder.py b/assistants/document-assistant/assistant/response/responder.py index 7653b1a9..17902dea 100644 --- a/assistants/document-assistant/assistant/response/responder.py +++ b/assistants/document-assistant/assistant/response/responder.py @@ -324,16 +324,13 @@ async def _step(self, step_count) -> StepResult: await update_dynamic_ui_state(self.context, tool_call.arguments) step_result = await handle_completion( - self.sampling_handler, step_result, completion, self.mcp_sessions, self.context, self.config.generative_ai_client_config.request_config, - "SILENCE", # TODO: This is not being used correctly. f"respond_to_conversation:step_{step_count}", response_start_time, - self.attachments_extension, self.config.orchestration.guidance.enabled, self.virtual_filesystem, ) diff --git a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/__init__.py b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/__init__.py index 0fe11475..12d00361 100644 --- a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/__init__.py +++ b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/__init__.py @@ -1,5 +1,5 @@ -from ._ls_tool import LsTool +from ._ls_tool import LsTool, LsToolOptions from ._tools import ToolCollection, tool_result_to_string from ._view_tool import ViewTool -__all__ = ["LsTool", "ToolCollection", "ViewTool", "tool_result_to_string"] +__all__ = ["LsTool", "ToolCollection", "ViewTool", "tool_result_to_string", "LsToolOptions"] diff --git a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_ls_tool.py b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_ls_tool.py index 30b9c9c3..3ca95102 100644 --- a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_ls_tool.py +++ b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_ls_tool.py @@ -1,15 +1,28 @@ +from dataclasses import dataclass from typing import Iterable + from openai.types.chat import ChatCompletionContentPartTextParam, ChatCompletionToolParam from chat_context_toolkit.virtual_filesystem._types import DirectoryEntry, FileEntry, ToolDefinition from chat_context_toolkit.virtual_filesystem._virtual_filesystem import VirtualFileSystem +@dataclass +class LsToolOptions: + tool_name: str = "ls" + """Name of the tool provided to the LLM.""" + tool_description: str = "List files and directories at the specified path. Root directories: {root_path_list}" + """Description of the tool provided to the LLM.""" + path_argument_description: str = "The path to list (e.g., '/', '/docs', '/docs/subdir)" + """Description of the 'path' argument.""" + + class LsTool(ToolDefinition): """Tool for listing files and directories in the virtual file system.""" - def __init__(self, virtual_filesystem: VirtualFileSystem): + def __init__(self, virtual_filesystem: VirtualFileSystem, options: LsToolOptions = LsToolOptions()) -> None: self.virtual_filesystem = virtual_filesystem + self.options = options @property def tool_param(self) -> ChatCompletionToolParam: @@ -20,14 +33,14 @@ def tool_param(self) -> ChatCompletionToolParam: return ChatCompletionToolParam( type="function", function={ - "name": "ls", - "description": "List files and directories at the specified path. Root directories: " + mount_list, + "name": self.options.tool_name, + "description": self.options.tool_description.format(root_path_list=mount_list), "parameters": { "type": "object", "properties": { "path": { "type": "string", - "description": "The path to list (e.g., '/', '/docs', '/docs/subdir')", + "description": self.options.path_argument_description.format(root_path_list=mount_list), } }, "required": ["path"], @@ -39,7 +52,7 @@ async def execute(self, args: dict) -> str | Iterable[ChatCompletionContentPartT """Execute the built-in ls tool to list directory contents.""" path = args.get("path") if not path: - return "Error: 'path' argument is required for the ls tool" + return f"Error: 'path' argument is required for the {self.options.tool_name} tool" try: entries = await self.virtual_filesystem.list_directory(path) diff --git a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_view_tool.py b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_view_tool.py index 0ef7db35..9be60ba1 100644 --- a/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_view_tool.py +++ b/libraries/python/chat-context-toolkit/chat_context_toolkit/virtual_filesystem/tools/_view_tool.py @@ -1,29 +1,42 @@ +from dataclasses import dataclass from typing import Iterable + +from openai.types.chat import ChatCompletionContentPartTextParam, ChatCompletionToolParam + from chat_context_toolkit.virtual_filesystem._types import ToolDefinition from chat_context_toolkit.virtual_filesystem._virtual_filesystem import VirtualFileSystem -from openai.types.chat import ChatCompletionContentPartTextParam, ChatCompletionToolParam + + +@dataclass +class ViewToolOptions: + tool_name: str = "view" + """Name of the tool provided to the LLM.""" + tool_description: str = "Read the contents of a file at the specified path" + """Description of the tool provided to the LLM.""" + path_argument_description: str = "The path to the file to read (e.g., '/docs/file.txt')" + """Description of the 'path' argument.""" class ViewTool(ToolDefinition): """Tool for viewing the contents of a file in the virtual file system.""" - def __init__(self, virtual_filesystem: VirtualFileSystem, tool_name: str = "view"): + def __init__(self, virtual_filesystem: VirtualFileSystem, options: ViewToolOptions = ViewToolOptions()) -> None: self.virtual_filesystem = virtual_filesystem - self.tool_name = tool_name + self.options = options @property def tool_param(self) -> ChatCompletionToolParam: return ChatCompletionToolParam( type="function", function={ - "name": self.tool_name, - "description": "Read the contents of a file at the specified path", + "name": self.options.tool_name, + "description": self.options.tool_description, "parameters": { "type": "object", "properties": { "path": { "type": "string", - "description": "The path to the file to read (e.g., '/docs/file.txt')", + "description": self.options.path_argument_description, } }, "required": ["path"], @@ -35,12 +48,12 @@ async def execute(self, args: dict) -> str | Iterable[ChatCompletionContentPartT """Execute the built-in view tool to read file contents.""" path = args.get("path") if not path: - return "Error: 'path' argument is required for the view tool" + return f"Error: 'path' argument is required for the {self.options.tool_name} tool" try: file_content = await self.virtual_filesystem.read_file(path) except FileNotFoundError: return f"Error: File at path {path} not found. Please pay attention to the available files and try again." - result = f"\n{file_content}\n" + result = f'\n{file_content}\n' return result From f1c7263a7a43fd8f78dd37ba559b465f44bb450c Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Mon, 30 Jun 2025 15:33:43 -0700 Subject: [PATCH 2/3] Updates test --- .../test/virtual_filesystem/tools/test_view_tool.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/python/chat-context-toolkit/test/virtual_filesystem/tools/test_view_tool.py b/libraries/python/chat-context-toolkit/test/virtual_filesystem/tools/test_view_tool.py index 6d1f4aee..7a6a666b 100644 --- a/libraries/python/chat-context-toolkit/test/virtual_filesystem/tools/test_view_tool.py +++ b/libraries/python/chat-context-toolkit/test/virtual_filesystem/tools/test_view_tool.py @@ -14,7 +14,7 @@ async def test_builtin_view_tool_reads_file(): result = await view_tool.execute({"path": "/docs/file1.txt"}) mock_vfs.read_file.assert_called_once_with("/docs/file1.txt") - assert result == "\nHello World\n" + assert result == '\nHello World\n' async def test_view_tool_error_handling(): @@ -25,4 +25,7 @@ async def test_view_tool_error_handling(): # view on non-existent file should raise error view_tool = ViewTool(mock_vfs) result = await view_tool.execute({"path": "/nonexistent.txt"}) - assert result == "Error: File at path /nonexistent.txt not found. Please pay attention to the available files and try again." + assert ( + result + == "Error: File at path /nonexistent.txt not found. Please pay attention to the available files and try again." + ) From e91dc2276960c4c2974cf881488e0e7a536e51ca Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Mon, 30 Jun 2025 15:47:00 -0700 Subject: [PATCH 3/3] Run library tests in parallel --- .github/workflows/libraries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/libraries.yml b/.github/workflows/libraries.yml index d0bdc258..99db4644 100644 --- a/.github/workflows/libraries.yml +++ b/.github/workflows/libraries.yml @@ -36,4 +36,4 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: Install and run tests - run: make test + run: make -j $(nproc) test