aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/chatbot/agentsystem/Agent.ts14
-rw-r--r--src/client/views/nodes/chatbot/guides/guide.md647
-rw-r--r--src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts158
-rw-r--r--src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts561
-rw-r--r--src/server/chunker/pdf_chunker.py5
5 files changed, 952 insertions, 433 deletions
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
index bab2081b3..b166254bb 100644
--- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts
+++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
@@ -8,7 +8,6 @@ import { StreamedAnswerParser } from '../response_parsers/StreamedAnswerParser';
import { BaseTool } from '../tools/BaseTool';
import { CalculateTool } from '../tools/CalculateTool';
//import { CreateAnyDocumentTool } from '../tools/CreateAnyDocTool';
-import { CreateDocTool } from '../tools/CreateDocumentTool';
import { DataAnalysisTool } from '../tools/DataAnalysisTool';
import { DocumentMetadataTool } from '../tools/DocumentMetadataTool';
import { ImageCreationTool } from '../tools/ImageCreationTool';
@@ -83,13 +82,8 @@ export class Agent {
dataAnalysis: new DataAnalysisTool(csvData),
websiteInfoScraper: new WebsiteInfoScraperTool(addLinkedUrlDoc),
searchTool: new SearchTool(addLinkedUrlDoc),
- // createCSV: new CreateCSVTool(createCSVInDash),
noTool: new NoTool(),
- imageCreationTool: new ImageCreationTool(createImage),
- // createTextDoc: new CreateTextDocTool(addLinkedDoc),
- // createDoc: new CreateDocTool(addLinkedDoc),
- // createAnyDocument: new CreateAnyDocumentTool(addLinkedDoc),
- // dictionary: new DictionaryTool(),
+ //imageCreationTool: new ImageCreationTool(createImage),
documentMetadata: new DocumentMetadataTool(this),
};
}
@@ -103,7 +97,7 @@ export class Agent {
* @param maxTurns The maximum number of turns to allow in the conversation.
* @returns The final response from the assistant.
*/
- async askAgent(question: string, onProcessingUpdate: (processingUpdate: ProcessingInfo[]) => void, onAnswerUpdate: (answerUpdate: string) => void, maxTurns: number = 30): Promise<AssistantMessage> {
+ async askAgent(question: string, onProcessingUpdate: (processingUpdate: ProcessingInfo[]) => void, onAnswerUpdate: (answerUpdate: string) => void, maxTurns: number = 50): Promise<AssistantMessage> {
console.log(`Starting query: ${question}`);
const MAX_QUERY_LENGTH = 1000; // adjust the limit as needed
@@ -470,7 +464,7 @@ export class Agent {
actionInput.fieldValue = String(actionInput.fieldValue);
}
}
-
+
// Handle fieldEdits parameter (for multiple field edits)
if ('fieldEdits' in actionInput && actionInput.fieldEdits) {
try {
@@ -521,7 +515,7 @@ export class Agent {
/**
* Reinitializes the DocumentMetadataTool with a direct reference to the ChatBox instance.
* This ensures that the tool can properly access the ChatBox document and find related documents.
- *
+ *
* @param chatBox The ChatBox instance to pass to the DocumentMetadataTool
*/
public reinitializeDocumentMetadataTool(chatBox: any): void {
diff --git a/src/client/views/nodes/chatbot/guides/guide.md b/src/client/views/nodes/chatbot/guides/guide.md
new file mode 100644
index 000000000..2af76490d
--- /dev/null
+++ b/src/client/views/nodes/chatbot/guides/guide.md
@@ -0,0 +1,647 @@
+# Dash Agent Tool Development Guide
+
+**Table of Contents**
+
+1. [Introduction: The Role and Potential of Tools](#1-introduction-the-role-and-potential-of-tools)
+ - Beyond Information Retrieval: Action and Creation
+ - The Agent as an Extension of the User within Dash
+2. [Core Agent Architecture Deep Dive](#2-core-agent-architecture-deep-dive)
+ - The ReAct-Inspired Interaction Loop: Rationale and Flow
+ - XML Structure: Why XML? Parsing and LLM Guidance
+ - Stages (`<stage>`) and Roles (`role="..."`): Enforcing Order
+ - Message Management (`messages`, `interMessages`): Building Context
+ - State Handling: Agent's Internal State vs. Tool Statelessness
+ - Key Components Revisited (`Agent.ts`, `prompts.ts`, `BaseTool.ts`, Parsers)
+ - Role of `prompts.ts`: Template and Dynamic Content Injection
+ - Limits and Safeguards (`maxTurns`)
+3. [Anatomy of a Dash Agent Tool (Detailed Breakdown)](#3-anatomy-of-a-dash-agent-tool-detailed-breakdown)
+ - The `BaseTool` Abstract Class: Foundation and Contract
+ - `ToolInfo`: Defining Identity and LLM Instructions
+ - `name`: Uniqueness and LLM Invocation Trigger
+ - `description`: The LLM's Primary Guide - _Dynamically Injected into Prompt_
+ - `parameterRules`: The Input Contract (In Depth)
+ - `citationRules`: Controlling Grounding in the Final Answer
+ - The `execute` Method: Heart of the Tool
+ - Asynchronous Nature (`async/await`)
+ - Receiving Arguments (`args: ParametersType<P>`)
+ - Performing the Core Logic (API calls, Dash functions)
+ - Returning `Observation[]`: The Output Contract (In Depth)
+ - The `inputValidator` Method: Handling Edge Cases
+4. [The Agent-Tool Interaction Flow (Annotated XML Trace)](#4-the-agent-tool-interaction-flow-annotated-xml-trace)
+ - Detailed Step-by-Step with `Agent.ts` actions highlighted
+5. [Step-by-Step Guide: Creating a New Tool](#5-step-by-step-guide-creating-a-new-tool)
+ - Step 1: Define Goal, Scope, Inputs, Outputs, Dash Interactions, Side Effects
+ - Step 2: Create the Tool Class File (Directory Structure)
+ - Step 3: Define Parameters (`parameterRules`) - Type Handling, Arrays
+ - Step 4: Define Tool Information (`ToolInfo`) - Crafting the _Crucial_ `description`
+ - Step 5: Implement `execute` - Defensive Coding, Using Injected Functions, Error Handling Pattern
+ - Step 6: Format Output (`Observation[]`) - Chunk Structure, `chunk_type`, IDs
+ - Step 7: Register Tool in `Agent.ts` - _This makes the tool available to the prompt_
+ - Step 8: Verify Prompt Integration (No Manual Editing Needed)
+ - Step 9: Testing Your Tool - Strategies and What to Look For
+6. [Deep Dive: Advanced Concepts & Patterns](#6-deep-dive-advanced-concepts--patterns)
+ - Handling Complex Data Types (Arrays, Objects) in Parameters/Observations
+ - Binary Data Handling (e.g., Base64 in Chunks)
+ - Managing Long-Running Tasks (Beyond simple `await`)
+ - Tools Needing Dash Context (Passing `this` vs. specific functions)
+ - The Role of `chunk_id` and `chunk_type`
+7. [Best Practices and Advanced Considerations](#7-best-practices-and-advanced-considerations)
+ - Error Handling & Reporting (Specific Error Chunks)
+ - Security Considerations (Input Sanitization, API Key Management, Output Filtering)
+ - Performance Optimization (Minimize `execute` workload)
+ - Idempotency: Designing for Retries
+ - Tool Granularity: Single Responsibility Principle
+ - Context Window Management (Concise Descriptions are Key)
+ - User Experience (Tool output clarity)
+ - Maintainability and Code Comments
+8. [Debugging Strategies](#8-debugging-strategies)
+ - Console Logging within `execute`
+ - Inspecting `interMessages` in `Agent.ts`
+ - Testing Tool Logic Standalone
+ - Analyzing LLM Failures (Incorrect tool choice -> Check `description`, bad parameters)
+9. [Example: `CreateDashNoteTool`](#9-example-createdashnotetool)
+10. [Glossary of Key Terms](#10-glossary-of-key-terms)
+11. [Conclusion](#11-conclusion)
+
+---
+
+## 1. Introduction: The Role and Potential of Tools
+
+Welcome, Dash team member! This guide will walk you through creating new tools for the Dash Agent. The Agent is designed to interact with users, understand their queries, and leverage specialized **Tools** to perform actions or retrieve information that the core Large Language Model (LLM) cannot do on its own.
+
+Tools extend the Agent's capabilities beyond simple conversation. They allow the Agent to:
+
+- Interact with external APIs (e.g., web search, calculators, image generation).
+- Access and process data specific to the user's Dash environment (e.g., querying document metadata, analyzing linked CSVs).
+- Perform actions within Dash (e.g., creating new documents, adding links, modifying metadata).
+
+By building new tools, you directly enhance the Agent's utility and integration within the Dash ecosystem.
+
+### Beyond Information Retrieval: Action and Creation
+
+While tools like `RAGTool` and `SearchTool` retrieve information, others _act_. `CalculateTool` performs computations, `ImageCreationTool` generates content, and importantly, tools like `DocumentMetadataTool` and your custom tools can **modify the Dash environment**, creating documents, adding links, or changing properties.
+
+### The Agent as an Extension of the User within Dash
+
+Think of the Agent, equipped with tools, as an intelligent assistant that can perform tasks _on behalf of the user_ directly within their Dash workspace. This deep integration is a key differentiator.
+
+---
+
+## 2. Core Agent Architecture Deep Dive
+
+Understanding the "why" behind the architecture helps in tool development.
+
+### The ReAct-Inspired Interaction Loop: Rationale and Flow
+
+The Agent operates based on a loop inspired by the ReAct (Reason + Act) framework. The LLM alternates between:
+
+- **Reasoning (`<thought>`):** Analyzing the query and deciding the next step.
+- **Acting (`<action>`, `<action_input>`):** Selecting and preparing to use a tool, or formulating a final answer (`<answer>`).
+- **Observing (`<observation>`):** Receiving the results from a tool execution.
+
+This structure (Reason -> Act -> Observe -> Reason...) forces the LLM to break down complex tasks into manageable steps, making the process more reliable and auditable than letting the LLM generate a monolithic plan upfront.
+
+### XML Structure: Why XML? Parsing and LLM Guidance
+
+- **Why XML?** LLMs are generally adept at generating well-formed XML. XML's explicit start/end tags make parsing by `Agent.ts` (using libraries like `fast-xml-parser`) more robust and less prone to LLM "hallucinations" breaking the structure compared to formats like JSON in some complex scenarios.
+- **LLM Guidance:** The strict XML schema defined in the system prompt provides clear guardrails for the LLM's output, constraining it to valid actions and formats.
+
+### Stages (`<stage>`) and Roles (`role="..."`): Enforcing Order
+
+The `<stage number="...">` ensures sequential processing. The `role` attribute indicates the source (e.g., `user`, `assistant`) and dictates control flow. `Agent.ts` _waits_ for a `user` (or `system-error-reporter`) stage after sending an `assistant` stage, enforcing the turn-based nature. The LLM is explicitly told only to generate `assistant` stages.
+
+### Message Management (`messages`, `interMessages`): Building Context
+
+- `messages`: The user-facing chat history (persisted in the Dash Doc `data` field).
+- `interMessages`: The **internal, complete context** sent to the LLM for each turn. It includes the system prompt, user queries, _all intermediate thoughts, actions, rules, inputs, and observations_. This ensures the LLM has the full history of the current reasoning chain. It grows with each step in the loop.
+
+### State Handling: Agent's Internal State vs. Tool Statelessness
+
+- `Agent.ts` manages the conversational state (`interMessages`, current turn number, `processingInfo`, etc.).
+- **Tools should be designed to be stateless.** They receive inputs via `args`, perform their action, and return results. Any persistent state relevant to the user's work should reside within Dash Docs/Fobs, accessible perhaps via tools like `DocumentMetadataTool` or specific functions passed to the tool.
+
+### Key Components Revisited (`Agent.ts`, `prompts.ts`, `BaseTool.ts`, Parsers)
+
+- `Agent.ts`: The central controller. Parses XML, validates actions, manages the loop, calls `tool.execute`, formats `Observation`s. Handles the streaming updates for the _final_ answer via `StreamedAnswerParser`. Holds the registry of available tools (`this.tools`).
+- `prompts.ts` (`getReactPrompt`): Generates the system prompt for the LLM. It acts as a **template** defining the Agent's overall task, rules, and the required XML structure. Crucially, it **dynamically injects the list of available tools** (including their names and descriptions) based on the tools registered in the `Agent.ts` instance at runtime. **_You do not manually add tool descriptions here._**
+- `BaseTool.ts`: The abstract class defining the _interface_ all tools must adhere to. Contains properties like `name` and `description` used by `getReactPrompt`.
+- Parsers (`AnswerParser`, `StreamedAnswerParser`): Handle the final `<answer>` tag, extracting structured content, citations, etc., for UI display (`ChatBox.tsx`).
+
+### Limits and Safeguards (`maxTurns`)
+
+`Agent.ts` includes a `maxTurns` limit (default 30) to prevent infinite loops if the LLM gets stuck or fails to reach an `<answer>` stage.
+
+---
+
+## 3. Anatomy of a Dash Agent Tool (Detailed Breakdown)
+
+All tools inherit from the abstract class `BaseTool`.
+
+### The `BaseTool` Abstract Class: Foundation and Contract
+
+- Located in `src/components/views/nodes/chatbot/agentsystem/tools/BaseTool.ts`.
+- Generic `BaseTool<P extends ReadonlyArray<Parameter>>`: `P` represents the specific, readonly array of `Parameter` definitions for _your_ tool, ensuring type safety for the `args` in `execute`.
+- Defines the public properties (`name`, `description`, `parameterRules`, `citationRules`) and the abstract `execute` method that all tools must implement.
+
+### `ToolInfo`: Defining Identity and LLM Instructions
+
+- A configuration object (`{ name: string; description: string; parameterRules: P; citationRules: string; }`) passed to the `BaseTool` constructor.
+- `name: string`:
+ - The **unique identifier** for your tool (e.g., `dictionaryLookup`, `createDashNote`).
+ - Must match the key used when registering the tool in `Agent.ts`'s `this.tools` map.
+ - This is the string the LLM will output in the `<action>` tag to invoke your tool.
+ - Keep it concise and descriptive (camelCase recommended).
+- `description: string`: The LLM's Primary Guide - _Dynamically Injected into Prompt_.
+ - This text is extracted from your `ToolInfo` object when `getReactPrompt` is called.
+ - It's **the text the LLM sees** to understand your tool's purpose and when to use it.
+ - **Crafting this is critical.** Make it extremely clear, concise, and accurate. Explicitly state:
+ - What the tool _does_.
+ - What _inputs_ it needs (briefly).
+ - What _output_ it provides.
+ - _Crucially_, under what circumstances the Agent should _choose_ this tool over others. (e.g., "Use this tool to create _new_ Dash notes, not for editing existing ones.")
+- `parameterRules: P` (where `P extends ReadonlyArray<Parameter>`):
+ - The readonly array defining the **exact inputs** your `execute` method expects.
+ - Each element is a `Parameter` object (`{ name: string; type: 'string' | ... ; required: boolean; description: string; max_inputs?: number }`):
+ - `name`: Name of the parameter (e.g., `wordToDefine`, `noteContent`). Used as the key in the `args` object passed to `execute`.
+ - `type`: `'string' | 'number' | 'boolean' | 'string[]' | 'number[]' | 'boolean[]'`. `Agent.ts` uses this for basic validation and parsing (specifically for arrays).
+ - `required`: `true` if the LLM _must_ provide this parameter for the tool to function. `Agent.ts` checks this before calling `execute` (unless `inputValidator` overrides).
+ - `description`: Explanation of the parameter _for the LLM_. Guides the LLM on _what value_ to provide. Be specific (e.g., "The exact URL to scrape", "A search query suitable for web search").
+ - `max_inputs?`: Optional. For array types (`string[]`, etc.), suggests a limit to the LLM on the number of items to provide.
+- `citationRules: string`:
+ - Instructions for the LLM on how to construct the `<citations>` block within the final `<answer>` tag _when information obtained from this specific tool is used_.
+ - Directly influences the grounding and verifiability of the Agent's final response.
+ - Be explicit about the format: "Cite using `<citation index="..." chunk_id="..." type="your_chunk_type">Optional text snippet</citation>`".
+ - Specify what goes into `chunk_id` (e.g., "Use the ID provided in the observation chunk"), `type` (a constant string representing your tool's output type), and whether the text snippet should be included (often empty for URLs, calculations).
+ - If no citation is appropriate (e.g., calculator, ephemeral action), state clearly: "No citation needed for this tool's output."
+
+### The `execute` Method: Heart of the Tool
+
+- `abstract execute(args: ParametersType<P>): Promise<Observation[]>;`
+- **Asynchronous Nature (`async/await`):** Must be `async` because tool actions often involve I/O (network requests, database access via Dash functions, filesystem). Must return a `Promise`.
+- **Receiving Arguments (`args: ParametersType<P>`):**
+ - Receives a single argument `args`. This object's keys are your defined parameter names (from `parameterRules`), and the values are those provided by the LLM in the `<action_input><inputs>` block.
+ - The type `ParametersType<P>` infers the structure of `args` based on your specific `parameterRules` definition (`P`), providing TypeScript type safety.
+ - `Agent.ts` performs basic validation (required fields) and type coercion (for arrays) before calling `execute`. However, **always perform defensive checks** within `execute` (e.g., check if required args are truly present and not empty strings, check types if crucial).
+- **Performing the Core Logic:** This is where your tool does its work. Examples:
+ - Call external APIs (using `axios`, `fetch`, or specific SDKs).
+ - Call Dash functions passed via the constructor (e.g., `this._createDocInDash(...)`, `this._addLinkedUrlDoc(...)`).
+ - Perform calculations or data transformations.
+ - Interact with other backend systems if necessary.
+- **Returning `Observation[]`: The Output Contract (In Depth)**
+ - The method **must** resolve to an array of `Observation` objects. Even if there's only one piece of output, return it in an array: `[observation]`.
+ - Each `Observation` object usually has the structure `{ type: 'text', text: string }`. Other types might be possible but `text` is standard for LLM interaction.
+ - The `text` string **must** contain the tool's output formatted within one or more `<chunk>` tags. This is how the Agent passes structured results back to the LLM.
+ - Format: `<chunk chunk_id="UNIQUE_ID" chunk_type="YOUR_TYPE">OUTPUT_DATA</chunk>`
+ - `chunk_id`: A unique identifier for this specific piece of output (use `uuidv4()`). Essential for linking citations back to observations.
+ - `chunk_type`: A string literal describing the _semantic type_ of the data (e.g., `'search_result_url'`, `'calculation_result'`, `'note_creation_status'`, `'error'`, `'metadata_info'`). Helps the LLM interpret the result. Choose consistent and descriptive names.
+ - `OUTPUT_DATA`: The actual result from your tool. Can be simple text, JSON stringified data, etc. Keep it concise if possible for the LLM context.
+ - **Return errors** using the same format, but with `chunk_type="error"` and a descriptive error message inside the chunk tag. This allows the Agent loop to continue gracefully and potentially inform the LLM or user.
+
+### The `inputValidator` Method: Handling Edge Cases
+
+- `inputValidator(inputParam: ParametersType<readonly Parameter[]>) { return false; }` (Default implementation in `BaseTool`).
+- Override this method _only_ if your tool needs complex input validation logic beyond simple `required` checks (e.g., dependencies between parameters).
+- If you override it to return `true`, `Agent.ts` will skip its standard check for missing _required_ parameters. Your `execute` method becomes fully responsible for validating the `args` object.
+- Use case example: `DocumentMetadataTool` uses it to allow either `fieldName`/`fieldValue` OR `fieldEdits` to be provided for the "edit" action.
+
+---
+
+## 4. The Agent-Tool Interaction Flow (Annotated XML Trace)
+
+Let's trace the `dictionaryLookup` example with `Agent.ts` actions:
+
+1. **User Input:** User types "What is hypermedia?" and submits.
+
+ - `// ChatBox.tsx calls agent.askAgent("What is hypermedia?")`
+ - `// Agent.ts adds stage 1 to interMessages:`
+
+ ```xml
+ <stage number="1" role="user">
+ <query>What is hypermedia?</query>
+ </stage>
+ ```
+
+ - `// Agent.ts calls LLM with interMessages.`
+
+2. **LLM Thought & Action:** LLM processes the query and system prompt (which includes the dynamically generated description for `dictionaryLookup`).
+
+ - `// LLM responds with stage 2:`
+
+ ```xml
+ <stage number="2" role="assistant">
+ <thought>The user is asking for a definition. The dictionaryLookup tool is appropriate for this.</thought>
+ <action>dictionaryLookup</action>
+ </stage>
+ ```
+
+ - `// Agent.ts parses stage 2. Finds <action>dictionaryLookup</action>.`
+ - `// Agent.ts retrieves the dictionaryTool instance: tool = this.tools['dictionaryLookup'].`
+ - `// Agent.ts gets parameter rules: rules = tool.getActionRule().`
+
+3. **Agent Provides Rules:** `Agent.ts` formats the rules into XML.
+
+ - `// Agent.ts adds stage 3 to interMessages:`
+
+ ```xml
+ <stage number="3" role="user">
+ <action_rules>
+ <tool>dictionaryLookup</tool>
+ <description>Looks up the definition of a given English word.</description>
+ <citationRules>Cite the definition using the provided chunk_id and type="dictionary_definition". Leave citation content empty.</citationRules>
+ <parameters>
+ <word>
+ <type>string</type>
+ <description>The word to define.</description>
+ <required>true</required>
+ </word>
+ </parameters>
+ </action_rules>
+ </stage>
+ ```
+
+ - `// Agent.ts calls LLM with updated interMessages.`
+
+4. **LLM Provides Inputs:** LLM uses the rules to formulate the required inputs.
+
+ - `// LLM responds with stage 4:`
+
+ ```xml
+ <stage number="4" role="assistant">
+ <action_input>
+ <action_input_description>Looking up the definition for the word 'hypermedia'.</action_input_description>
+ <inputs>
+ <word>hypermedia</word>
+ </inputs>
+ </action_input>
+ </stage>
+ ```
+
+ - `// Agent.ts parses stage 4. Finds <action_input>. Extracts inputs: { word: 'hypermedia' }.`
+ - `// Agent.ts validates required params (finds 'word'). Checks tool.inputValidator (returns false). OK.`
+ - `// Agent.ts calls: const observations = await dictionaryTool.execute({ word: 'hypermedia' });`
+
+5. **Tool Execution -> Observation:** The `dictionaryTool.execute` method runs, calls an API, gets the definition, and formats the `Observation`.
+
+ - `// dictionaryTool.execute resolves Promise with:`
+
+ ```javascript
+ [{ type: 'text', text: '<chunk chunk_id="dict-abc-123" chunk_type="dictionary_definition">Hypermedia is an extension of hypertext allowing...</chunk>' }];
+ ```
+
+ - `// Agent.ts receives the resolved Observation[].`
+
+6. **Agent Provides Observation:** `Agent.ts` formats the observation(s) into the next stage.
+
+ - `// Agent.ts adds stage 5 to interMessages:`
+
+ ```xml
+ <stage number="5" role="user">
+ <observation>
+ <chunk chunk_id="dict-abc-123" chunk_type="dictionary_definition">Hypermedia is an extension of hypertext allowing...</chunk>
+ </observation>
+ </stage>
+ ```
+
+ - `// Agent.ts calls LLM with updated interMessages.`
+
+7. **LLM Generates Final Answer:** LLM uses the full context (query, thoughts, actions, observation) and the `citationRules` from the `dictionaryLookup` tool.
+ - `// LLM responds with stage 6:`
+ ```xml
+ <stage number="6" role="assistant">
+ <thought>I have received the definition from the dictionary tool. I will now format the final answer according to the specified structure, including the citation.</thought>
+ <answer>
+ <grounded_text citation_index="1">Hypermedia is an extension of hypertext allowing graphics, audio, video, plain text and hyperlinks to intertwine to create a generally non-linear medium of information.</grounded_text>
+ <citations>
+ <citation index="1" chunk_id="dict-abc-123" type="dictionary_definition"></citation>
+ </citations>
+ <follow_up_questions>
+ <question>Can you give an example of a hypermedia system?</question>
+ <question>How does hypermedia differ from multimedia?</question>
+ <question>Who coined the term hypermedia?</question>
+ </follow_up_questions>
+ <loop_summary>Used dictionaryLookup tool to define 'hypermedia'.</loop_summary>
+ </answer>
+ </stage>
+ ```
+ - `// Agent.ts parses stage 6. Finds <answer>. Calls AnswerParser.`
+ - `// Agent.ts updates ChatBox UI (`\_history.push(...)`). Loop ends.`
+
+---
+
+## 5. Step-by-Step Guide: Creating a New Tool
+
+Let's use the example of creating a `CreateDashNoteTool`.
+
+### Step 1: Define Goal, Scope, Inputs, Outputs, Dash Interactions, Side Effects
+
+- **Goal:** Allow Agent to create a new text note (`DocumentType.TEXT` or equivalent) in Dash.
+- **Scope:** Creates a _simple_ note with title and text content. Does not handle complex formatting, linking (beyond default linking to ChatBox if handled by the creation function), or specific placement beyond a potential default offset.
+- **Inputs:** `noteTitle` (string, required), `noteContent` (string, required).
+- **Outputs (Observation):** Confirmation message with new note's Dash Document ID, or an error message.
+- **Dash Interactions:** Calls a function capable of creating Dash documents (e.g., `createDocInDash` passed via constructor).
+- **Side Effects:** A new Dash text document is created in the user's space and potentially linked to the ChatBox.
+
+### Step 2: Create the Tool Class File (Directory Structure)
+
+- Create file: `src/components/views/nodes/chatbot/agentsystem/tools/CreateDashNoteTool.ts`
+- Ensure it's within the `tools` subdirectory.
+
+### Step 3: Define Parameters (`parameterRules`) - Type Handling, Arrays
+
+- Use `as const` for the array to allow TypeScript to infer literal types, which aids `ParametersType`.
+- Define `noteTitle` and `noteContent` as required strings.
+
+```typescript
+import { Parameter } from '../types/tool_types';
+
+const createDashNoteToolParams = [
+ {
+ name: 'noteTitle',
+ type: 'string',
+ required: true,
+ description: 'The title for the new Dash note document. Cannot be empty.',
+ },
+ {
+ name: 'noteContent',
+ type: 'string',
+ required: true,
+ description: 'The text content for the new Dash note. Can be an empty string.', // Specify if empty content is allowed
+ },
+] as const; // Use 'as const' for precise typing
+
+// Infer the type for args object in execute
+type CreateDashNoteToolParamsType = typeof createDashNoteToolParams;
+```
+
+### Step 4: Define Tool Information (`ToolInfo`) - Crafting the _Crucial_ `description`
+
+- This object's `description` is key for the LLM.
+
+```typescript
+import { ToolInfo, ParametersType } from '../types/tool_types';
+
+// Assuming createDashNoteToolParams and CreateDashNoteToolParamsType are defined above
+
+const createDashNoteToolInfo: ToolInfo<CreateDashNoteToolParamsType> = {
+ name: 'createDashNote', // Must match registration key in Agent.ts
+ description:
+ 'Creates a *new*, simple text note document within the current Dash view. Requires a title and text content. The note will be linked to the ChatBox and placed nearby with default dimensions. Use this when the user asks to create a new note, save information, or write something down persistently in Dash.',
+ parameterRules: createDashNoteToolParams,
+ citationRules: 'This tool creates a document. The observation confirms success and provides the new document ID. No citation is typically needed in the final answer unless confirming the action.',
+};
+```
+
+### Step 5: Implement `execute` - Defensive Coding, Using Injected Functions, Error Handling Pattern
+
+- Implement the `execute` method within your class.
+- Wrap logic in `try...catch`.
+- Validate inputs defensively.
+- Check injected dependencies (`this._createDocInDash`).
+- Call the Dash function.
+- Handle the return value.
+- Format success or error `Observation`.
+
+```typescript
+import { BaseTool } from './BaseTool';
+import { Observation } from '../types/types';
+import { supportedDocTypes } from '../types/tool_types';
+import { parsedDoc } from '../chatboxcomponents/ChatBox'; // May need adjustment based on actual path
+import { Doc } from '../../../../../../fields/Doc'; // Adjust path as needed
+import { v4 as uuidv4 } from 'uuid';
+import { RTFCast } from '../../../../../../fields/Types'; // Adjust path as needed
+
+// Assuming createDashNoteToolParams, CreateDashNoteToolParamsType, createDashNoteToolInfo are defined above
+
+export class CreateDashNoteTool extends BaseTool<CreateDashNoteToolParamsType> {
+ // Dependency: Function to create a document in Dash
+ private _createDocInDash: (doc: parsedDoc) => Doc | undefined;
+
+ // Constructor to inject dependencies
+ constructor(createDocInDash: (doc: parsedDoc) => Doc | undefined) {
+ super(createDashNoteToolInfo);
+ if (typeof createDocInDash !== 'function') {
+ console.error('CreateDashNoteTool Error: createDocInDash function dependency not provided during instantiation!');
+ // Consider throwing an error or setting a flag to prevent execution
+ }
+ this._createDocInDash = createDocInDash;
+ }
+
+ async execute(args: ParametersType<CreateDashNoteToolParamsType>): Promise<Observation[]> {
+ const chunkId = uuidv4(); // Unique ID for this observation
+ const { noteTitle, noteContent } = args;
+
+ // --- Input Validation ---
+ if (typeof noteTitle !== 'string' || !noteTitle.trim()) {
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error">Invalid input: Note title must be a non-empty string.</chunk>` }];
+ }
+ if (typeof noteContent !== 'string') {
+ // Assuming empty content IS allowed based on description
+ // If not allowed, return error here.
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error">Invalid input: Note content must be a string.</chunk>` }];
+ }
+ if (!this._createDocInDash) {
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error">Tool Configuration Error: Document creation function not available.</chunk>` }];
+ }
+ // --- End Validation ---
+
+ try {
+ const trimmedTitle = noteTitle.trim();
+
+ // Prepare the document object for the creation function
+ const noteDoc: parsedDoc = {
+ doc_type: supportedDocTypes.note, // Use the correct type for a text note
+ title: trimmedTitle,
+ data: RTFCast(noteContent) as unknown as string, // Ensure data is correctly formatted if needed
+ // Example default properties:
+ _width: 300,
+ _layout_fitWidth: false,
+ _layout_autoHeight: true,
+ backgroundColor: '#FFFFE0', // Light yellow background
+ // Add x, y coordinates if desired, potentially relative to ChatBox if context is available
+ };
+
+ console.log(`CreateDashNoteTool: Attempting to create doc:`, { title: noteDoc.title, type: noteDoc.doc_type }); // Avoid logging full content
+
+ // Call the injected Dash function
+ const createdDoc = this._createDocInDash(noteDoc);
+
+ // Check the result
+ if (createdDoc && createdDoc.id) {
+ const successMessage = `Successfully created note titled "${trimmedTitle}" with ID: ${createdDoc.id}. It has been added to your current view.`;
+ console.log(`CreateDashNoteTool: Success - ${successMessage}`);
+ // Return observation confirming success
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="note_creation_status">${successMessage}</chunk>` }];
+ } else {
+ console.error('CreateDashNoteTool Error: _createDocInDash returned undefined or document without an ID.');
+ throw new Error('Dash document creation failed or did not return a valid document ID.');
+ }
+ } catch (error) {
+ console.error(`CreateDashNoteTool: Error creating note titled "${noteTitle.trim()}":`, error);
+ const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred during note creation.';
+ // Return observation indicating error
+ return [{ type: 'text', text: `<chunk chunk_id="${chunkId}" chunk_type="error">Error creating note: ${errorMessage}</chunk>` }];
+ }
+ }
+}
+```
+
+### Step 6: Format Output (`Observation[]`) - Chunk Structure, `chunk_type`, IDs
+
+- Ensure the `text` field within the returned `Observation` contains `<chunk chunk_id="..." chunk_type="...">...</chunk>`.
+- Use a specific `chunk_type` (e.g., `note_creation_status`, `error`).
+- Generate a unique `chunk_id` using `uuidv4()`.
+- The text inside the chunk should be informative for the LLM and potentially for debugging.
+
+### Step 7: Register Tool in `Agent.ts` - _This makes the tool available to the prompt_
+
+- Import your tool class at the top of `Agent.ts`:
+ ```typescript
+ import { CreateDashNoteTool } from '../tools/CreateDashNoteTool';
+ ```
+- In the `Agent` constructor, instantiate your tool within the `this.tools = { ... };` block. Ensure the key matches `ToolInfo.name` and pass any required dependencies (like the `createDocInDash` function).
+
+ ```typescript
+ constructor(
+ _vectorstore: Vectorstore,
+ summaries: () => string,
+ history: () => string,
+ csvData: () => { filename: string; id: string; text: string }[],
+ addLinkedUrlDoc: (url: string, id: string) => void,
+ createImage: (result: any, options: any) => void, // Use specific types if known
+ createDocInDashFunc: (doc: parsedDoc) => Doc | undefined, // Renamed for clarity
+ createCSVInDash: (url: string, title: string, id: string, data: string) => void
+ ) {
+ // ... existing initializations (OpenAI client, vectorstore, etc.) ...
+ this.vectorstore = _vectorstore;
+ this._summaries = summaries;
+ this._history = history;
+ this._csvData = csvData;
+
+ this.tools = {
+ calculate: new CalculateTool(),
+ rag: new RAGTool(this.vectorstore),
+ dataAnalysis: new DataAnalysisTool(csvData),
+ websiteInfoScraper: new WebsiteInfoScraperTool(addLinkedUrlDoc),
+ searchTool: new SearchTool(addLinkedUrlDoc),
+ noTool: new NoTool(),
+ imageCreationTool: new ImageCreationTool(createImage),
+ documentMetadata: new DocumentMetadataTool(this), // Pass ChatBox instance if needed by tool
+ // Register the new tool here:
+ createDashNote: new CreateDashNoteTool(createDocInDashFunc), // Pass the required function
+ };
+ // ... rest of constructor
+ }
+ ```
+
+- **Verify Dependencies:** Ensure that the `createDocInDashFunc` parameter (or however you name it) is actually being passed into the `Agent` constructor when it's instantiated (likely within `ChatBox.tsx`). Trace the dependency chain.
+
+### Step 8: Verify Prompt Integration (No Manual Editing Needed)
+
+- **No manual changes are needed in `prompts.ts`**. The `getReactPrompt` function dynamically builds the `<tools>` section from `this.tools`.
+- **Verify (Recommended):** Temporarily add `console.log(systemPrompt)` in `Agent.ts` right after `const systemPrompt = getReactPrompt(...)` within the `askAgent` method. Run a query. Examine the console output to confirm the system prompt includes your tool's `<title>` and `<description>` within the `<tools>` block. Remove the log afterward.
+
+### Step 9: Testing Your Tool - Strategies and What to Look For
+
+- **Functional Tests:** Use specific prompts like "Create a note called 'Ideas' with content 'Test 1'." Check the Dash UI for the note and the chat for the success message/ID.
+- **Edge Case Tests:** Test empty titles (should fail validation), empty content (should succeed if allowed), titles/content with special characters or excessive length.
+- **LLM Interaction Tests:** Use less direct prompts like "Save this thought: Remember to buy milk." Does the LLM correctly identify the need for your tool and extract/request the title and content?
+- **Failure Tests:** If possible, simulate failure in the dependency (`createDocInDash`) to ensure the `error` chunk is returned correctly.
+- **Console/Debugging:** Use `console.log` within `execute` and inspect `interMessages` in `Agent.ts` to trace the flow and identify issues.
+
+---
+
+## 6. Deep Dive: Advanced Concepts & Patterns
+
+### Handling Complex Data Types (Arrays, Objects) in Parameters/Observations
+
+- **Parameters:** For complex inputs, define the parameter `type` as `string` in `parameterRules`. In the `description`, instruct the LLM to provide a **valid JSON string**. Inside your `execute` method, use `JSON.parse()` within a `try...catch` block to parse this string. Handle potential parsing errors gracefully (return an `error` chunk).
+- **Observations:** To return structured data, `JSON.stringify` your object/array and embed this string _inside_ the `<chunk>` tag. Use a specific `chunk_type` (e.g., `json_data_analysis`). The LLM might need guidance (via prompt engineering) on how to interpret and use this JSON data effectively in its final response.
+
+### Binary Data Handling (e.g., Base64 in Chunks)
+
+- **Avoid large binary data in observations.** Context windows are limited.
+- **Preferred:** Save binary data server-side (e.g., using `DashUploadUtils` or similar) or reference existing Dash media docs. Return a **reference** (URL, Doc ID, file path accessible by Dash) within the `<chunk>`.
+- **If absolutely necessary:** For small images/data needed _directly_ by the LLM, Base64 encode it inside the chunk: `<chunk chunk_id="..." chunk_type="base64_image_png">BASE64_STRING</chunk>`.
+
+### Managing Long-Running Tasks (Beyond simple `await`)
+
+- The agent's `askAgent` loop `await`s `tool.execute()`. Tasks taking more than ~5-10 seconds degrade user experience. Very long tasks risk timeouts.
+- **Limitation:** The current architecture doesn't have built-in support for asynchronous background jobs with status polling.
+- **Possible (Complex) Workaround:**
+ 1. Tool `execute` initiates a long-running _external_ process (like the Python PDF chunker) or backend job.
+ 2. `execute` _immediately_ returns an `Observation` like `<chunk chunk_type="task_initiated" job_id="JOB123">Processing started. Use status check tool with ID JOB123.</chunk>`.
+ 3. Requires a _separate_ `StatusCheckTool` that takes a `job_id` and queries the external process/backend for status.
+ 4. This adds significant complexity to the agent's reasoning flow. Use only if absolutely necessary.
+
+### Tools Needing Dash Context (Passing `this` vs. specific functions)
+
+- **Specific Functions (Preferred):** Pass only necessary functions from `ChatBox`/Dash utilities. Promotes modularity and testability. Requires updating constructors if needs change.
+- **`ChatBox` Instance (`this`) (As in `DocumentMetadataTool`):** Provides broad access to `ChatBox` state (`Document`, `layoutDoc`, computed properties) and methods. Easier for tools with complex Dash interactions but increases coupling and makes testing harder.
+- **Decision:** Start with specific functions. Escalate to passing `this` only if the tool's requirements become extensive and unmanageable via individual function injection.
+
+### The Role of `chunk_id` and `chunk_type`
+
+- `chunk_id` (e.g., `uuidv4()`): **Traceability & Citation.** Uniquely identifies a piece of data returned by a tool. Allows the final `<answer>`'s `<citation>` tag to precisely reference the source observation via this ID. Essential for debugging and grounding.
+- `chunk_type`: **Semantic Meaning.** Tells the LLM _what kind_ of information the chunk contains (e.g., `url`, `calculation_result`, `error`, `note_creation_status`). Guides the LLM in processing the observation and formatting the final answer appropriately. Use consistent and descriptive type names.
+
+---
+
+## 7. Best Practices and Advanced Considerations
+
+- **Error Handling & Reporting:** Return errors in structured `<chunk chunk_type="error">...</chunk>` format. Include context in the message (e.g., "API call failed for URL: [url]", "Invalid value for parameter: [param_name]").
+- **Security:**
+ - **Input Sanitization:** **Crucial.** If tool inputs influence API calls, file paths, database queries, etc., validate and sanitize them rigorously. Do not trust LLM output implicitly.
+ - **API Keys:** Use server-side environment variables (`process.env`) for keys used in backend routes called by tools. Avoid exposing keys directly in client-side tool code if possible.
+ - **Output Filtering:** Be mindful of sensitive data. Don't leak PII or internal details in observations or error messages.
+- **Performance Optimization:** Keep `execute` logic efficient. Minimize blocking operations. Use asynchronous patterns correctly.
+- **Idempotency:** Design tools (especially those causing side effects like creation/modification) to be safe if run multiple times with the same input, if possible.
+- **Tool Granularity (SRP):** Aim for tools that do one thing well. Complex workflows can be achieved by the LLM chaining multiple focused tools.
+- **Context Window Management:** Write concise but clear tool `description`s. Keep `Observation` data relevant and succinct.
+- **User Experience:** Tool output (via observations) influences the final answer. Ensure returned data is clear and `citationRules` guide the LLM to produce understandable results.
+- **Maintainability:** Use clear code, comments for complex logic, TypeScript types, and follow project conventions.
+
+---
+
+## 8. Debugging Strategies
+
+1. **`console.log`:** Liberally use `console.log` inside your tool's `execute` method to inspect `args`, intermediate variables, API responses, and the `Observation[]` object just before returning.
+2. **Inspect `interMessages`:** Temporarily modify `Agent.ts` (e.g., in the `askAgent` `while` loop) to `console.log(JSON.stringify(this.interMessages, null, 2))` before each LLM call. This shows the exact XML context the LLM sees and its raw XML response. Pinpoint where the conversation deviates or breaks.
+3. **Test Standalone:** Create a simple test script (`.ts` file run with `ts-node` or similar). Import your tool. Create mock objects/functions for its dependencies (e.g., `const mockCreateDoc = (doc) => ({ id: 'mock-doc-123', ...doc });`). Instantiate your tool with mocks: `const tool = new YourTool(mockCreateDoc);`. Call `await tool.execute(testArgs);` and assert the output. This isolates tool logic.
+4. **Analyzing LLM Failures:** Use the `interMessages` log:
+ - **Wrong Tool Chosen:** LLM's `<thought>` selects the wrong tool, or uses `<action>noTool</action>` inappropriately. -> **Refine your tool's `description`** in `ToolInfo` for clarity and better differentiation.
+ - **Missing/Incorrect Parameters:** LLM fails to provide required parameters in `<inputs>`, or provides wrong values. -> **Refine parameter `description`s** in `parameterRules`. Check the `<action_input>` stage in the log.
+ - **Ignoring Observation/Bad Answer:** LLM gets the correct `<observation>` but generates a poor `<answer>` (ignores data, bad citation). -> Check `chunk_type`, data format inside the chunk, and **clarify `citationRules`**. Simplify observation data if needed.
+ - **XML Formatting Errors:** LLM returns malformed XML. -> This might require adjusting the system prompt's structure rules or adding more robust parsing/error handling in `Agent.ts`.
+
+---
+
+## 9. Example: `CreateDashNoteTool`
+
+The code provided in Step 5 serves as a practical example, demonstrating dependency injection, input validation, calling a Dash function, and formatting success/error observations within the required `<chunk>` structure. Ensure the dependency (`createDocInDashFunc`) is correctly passed during `Agent` instantiation in `ChatBox.tsx`.
+
+---
+
+## 10. Glossary of Key Terms
+
+- **Agent (`Agent.ts`):** The orchestrator class managing the LLM interaction loop and tool usage.
+- **Tool (`BaseTool.ts`):** A class extending `BaseTool` to provide specific functionality (API calls, Dash actions).
+- **LLM (Large Language Model):** The AI model providing reasoning and text generation (e.g., GPT-4o).
+- **ReAct Loop:** The core interaction pattern: Reason -> Act -> Observe.
+- **XML Structure:** The tag-based format (`<stage>`, `<thought>`, etc.) for LLM communication.
+- **`interMessages`:** The internal, complete conversational context sent to the LLM.
+- **`ToolInfo`:** Configuration object (`name`, `description`, `parameterRules`, `citationRules`) defining a tool. **Source of dynamic prompt content for the tool list.**
+- **`parameterRules`:** Array defining a tool's expected input parameters.
+- **`citationRules`:** Instructions for the LLM on citing a tool's output.
+- **`execute`:** The primary asynchronous method within a tool containing its core logic.
+- **`Observation`:** The structured object (`{ type: 'text', text: '<chunk>...' }`) returned by `execute`.
+- **`<chunk>`:** The required XML-like wrapper within an Observation's `text`, containing `chunk_id` and `chunk_type`.
+- **`chunk_type`:** Semantic identifier for the data type within a `<chunk>`.
+- **System Prompt (`getReactPrompt`):** The foundational instructions for the LLM, acting as a **template dynamically populated** with registered tool descriptions.
+- **Dash Functions:** Capabilities from the Dash environment (e.g., `createDocInDash`) injected into tools.
+- **Stateless Tool:** A tool whose output depends solely on current inputs, not past interactions.
+
+---
+
+## 11. Conclusion
+
+This guide provides a detailed framework for extending the Dash Agent with custom tools. By adhering to the `BaseTool` structure, understanding the agent's interaction flow, crafting clear `ToolInfo` descriptions, implementing robust `execute` methods, and correctly registering your tool in `Agent.ts`, you can build powerful integrations that leverage both AI and the unique capabilities of the Dash hypermedia environment. Remember that testing and careful consideration of dependencies, errors, and security are crucial for creating reliable tools.
diff --git a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts
deleted file mode 100644
index 754d230c8..000000000
--- a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { toLower } from 'lodash';
-import { Doc } from '../../../../../fields/Doc';
-import { Id } from '../../../../../fields/FieldSymbols';
-import { DocumentOptions } from '../../../../documents/Documents';
-import { parsedDoc } from '../chatboxcomponents/ChatBox';
-import { ParametersType, ToolInfo } from '../types/tool_types';
-import { Observation } from '../types/types';
-import { BaseTool } from './BaseTool';
-import { supportedDocTypes } from './CreateDocumentTool';
-
-const standardOptions = ['title', 'backgroundColor'];
-/**
- * Description of document options and data field for each type.
- */
-const documentTypesInfo: { [key in supportedDocTypes]: { options: string[]; dataDescription: string } } = {
- [supportedDocTypes.flashcard]: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'an array of two strings. the first string contains a question, and the second string contains an answer',
- },
- [supportedDocTypes.text]: {
- options: [...standardOptions, 'fontColor', 'text_align'],
- dataDescription: 'The text content of the document.',
- },
- [supportedDocTypes.html]: {
- options: [],
- dataDescription: 'The HTML-formatted text content of the document.',
- },
- [supportedDocTypes.equation]: {
- options: [...standardOptions, 'fontColor'],
- dataDescription: 'The equation content as a string.',
- },
- [supportedDocTypes.functionplot]: {
- options: [...standardOptions, 'function_definition'],
- dataDescription: 'The function definition(s) for plotting. Provide as a string or array of function definitions.',
- },
- [supportedDocTypes.dataviz]: {
- options: [...standardOptions, 'chartType'],
- dataDescription: 'A string of comma-separated values representing the CSV data.',
- },
- [supportedDocTypes.notetaking]: {
- options: standardOptions,
- dataDescription: 'The initial content or structure for note-taking.',
- },
- [supportedDocTypes.rtf]: {
- options: standardOptions,
- dataDescription: 'The rich text content in RTF format.',
- },
- [supportedDocTypes.image]: {
- options: standardOptions,
- dataDescription: 'The image content as an image file URL.',
- },
- [supportedDocTypes.pdf]: {
- options: standardOptions,
- dataDescription: 'the pdf content as a PDF file url.',
- },
- [supportedDocTypes.audio]: {
- options: standardOptions,
- dataDescription: 'The audio content as a file url.',
- },
- [supportedDocTypes.video]: {
- options: standardOptions,
- dataDescription: 'The video content as a file url.',
- },
- [supportedDocTypes.message]: {
- options: standardOptions,
- dataDescription: 'The message content of the document.',
- },
- [supportedDocTypes.diagram]: {
- options: ['title', 'backgroundColor'],
- dataDescription: 'diagram content as a text string in Mermaid format.',
- },
- [supportedDocTypes.script]: {
- options: ['title', 'backgroundColor'],
- dataDescription: 'The compilable JavaScript code. Use this for creating scripts.',
- },
-};
-
-const createAnyDocumentToolParams = [
- {
- name: 'document_type',
- type: 'string',
- description: `The type of the document to create. Supported types are: ${Object.values(supportedDocTypes).join(', ')}`,
- required: true,
- },
- {
- name: 'data',
- type: 'string',
- description: 'The content or data of the document. The exact format depends on the document type.',
- required: true,
- },
- {
- name: 'options',
- type: 'string',
- required: false,
- description: `A JSON string representing the document options. Available options depend on the document type. For example:
- ${Object.entries(documentTypesInfo).map( ([doc_type, info]) => `
-- For '${doc_type}' documents, options include: ${info.options.join(', ')}`)
- .join('\n')}`, // prettier-ignore
- },
-] as const;
-
-type CreateAnyDocumentToolParamsType = typeof createAnyDocumentToolParams;
-
-const createAnyDocToolInfo: ToolInfo<CreateAnyDocumentToolParamsType> = {
- name: 'createAnyDocument',
- description:
- `Creates any type of document with the provided options and data.
- Supported document types are: ${Object.values(supportedDocTypes).join(', ')}.
- dataviz is a csv table tool, so for CSVs, use dataviz. Here are the options for each type:
- <supported_document_types>` +
- Object.entries(documentTypesInfo)
- .map(
- ([doc_type, info]) =>
- `<document_type name="${doc_type}">
- <data_description>${info.dataDescription}</data_description>
- <options>` +
- info.options.map(option => `<option>${option}</option>`).join('\n') +
- `</options>
- </document_type>`
- )
- .join('\n') +
- `</supported_document_types>`,
- parameterRules: createAnyDocumentToolParams,
- citationRules: 'No citation needed.',
-};
-
-export class CreateAnyDocumentTool extends BaseTool<CreateAnyDocumentToolParamsType> {
- private _addLinkedDoc: (doc: parsedDoc) => Doc | undefined;
-
- constructor(addLinkedDoc: (doc: parsedDoc) => Doc | undefined) {
- super(createAnyDocToolInfo);
- this._addLinkedDoc = addLinkedDoc;
- }
-
- async execute(args: ParametersType<CreateAnyDocumentToolParamsType>): Promise<Observation[]> {
- try {
- const documentType = toLower(args.document_type) as unknown as supportedDocTypes;
- const info = documentTypesInfo[documentType];
-
- if (info === undefined) {
- throw new Error(`Unsupported document type: ${documentType}. Supported types are: ${Object.values(supportedDocTypes).join(', ')}.`);
- }
-
- if (!args.data) {
- throw new Error(`Data is required for ${documentType} documents. ${info.dataDescription}`);
- }
-
- const options: DocumentOptions = !args.options ? {} : JSON.parse(args.options);
-
- // Call the function to add the linked document (add default title that can be overriden if set in options)
- const doc = this._addLinkedDoc({ doc_type: documentType, data: args.data, title: `New ${documentType.charAt(0).toUpperCase() + documentType.slice(1)} Document`, ...options });
-
- return [{ type: 'text', text: `Created ${documentType} document with ID ${doc?.[Id]}.` }];
- } catch (error) {
- return [{ type: 'text', text: 'Error creating document: ' + (error as Error).message }];
- }
- }
-}
diff --git a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
index a9fb45b5a..a3d86287d 100644
--- a/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
+++ b/src/client/views/nodes/chatbot/tools/DocumentMetadataTool.ts
@@ -11,7 +11,6 @@ import { DocCast, StrCast } from '../../../../../fields/Types';
import { supportedDocTypes } from '../types/tool_types';
import { parsedDoc } from '../chatboxcomponents/ChatBox';
-
// Define the parameters for the DocumentMetadataTool
const parameterDefinitions: ReadonlyArray<Parameter> = [
{
@@ -61,7 +60,7 @@ const parameterDefinitions: ReadonlyArray<Parameter> = [
type: 'string',
required: false,
description: `The type of document to create. Required for "create" action. Options: ${Object.keys(supportedDocTypes).join(',')}`,
- }
+ },
] as const;
type DocumentMetadataToolParamsType = typeof parameterDefinitions;
@@ -105,14 +104,14 @@ IMPORTANT: Some fields have dependencies that must be handled for edits to work
- You can edit dependent fields in a single operation using the fieldEdits parameter
Example: To change document height, first disable auto-height:
-1. { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false }
-2. { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 300 }
+1. {... inputs: { action: "edit", documentId: "doc123", fieldName: "layout_autoHeight", fieldValue: false }}
+2. {... inputs: { action: "edit", documentId: "doc123", fieldName: "height", fieldValue: 300 }}
OR using multi-field edit (recommended for dependent fields):
-{ action: "edit", documentId: "doc123", fieldEdits: [
+{... inputs: { action: "edit", documentId: "doc123", fieldEdits: [
{ fieldName: "layout_autoHeight", fieldValue: false },
{ fieldName: "height", fieldValue: 300 }
-]}`;
+]}}`;
// Extensive usage guidelines for the tool
const citationRules = `USAGE GUIDELINES:
@@ -132,7 +131,7 @@ To CREATE a new document:
- title: The title of the document to create
- data: The content data for the document (text content, URL, etc.)
- doc_type: The type of document to create (text, web, image, etc.)
-- Example: { action: "create", title: "My Notes", data: "This is the content", doc_type: "text" }
+- Example: {...inputs: { action: "create", title: "My Notes", data: "This is the content", doc_type: "text" }}
- After creation, you can edit the document with more specific properties
To EDIT document metadata:
@@ -147,7 +146,7 @@ To EDIT document metadata:
SPECIAL FIELD HANDLING:
- Text fields: When editing the 'text' field, provide simple plain text
- Example: { action: "edit", documentId: "doc123", fieldName: "text", fieldValue: "Hello world" }
+ Example: {...inputs: { action: "edit", documentId: "doc123", fieldName: "text", fieldValue: "Hello world" }}
The tool will automatically convert your text to the proper RichTextField format
- Width/Height: Set layout_autoHeight/layout_autoWidth to false before editing
@@ -165,10 +164,10 @@ HANDLING DEPENDENT FIELDS:
- When editing some fields, you may need to update related dependent fields
- For example, when changing "height", you should also set "layout_autoHeight" to false
- Use the fieldEdits parameter to update dependent fields in a single operation (recommended):
- { action: "edit", documentId: "doc123", fieldEdits: [
+ {...inputs: { action: "edit", documentId: "doc123", fieldEdits: [
{ fieldName: "layout_autoHeight", fieldValue: false },
{ fieldName: "height", fieldValue: 300 }
- ]}
+]}}
- Always check for dependent fields that might affect your edits, such as:
- height → layout_autoHeight (set to false to allow manual height)
- width → layout_autoWidth (set to false to allow manual width)
@@ -200,8 +199,8 @@ Examples:
{ action: "edit", documentId: "doc123", fieldEdits: [
{ fieldName: "layout_autoHeight", fieldValue: false },
{ fieldName: "height", fieldValue: 200 }
- ]}`;
-
+ ]}
+- IMPORTANT: MULTI STEP WORKFLOWS ARE NOT ONLY ALLOWED BUT ENCOURAGED. TAKE THINGS 1 STEP AT A TIME.`;
const documentMetadataToolInfo: ToolInfo<DocumentMetadataToolParamsType> = {
name: 'documentMetadata',
description: toolDescription,
@@ -227,7 +226,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
constructor(chatBox: any) {
super(documentMetadataToolInfo);
this.chatBox = chatBox;
-
+
// Store a direct reference to the ChatBox document
if (chatBox && chatBox.Document) {
this.chatBoxDocument = chatBox.Document;
@@ -246,7 +245,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
} else {
console.warn('DocumentMetadataTool initialized without valid ChatBox Document reference');
}
-
+
this.initializeFieldMetadata();
}
@@ -273,12 +272,12 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
// Check if fieldInfo has description property (it's likely a FInfo instance)
if (fieldInfo && typeof fieldInfo === 'object' && 'description' in fieldInfo) {
fieldData.description = fieldInfo.description;
-
+
// Extract field type if available
if ('fieldType' in fieldInfo) {
fieldData.type = fieldInfo.fieldType;
}
-
+
// Extract possible values if available
if ('values' in fieldInfo && Array.isArray(fieldInfo.values)) {
fieldData.possibleValues = fieldInfo.values;
@@ -303,30 +302,30 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
// Use the LinkManager approach which is proven to work in ChatBox
if (this.chatBoxDocument) {
console.log('Finding documents linked to ChatBox document with ID:', this.chatBoxDocument.id);
-
+
// Get directly linked documents via LinkManager
const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.chatBoxDocument)
.map(d => DocCast(LinkManager.getOppositeAnchor(d, this.chatBoxDocument!)))
.map(d => DocCast(d?.annotationOn, d))
.filter(d => d);
-
+
console.log(`Found ${linkedDocs.length} linked documents via LinkManager`);
-
+
// Process the linked documents
linkedDocs.forEach((doc: Doc) => {
if (doc) {
this.processDocument(doc);
}
});
-
+
// Include the ChatBox document itself
this.processDocument(this.chatBoxDocument);
-
+
// If we have access to the Document's parent, try to find sibling documents
if (this.chatBoxDocument.parent) {
const parent = this.chatBoxDocument.parent;
console.log('Found parent document, checking for siblings');
-
+
// Check if parent is a Doc type and has a childDocs function
if (parent && typeof parent === 'object' && 'childDocs' in parent && typeof parent.childDocs === 'function') {
try {
@@ -356,7 +355,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
}
});
}
-
+
// Process the ChatBox document if available
if (this.chatBox.Document) {
this.processDocument(this.chatBox.Document);
@@ -366,11 +365,11 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
}
console.log(`DocumentMetadataTool found ${this.documentsById.size} total documents`);
-
+
// If we didn't find any documents, try a fallback method
if (this.documentsById.size === 0 && this.chatBox) {
console.log('No documents found, trying fallback method');
-
+
// Try to access any field that might contain documents
if (this.chatBox.props && this.chatBox.props.documents) {
const documents = this.chatBox.props.documents;
@@ -396,17 +395,17 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
private processDocument(doc: Doc) {
// Ensure document has a persistent ID
const docId = this.ensureDocumentId(doc);
-
+
// Only add if we haven't already processed this document
if (!this.documentsById.has(docId)) {
this.documentsById.set(docId, doc);
-
+
// Get layout doc (the document itself or its layout)
const layoutDoc = Doc.Layout(doc);
if (layoutDoc) {
this.layoutDocsById.set(docId, layoutDoc);
}
-
+
// Get data doc
const dataDoc = doc[DocData];
if (dataDoc) {
@@ -422,15 +421,15 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
*/
private ensureDocumentId(doc: Doc): string {
let docId: string | undefined;
-
+
// First try to get the ID from our custom field
if (doc[this.DOCUMENT_ID_FIELD]) {
docId = String(doc[this.DOCUMENT_ID_FIELD]);
return docId;
}
-
+
// Try different ways to get a document ID
-
+
// 1. Try the direct id property if it exists
if (doc.id && typeof doc.id === 'string') {
docId = doc.id;
@@ -448,14 +447,14 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
docId = uuidv4();
console.log(`Generated new UUID for document with title: ${doc.title || 'Untitled'}`);
}
-
+
// Store the ID in the document's metadata so it persists
try {
doc[this.DOCUMENT_ID_FIELD] = docId;
} catch (e) {
console.warn(`Could not assign ID to document property`, e);
}
-
+
return docId;
}
@@ -472,7 +471,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
const layoutDoc = this.layoutDocsById.get(docId);
const dataDoc = this.dataDocsById.get(docId);
-
+
const metadata: Record<string, any> = {
id: docId,
title: doc.title || '',
@@ -488,7 +487,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
Object.keys(this.fieldMetadata).forEach(fieldName => {
const fieldDef = this.fieldMetadata[fieldName];
const strippedName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName;
-
+
// Check if field exists on layout document
let layoutValue = undefined;
if (layoutDoc) {
@@ -499,7 +498,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
metadata.fieldLocationMap[strippedName] = 'layout';
}
}
-
+
// Check if field exists on data document
let dataValue = undefined;
if (dataDoc) {
@@ -512,12 +511,12 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
}
}
}
-
+
// For fields with stripped names (without leading underscore),
// also check if they exist on documents without the underscore
if (fieldName.startsWith('_')) {
const nonUnderscoreFieldName = fieldName.substring(1);
-
+
if (layoutDoc) {
const nonUnderscoreLayoutValue = layoutDoc[nonUnderscoreFieldName];
if (nonUnderscoreLayoutValue !== undefined) {
@@ -525,7 +524,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
metadata.fieldLocationMap[nonUnderscoreFieldName] = 'layout';
}
}
-
+
if (dataDoc) {
const nonUnderscoreDataValue = dataDoc[nonUnderscoreFieldName];
if (nonUnderscoreDataValue !== undefined) {
@@ -544,7 +543,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
metadata.fields.layout.width = metadata.fields.layout._width;
metadata.fieldLocationMap.width = 'layout';
}
-
+
if (metadata.fields.layout._height !== undefined && metadata.fields.layout.height === undefined) {
metadata.fields.layout.height = metadata.fields.layout._height;
metadata.fieldLocationMap.height = 'layout';
@@ -560,18 +559,22 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
* @param fieldValue The new value for the field (string, number, or boolean)
* @returns Object with success status, message, and additional information
*/
- private editDocumentField(docId: string, fieldName: string, fieldValue: string | number | boolean): {
- success: boolean;
- message: string;
- fieldName?: string;
- originalFieldName?: string;
+ private editDocumentField(
+ docId: string,
+ fieldName: string,
+ fieldValue: string | number | boolean
+ ): {
+ success: boolean;
+ message: string;
+ fieldName?: string;
+ originalFieldName?: string;
newValue?: any;
warning?: string;
} {
// Normalize field name (handle with/without underscore)
let normalizedFieldName = fieldName.startsWith('_') ? fieldName : fieldName;
const strippedFieldName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName;
-
+
// Handle common field name aliases (width → _width, height → _height)
// Many document fields use '_' prefix for layout properties
if (fieldName === 'width') {
@@ -579,36 +582,36 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
} else if (fieldName === 'height') {
normalizedFieldName = '_height';
}
-
+
// Get the documents
const doc = this.documentsById.get(docId);
if (!doc) {
return { success: false, message: `Document with ID ${docId} not found` };
}
-
+
const layoutDoc = this.layoutDocsById.get(docId);
const dataDoc = this.dataDocsById.get(docId);
-
+
if (!layoutDoc && !dataDoc) {
return { success: false, message: `Could not find layout or data document for document with ID ${docId}` };
}
-
+
try {
// Convert the field value to the appropriate type based on field metadata
const convertedValue = this.convertFieldValue(normalizedFieldName, fieldValue);
-
+
let targetDoc: Doc | undefined;
let targetLocation: string;
-
+
// First, check if field exists on layout document using Doc.Get
if (layoutDoc) {
const fieldExistsOnLayout = Doc.Get(layoutDoc, normalizedFieldName, true) !== undefined;
-
+
// If it exists on layout document, update it there
if (fieldExistsOnLayout) {
targetDoc = layoutDoc;
targetLocation = 'layout';
- }
+ }
// If it has an underscore prefix, it's likely a layout property even if not yet set
else if (normalizedFieldName.startsWith('_')) {
targetDoc = layoutDoc;
@@ -618,13 +621,13 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
else if (dataDoc) {
targetDoc = dataDoc;
targetLocation = 'data';
- }
+ }
// If no data document available, default to layout
else {
targetDoc = layoutDoc;
targetLocation = 'layout';
}
- }
+ }
// If no layout document, use data document
else if (dataDoc) {
targetDoc = dataDoc;
@@ -632,26 +635,26 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
} else {
return { success: false, message: `No valid document found for editing` };
}
-
+
if (!targetDoc) {
return { success: false, message: `Target document not available` };
}
-
+
// Set the field value on the target document
targetDoc[normalizedFieldName] = convertedValue;
-
- return {
- success: true,
+
+ return {
+ success: true,
message: `Successfully updated field '${normalizedFieldName}' on ${targetLocation} document (ID: ${docId})`,
fieldName: normalizedFieldName,
originalFieldName: fieldName,
- newValue: convertedValue
+ newValue: convertedValue,
};
} catch (error) {
console.error('Error editing document field:', error);
- return {
- success: false,
- message: `Error updating field: ${error instanceof Error ? error.message : String(error)}`
+ return {
+ success: false,
+ message: `Error updating field: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
@@ -667,7 +670,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
if (typeof fieldValue === 'number' || typeof fieldValue === 'boolean') {
return fieldValue;
}
-
+
// If fieldValue is a string "true" or "false", convert to boolean
if (typeof fieldValue === 'string') {
if (fieldValue.toLowerCase() === 'true') {
@@ -677,12 +680,12 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
return false;
}
}
-
+
// If fieldValue is not a string (and not a number or boolean), convert it to string
if (typeof fieldValue !== 'string') {
fieldValue = String(fieldValue);
}
-
+
// Special handling for text field - convert to proper RichTextField format
if (fieldName === 'text') {
try {
@@ -710,28 +713,28 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
return JSON.stringify(rtf);
}
}
-
+
// Get field metadata
const normalizedFieldName = fieldName.startsWith('_') ? fieldName : `_${fieldName}`;
const strippedFieldName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName;
-
+
// Check both versions of the field name in metadata
const fieldMeta = this.fieldMetadata[normalizedFieldName] || this.fieldMetadata[strippedFieldName];
-
+
// Special handling for width and height without metadata
if (!fieldMeta && (fieldName === '_width' || fieldName === '_height' || fieldName === 'width' || fieldName === 'height')) {
const num = Number(fieldValue);
return isNaN(num) ? fieldValue : num;
}
-
+
if (!fieldMeta) {
// If no metadata found, just return the string value
return fieldValue;
}
-
+
// Convert based on field type
const fieldType = fieldMeta.type;
-
+
if (fieldType === 'boolean') {
// Convert to boolean
return fieldValue.toLowerCase() === 'true';
@@ -761,7 +764,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
return fieldValue;
}
}
-
+
// Default to string
return fieldValue;
}
@@ -775,7 +778,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
if (value === undefined || value === null) {
return null;
}
-
+
// Handle Doc objects
if (value instanceof Doc) {
return {
@@ -785,7 +788,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
docType: value.type || '',
};
}
-
+
// Handle RichTextField (try to extract plain text)
if (typeof value === 'string' && value.includes('"type":"doc"') && value.includes('"content":')) {
try {
@@ -802,9 +805,9 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
node.content.forEach((child: any) => extractText(child));
}
};
-
+
extractText(rtfObj.doc);
-
+
// If we successfully extracted text, show it, but also preserve the original value
if (plainText) {
return {
@@ -819,14 +822,14 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
// If parsing fails, just treat as a regular string
}
}
-
+
// Handle arrays and complex objects
if (typeof value === 'object') {
// If the object has a toString method, use it
if (value.toString && value.toString !== Object.prototype.toString) {
return value.toString();
}
-
+
try {
// Try to convert to JSON string
return JSON.stringify(value);
@@ -834,7 +837,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
return '[Complex Object]';
}
}
-
+
// Return primitive values as is
return value;
}
@@ -856,7 +859,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
list: [],
date: [],
enumeration: [],
- other: []
+ other: [],
},
fieldNameMappings: {},
commonFields: {
@@ -865,19 +868,19 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
size: [],
content: [],
behavior: [],
- layout: []
- }
+ layout: [],
+ },
};
// Process each field in the metadata
Object.entries(this.fieldMetadata).forEach(([fieldName, fieldInfo]) => {
const strippedName = fieldName.startsWith('_') ? fieldName.substring(1) : fieldName;
-
+
// Add to fieldNameMappings
if (fieldName.startsWith('_')) {
result.fieldNameMappings[strippedName] = fieldName;
}
-
+
// Create structured field metadata
const fieldData: Record<string, any> = {
name: fieldName,
@@ -886,10 +889,10 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
type: fieldInfo.fieldType || 'unknown',
possibleValues: fieldInfo.values || [],
};
-
+
// Add field to fields collection
result.fields[fieldName] = fieldData;
-
+
// Categorize by field type
const type = fieldInfo.fieldType?.toLowerCase() || 'unknown';
if (type === 'string') {
@@ -909,7 +912,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
} else {
result.fieldsByType.other.push(fieldName);
}
-
+
// Categorize by field purpose
if (fieldName.includes('width') || fieldName.includes('height') || fieldName.includes('size')) {
result.commonFields.size.push(fieldName);
@@ -925,23 +928,23 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
result.commonFields.layout.push(fieldName);
}
});
-
+
// Add special section for auto-sizing related fields
result.autoSizingFields = {
height: {
autoHeightField: '_layout_autoHeight',
heightField: '_height',
displayName: 'height',
- usage: 'To manually set height, first set layout_autoHeight to false'
+ usage: 'To manually set height, first set layout_autoHeight to false',
},
width: {
autoWidthField: '_layout_autoWidth',
widthField: '_width',
displayName: 'width',
- usage: 'To manually set width, first set layout_autoWidth to false'
- }
+ usage: 'To manually set width, first set layout_autoWidth to false',
+ },
};
-
+
// Add special section for text field format
result.specialFields = {
text: {
@@ -949,10 +952,10 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
description: 'Document text content',
format: 'RichTextField',
note: 'When setting text, provide plain text - it will be automatically converted to the correct format',
- example: 'For setting: "Hello world" (plain text); For getting: Will be converted to plaintext for display'
- }
+ example: 'For setting: "Hello world" (plain text); For getting: Will be converted to plaintext for display',
+ },
};
-
+
return result;
}
@@ -963,121 +966,129 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
*/
async execute(args: ParametersType<DocumentMetadataToolParamsType>): Promise<Observation[]> {
console.log('DocumentMetadataTool: Executing with args:', args);
-
+
// Find all documents in the Freeform view
this.findDocumentsInFreeformView();
-
+
try {
// Validate required input parameters based on action
if (!this.inputValidator(args)) {
- return [{
- type: 'text',
- text: `Error: Invalid or missing parameters for action "${args.action}". ${this.getParameterRequirementsByAction(String(args.action))}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error: Invalid or missing parameters for action "${args.action}". ${this.getParameterRequirementsByAction(String(args.action))}`,
+ },
+ ];
}
-
+
// Ensure the action is valid and convert to string
const action = String(args.action);
if (!['get', 'edit', 'list', 'getFieldOptions', 'create'].includes(action)) {
- return [{
- type: 'text',
- text: 'Error: Invalid action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Invalid action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".',
+ },
+ ];
}
// Safely convert documentId to string or undefined
const documentId = args.documentId ? String(args.documentId) : undefined;
-
+
// Perform the specified action
switch (action) {
case 'get': {
// Get metadata for a specific document or all documents
const result = this.getDocumentMetadata(documentId);
console.log('DocumentMetadataTool: Get metadata result:', result);
- return [{
- type: 'text',
- text: `Document metadata ${documentId ? 'for document ' + documentId : ''} retrieved successfully:\n${JSON.stringify(result, null, 2)}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Document metadata ${documentId ? 'for document ' + documentId : ''} retrieved successfully:\n${JSON.stringify(result, null, 2)}`,
+ },
+ ];
}
-
+
case 'edit': {
// Edit a specific field on a document
if (!documentId) {
- return [{
- type: 'text',
- text: 'Error: Document ID is required for edit actions.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Document ID is required for edit actions.',
+ },
+ ];
}
-
+
// Ensure document exists
if (!this.documentsById.has(documentId)) {
- return [{
- type: 'text',
- text: `Error: Document with ID ${documentId} not found.`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error: Document with ID ${documentId} not found.`,
+ },
+ ];
}
-
+
// Check if we're doing a multi-field edit or a single field edit
if (args.fieldEdits) {
try {
// Parse fieldEdits array
const edits = JSON.parse(String(args.fieldEdits));
if (!Array.isArray(edits) || edits.length === 0) {
- return [{
- type: 'text',
- text: 'Error: fieldEdits must be a non-empty array of field edits.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: fieldEdits must be a non-empty array of field edits.',
+ },
+ ];
}
-
+
// Track results for all edits
- const results: {
- success: boolean;
- message: string;
- fieldName?: string;
- originalFieldName?: string;
+ const results: {
+ success: boolean;
+ message: string;
+ fieldName?: string;
+ originalFieldName?: string;
newValue?: any;
warning?: string;
}[] = [];
-
+
let allSuccessful = true;
-
+
// Process each edit
for (const edit of edits) {
// Get fieldValue in its original form
let fieldValue = edit.fieldValue;
-
+
// Only convert to string if it's neither boolean nor number
if (typeof fieldValue !== 'boolean' && typeof fieldValue !== 'number') {
fieldValue = String(fieldValue);
}
-
+
const fieldName = String(edit.fieldName);
-
+
// Edit the field
- const result = this.editDocumentField(
- documentId,
- fieldName,
- fieldValue
- );
-
+ const result = this.editDocumentField(documentId, fieldName, fieldValue);
+
console.log(`DocumentMetadataTool: Edit field result for ${fieldName}:`, result);
-
+
// Add to results
results.push(result);
-
+
// Update success status
if (!result.success) {
allSuccessful = false;
}
}
-
+
// Format response based on results
let responseText = '';
if (allSuccessful) {
responseText = `Successfully edited ${results.length} fields on document ${documentId}:\n`;
results.forEach(result => {
responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`;
-
+
// Add any warnings
if (result.warning) {
responseText += ` Warning: ${result.warning}\n`;
@@ -1088,7 +1099,7 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
results.forEach(result => {
if (result.success) {
responseText += `- Field '${result.originalFieldName}': updated to ${JSON.stringify(result.newValue)}\n`;
-
+
// Add any warnings
if (result.warning) {
responseText += ` Warning: ${result.warning}\n`;
@@ -1098,120 +1109,134 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
}
});
}
-
+
// Get the updated metadata to return
const updatedMetadata = this.getDocumentMetadata(documentId);
-
- return [{
- type: 'text',
- text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`
- }];
+
+ return [
+ {
+ type: 'text',
+ text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`,
+ },
+ ];
} catch (error) {
- return [{
- type: 'text',
- text: `Error processing fieldEdits: ${error instanceof Error ? error.message : String(error)}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error processing fieldEdits: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
}
} else {
// Single field edit (original behavior)
if (!args.fieldName) {
- return [{
- type: 'text',
- text: 'Error: Field name and field value are required for edit actions.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Field name and field value are required for edit actions.',
+ },
+ ];
}
-
+
// Get fieldValue in its original form - we'll handle conversion in editDocumentField
let fieldValue = args.fieldValue;
-
+
// Only convert to string if it's neither boolean nor number
if (typeof fieldValue !== 'boolean' && typeof fieldValue !== 'number') {
fieldValue = String(fieldValue);
}
-
+
const fieldName = String(args.fieldName);
-
+
// Edit the field
- const result = this.editDocumentField(
- documentId,
- fieldName,
- fieldValue
- );
-
+ const result = this.editDocumentField(documentId, fieldName, fieldValue);
+
console.log('DocumentMetadataTool: Edit field result:', result);
-
+
if (!result.success) {
return [{ type: 'text', text: result.message }];
}
-
+
// Include warning if present
let responseText = result.message;
if (result.warning) {
responseText += `\n\n${result.warning}`;
}
-
+
// Get the updated metadata to return
const updatedMetadata = this.getDocumentMetadata(documentId);
-
- return [{
- type: 'text',
- text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`
- }];
+
+ return [
+ {
+ type: 'text',
+ text: `${responseText}\nUpdated metadata:\n${JSON.stringify(updatedMetadata, null, 2)}`,
+ },
+ ];
}
}
-
+
case 'list': {
// List all available documents in simple format
const docs = Array.from(this.documentsById.entries()).map(([id, doc]) => ({
id,
title: doc.title || 'Untitled Document',
- type: doc.type || 'Unknown Type'
+ type: doc.type || 'Unknown Type',
}));
-
+
if (docs.length === 0) {
- return [{
- type: 'text',
- text: 'No documents found in the current view.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'No documents found in the current view.',
+ },
+ ];
}
-
- return [{
- type: 'text',
- text: `Found ${docs.length} document(s) in the current view:\n${JSON.stringify(docs, null, 2)}`
- }];
+
+ return [
+ {
+ type: 'text',
+ text: `Found ${docs.length} document(s) in the current view:\n${JSON.stringify(docs, null, 2)}`,
+ },
+ ];
}
-
+
case 'getFieldOptions': {
// Get all available field options with metadata
const fieldOptions = this.getAllFieldMetadata();
-
- return [{
- type: 'text',
- text: `Document field options retrieved successfully.\nThis information should be consulted before editing document fields to understand available options and dependencies:\n${JSON.stringify(fieldOptions, null, 2)}`
- }];
+
+ return [
+ {
+ type: 'text',
+ text: `Document field options retrieved successfully.\nThis information should be consulted before editing document fields to understand available options and dependencies:\n${JSON.stringify(fieldOptions, null, 2)}`,
+ },
+ ];
}
-
+
case 'create': {
// Create a new document
if (!args.title || !args.data || !args.doc_type) {
- return [{
- type: 'text',
- text: 'Error: Title, data, and doc_type are required for create action.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Title, data, and doc_type are required for create action.',
+ },
+ ];
}
-
+
const docType = String(args.doc_type);
const title = String(args.title);
const data = String(args.data);
-
+
// Validate doc_type
if (!this.isValidDocType(docType)) {
- return [{
- type: 'text',
- text: `Error: Invalid doc_type. Valid options are: ${Object.keys(supportedDocTypes).join(',')}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error: Invalid doc_type. Valid options are: ${Object.keys(supportedDocTypes).join(',')}`,
+ },
+ ];
}
-
+
try {
// Create simple document with just title and data
const simpleDoc: parsedDoc = {
@@ -1223,35 +1248,40 @@ export class DocumentMetadataTool extends BaseTool<DocumentMetadataToolParamsTyp
_width: 300,
_height: 300,
_layout_fitWidth: false,
- _layout_autoHeight: true
+ _layout_autoHeight: true,
};
-
+
// Use the chatBox's createDocInDash method to create and link the document
if (!this.chatBox || !this.chatBox.createDocInDash) {
- return [{
- type: 'text',
- text: 'Error: Could not access document creation functionality.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Could not access document creation functionality.',
+ },
+ ];
}
-
+
const createdDoc = this.chatBox.createDocInDash(simpleDoc);
-
+
if (!createdDoc) {
- return [{
- type: 'text',
- text: 'Error: Failed to create document.'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Failed to create document.',
+ },
+ ];
}
-
+
// Update our local document maps with the new document
this.processDocument(createdDoc);
-
+
// Get the created document's metadata
const createdMetadata = this.getDocumentMetadata(createdDoc.id);
-
- return [{
- type: 'text',
- text: `Document created successfully.
+
+ return [
+ {
+ type: 'text',
+ text: `Document created successfully.
Document ID: ${createdDoc.id}
Type: ${docType}
Title: "${title}"
@@ -1266,36 +1296,43 @@ Next steps:
4. For text documents, you can edit the content with: { action: "edit", documentId: "${createdDoc.id}", fieldName: "text", fieldValue: "New content" }
Full metadata for the created document:
-${JSON.stringify(createdMetadata, null, 2)}`
- }];
+${JSON.stringify(createdMetadata, null, 2)}`,
+ },
+ ];
} catch (error) {
- return [{
- type: 'text',
- text: `Error creating document: ${error instanceof Error ? error.message : String(error)}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error creating document: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
}
}
-
+
default:
- return [{
- type: 'text',
- text: 'Error: Unknown action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".'
- }];
+ return [
+ {
+ type: 'text',
+ text: 'Error: Unknown action. Valid actions are "get", "edit", "list", "getFieldOptions", or "create".',
+ },
+ ];
}
} catch (error) {
console.error('DocumentMetadataTool execution error:', error);
- return [{
- type: 'text',
- text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}`
- }];
+ return [
+ {
+ type: 'text',
+ text: `Error executing DocumentMetadataTool: ${error instanceof Error ? error.message : String(error)}`,
+ },
+ ];
}
}
/**
* Validates the input parameters for the DocumentMetadataTool
- * This custom validator allows numbers and booleans to be passed for fieldValue
+ * This custom validator allows numbers and booleans to be passed for fieldValue
* while maintaining compatibility with the standard validation
- *
+ *
* @param params The parameters to validate
* @returns True if the parameters are valid, false otherwise
*/
@@ -1304,12 +1341,12 @@ ${JSON.stringify(createdMetadata, null, 2)}`
if (params.action === undefined) {
return false;
}
-
+
// For create action, validate required parameters
if (params.action === 'create') {
return !!(params.title && params.data && params.doc_type);
}
-
+
// For edit action, validate either single field edit or multiple field edits
if (params.action === 'edit') {
// If fieldEdits is provided, it must be valid and we'll ignore fieldName/fieldValue
@@ -1317,13 +1354,13 @@ ${JSON.stringify(createdMetadata, null, 2)}`
try {
// Parse fieldEdits and validate its structure
const edits = JSON.parse(String(params.fieldEdits));
-
+
// Ensure it's an array
if (!Array.isArray(edits)) {
console.log('fieldEdits is not an array');
return false;
}
-
+
// Ensure each item has fieldName and fieldValue
for (const edit of edits) {
if (!edit.fieldName) {
@@ -1335,7 +1372,7 @@ ${JSON.stringify(createdMetadata, null, 2)}`
return false;
}
}
-
+
// Everything looks good with fieldEdits
return !!params.documentId; // Just ensure documentId is provided
} catch (error) {
@@ -1349,22 +1386,22 @@ ${JSON.stringify(createdMetadata, null, 2)}`
}
}
}
-
+
// For get action with documentId, documentId is required
if (params.action === 'get' && params.documentId === '') {
return false;
}
-
+
// getFieldOptions action doesn't require any additional parameters
if (params.action === 'getFieldOptions') {
return true;
}
-
+
// list action doesn't require any additional parameters
if (params.action === 'list') {
return true;
}
-
+
// Allow for numeric or boolean fieldValue even though the type is defined as string
if (params.fieldValue !== undefined) {
if (typeof params.fieldValue === 'number') {
@@ -1372,14 +1409,14 @@ ${JSON.stringify(createdMetadata, null, 2)}`
// We'll convert it later, so don't fail validation
return true;
}
-
+
if (typeof params.fieldValue === 'boolean') {
console.log('Boolean fieldValue detected, will be converted appropriately');
// We'll handle boolean conversion in the execute method
return true;
}
}
-
+
return true;
}
@@ -1424,11 +1461,11 @@ ${JSON.stringify(createdMetadata, null, 2)}`
for (const docId of this.documentsById.keys()) {
documentsMetadata[docId] = this.extractDocumentMetadata(docId);
}
-
+
return {
documentCount: this.documentsById.size,
documents: documentsMetadata,
- fieldDefinitions: this.fieldMetadata
+ fieldDefinitions: this.fieldMetadata,
};
}
}
@@ -1441,4 +1478,4 @@ ${JSON.stringify(createdMetadata, null, 2)}`
private isValidDocType(docType: string): boolean {
return Object.values(supportedDocTypes).includes(docType as supportedDocTypes);
}
-} \ No newline at end of file
+}
diff --git a/src/server/chunker/pdf_chunker.py b/src/server/chunker/pdf_chunker.py
index 9d4cfb1b2..feb437f1f 100644
--- a/src/server/chunker/pdf_chunker.py
+++ b/src/server/chunker/pdf_chunker.py
@@ -602,12 +602,11 @@ class PDFChunker:
try:
response = self.client.messages.create(
- model='claude-3-5-sonnet-20240620',
+ model='claude-3-7-sonnet-20250219',
system=prompt,
max_tokens=400 * len(images), # Increased token limit for more detailed summaries
messages=messages,
temperature=0,
- extra_headers={"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15"}
)
# Parse the response
@@ -767,7 +766,7 @@ class Document:
client = OpenAI() # Initialize OpenAI client for text generation
completion = client.chat.completions.create(
- model="gpt-3.5-turbo", # Specify the language model
+ model="gpt-4o", # Specify the language model
messages=[
{"role": "system",
"content": "You are an AI assistant tasked with summarizing a document. You are provided with important chunks from the document and provide a summary, as best you can, of what the document will contain overall. Be concise and brief with your response."},