In the previous chapter, Dynamic Prompt Engineering, we taught the AI how and when to use our tool by giving it a smart instruction manual.
Now, we need to actually do the work. In this chapter, we will build the engine of our tool: the call() method. This is where we fetch data, clean it up, and serve it to the AI.
Imagine our task database is like a giant warehouse. It contains:
If we just grabbed everything from the warehouse and dumped it on the AI's desk, the AI would be confused. It might say, "Why is Task B blocked? The blocker is already done!" or "What is this weird system metadata file?"
We need to act like a helpful Librarian. When a user asks for a list of books (tasks):
This logic lives inside the call() function of our TaskListTool.
To implement this "Librarian," we need to master three small logical steps.
Sometimes our database stores tasks that are purely for the system (internal metadata). We don't want the AI to see these or try to edit them.
The Rule: If a task has metadata._internal set to true, throw it out.
This is the most critical logic.
Finally, we must shape the data to fit the Output Schema we defined in Chapter 1: Data Schema & Validation.
Before we look at the code, let's visualize exactly what happens when the call() function runs.
call() Method
Let's build this function step-by-step inside TaskListTool.ts.
First, we get the ID of the list we are working on, fetch the tasks, and immediately hide the internal ones.
async call() {
const taskListId = getTaskListId()
// Fetch tasks and remove internal metadata
const allTasks = (await listTasks(taskListId)).filter(
t => !t.metadata?._internal,
)
// ... logic continues
Explanation:
listTasks: This is our database helper. It grabs everything..filter(...): We look at every task. If metadata._internal exists, we remove that task from our list.To solve the dependency problem (Concept 2), we first need to know which tasks are already finished.
// ... inside call()
// Create a Set of IDs for tasks that are DONE
const resolvedTaskIds = new Set(
allTasks
.filter(t => t.status === 'completed')
.map(t => t.id),
)
Explanation:
Set (a fast lookup list).id of every task where the status is 'completed'.resolvedTaskIds.has('task_1')? If yes, Task 1 is done.Now we loop through our tasks to prepare the final output. This is where we check the blockers against our cheat sheet.
// ... inside call()
const tasks = allTasks.map(task => ({
id: task.id,
subject: task.subject,
status: task.status,
owner: task.owner, // Pass the owner field through
// ... blocker logic below
Explanation:
.map() to transform the raw database objects into the clean shape our Schema expects.
Here is the "Librarian" logic in action. We filter the blockedBy list.
// ... inside the .map() object
// Only keep blockers that are NOT finished yet
blockedBy: task.blockedBy.filter(
id => !resolvedTaskIds.has(id)
),
}))
// ... return statement next
Explanation:
task.blockedBy: A list of IDs blocking this task (e.g., ['id_1', 'id_2'])..filter: We check each blocker ID.!resolvedTaskIds.has(id): If the blocker ID is inside our "Completed Cheat Sheet", we remove it. We only keep blockers that are still active.Finally, we wrap the data in the object structure required by our Schema.
// ... inside call()
return {
data: {
tasks, // This matches our Output Schema
},
}
},
Explanation:
data property.data, we have tasks, which is the array of clean task objects we just created.Let's look at the difference between Raw Data and Logically Processed Data.
Scenario:
Without Logic (Raw Data):
AI: "I see 'Make Omelet' is blocked by 101. I cannot make the omelet yet."
(The AI is stuck because it doesn't realize 101 is already done).
With Logic (Our Code):
1. Code sees 101 is completed. Adds 101 to
resolvedTaskIds.
2. Code looks at Task B. Sees blocked by 101.
3. Code filters 101 out because it is resolved.
4. AI receives: Task B, Blocked By: [].
AI: "Task B has no blockers. I will start making the omelet."
We have successfully built the execution logic for our tool.
The tool now returns a clean, logical JavaScript object. However, Large Language Models (LLMs) are essentially text processors. While they can read JSON objects, sometimes it is better to format the output into a specific text style to make sure the AI pays attention to the most important details.
In the final chapter, we will handle how this data is presented back to the AI.
Next Chapter: LLM Response Formatting
Generated by Code IQ