Skip to main content

Building a Contract Review Agent

Let’s build an AI agent that reviews and edits NDAs.

Step 1: Upload the Document

import fs from "fs";

// Read the document file
const fileBuffer = fs.readFileSync("nda.docx");
const file = new File([fileBuffer], "nda.docx", {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});

// Create form data
const formData = new FormData();
formData.append("file", file);
formData.append("return_markdown", "true");

const response = await fetch(
  "https://api.agentoffice.dev/v1/documents/create",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
    },
    body: formData,
  }
);

const { doc_id, markdown } = await response.json();

Step 2: Create Agent Tools

import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const tools = {
  readFile: {
    description:
      "Use this tool to read the current state of the document as markdown.",
    parameters: z.object({}),
    execute: async () => {
      const response = await fetch(
        "https://api.agentoffice.dev/v1/documents/read",
        {
          method: "POST",
          headers: {
            Authorization: "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            doc_id,
            read_uid: crypto.randomUUID(),
          }),
        }
      );
      const result = await response.json();
      return result.markdown;
    },
  },
  editFile: {
    description:
      "Use this tool to directly apply an edit to a small section or paragraph of the document. All changes will be applied immediately, this tool should not be used for adding tracked changes or comments.",
    parameters: z.object({
      editInstructions: z
        .string()
        .describe(
          "Clear concise instructions of the edit to be made. Use first-person, clear descriptions. The editFile tool can handle many distinct edits at once if they are all in the same paragraph / section."
        ),
      lookupText: z
        .string()
        .optional()
        .describe(
          "Provide a unique sentence or set of phrases in a paragraph close to the edit. Do not include markdown or formatting syntax such as backslashes. Keyword matching is used to find the correct section to edit."
        ),
    }),
    execute: async ({ editInstructions, lookupText }) => {
      const response = await fetch(
        "https://api.agentoffice.dev/v1/documents/edit",
        {
          method: "POST",
          headers: {
            Authorization: "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            doc_id,
            edit_uid: crypto.randomUUID(),
            edit_instructions: editInstructions,
            lookup_text: lookupText,
          }),
        }
      );

      const result = await response.json();
      return result.edit_applied ? "Edit applied" : "Edit failed";
    },
  },
};

Step 3: Run Your Agent

const result = await generateText({
  model: anthropic("claude-sonnet-4"),
  tools,
  prompt: `You are an expert legal assistant. 

You represent henry hoovers (the recipient) who have just been sent an NDA by Glide Technologies.
This NDA has landed on your desk and you must fill in the placeholders with dummy values and make edits to the document so that henry hoovers is protected.

The document content is:

${markdown}

Use the editFile tool to make edits to the document.
After making a large amount of edits, use the readFile tool to read the document and make sure the edits are applied correctly.`,
});

Step 4: Download the Result

const response = await fetch(
  "https://api.agentoffice.dev/v1/documents/download",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ doc_id }),
  }
);

const { download_url } = await response.json();

Full Code Example

import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
import fs from "fs";

// Upload document
const fileBuffer = fs.readFileSync("nda.docx");
const file = new File([fileBuffer], "nda.docx", {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});

const formData = new FormData();
formData.append("file", file);
formData.append("return_markdown", "true");

const uploadResponse = await fetch(
  "https://api.agentoffice.dev/v1/documents/create",
  {
    method: "POST",
    headers: { Authorization: "Bearer YOUR_API_KEY" },
    body: formData,
  }
);

const { doc_id, markdown } = await uploadResponse.json();

// Define tools
const tools = {
  readFile: {
    description:
      "Use this tool to read the current state of the document as markdown.",
    parameters: z.object({}),
    execute: async () => {
      const response = await fetch(
        "https://api.agentoffice.dev/v1/documents/read",
        {
          method: "POST",
          headers: {
            Authorization: "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            doc_id,
            read_uid: crypto.randomUUID(),
          }),
        }
      );
      const result = await response.json();
      return result.markdown;
    },
  },
  editFile: {
    description:
      "Use this tool to directly apply an edit to a small section or paragraph of the document. All changes will be applied immediately, this tool should not be used for adding tracked changes or comments.",
    parameters: z.object({
      editInstructions: z
        .string()
        .describe(
          "Clear concise instructions of the edit to be made. Use first-person, clear descriptions. The editFile tool can handle many distinct edits at once if they are all in the same paragraph / section."
        ),
      lookupText: z
        .string()
        .optional()
        .describe(
          "Provide a unique sentence or set of phrases in a paragraph close to the edit. Do not include markdown or formatting syntax such as backslashes. Keyword matching is used to find the correct section to edit."
        ),
    }),
    execute: async ({ editInstructions, lookupText }) => {
      const response = await fetch(
        "https://api.agentoffice.dev/v1/documents/edit",
        {
          method: "POST",
          headers: {
            Authorization: "Bearer YOUR_API_KEY",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            doc_id,
            edit_uid: crypto.randomUUID(),
            edit_instructions: editInstructions,
            lookup_text: lookupText,
          }),
        }
      );
      const result = await response.json();
      return result.edit_applied ? "Edit applied" : "Edit failed";
    },
  },
};

// Run agent
const result = await generateText({
  model: anthropic("claude-sonnet-4"),
  tools,
  prompt: `You are an expert legal assistant. 

You represent henry hoovers (the recipient) who have just been sent an NDA by Glide Technologies.
This NDA has landed on your desk and you must fill in the placeholders with dummy values and make edits to the document so that henry hoovers is protected.

The document content is:

${markdown}

Use the editFile tool to make edits to the document.
After making a large amount of edits, use the readFile tool to read the document and make sure the edits are applied correctly.`,
});

// Download result
const downloadResponse = await fetch(
  "https://api.agentoffice.dev/v1/documents/download",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ doc_id }),
  }
);

const { download_url } = await downloadResponse.json();
I