From 53af68c708274e6d9968483b2d982d31f1248eb0 Mon Sep 17 00:00:00 2001 From: David Dizon Date: Thu, 9 Jul 2026 13:51:39 -0700 Subject: [PATCH 1/5] Add Unstructured Transform MCP cookbook Co-Authored-By: Claude Opus 4.8 (1M context) --- index.toml | 6 + notebooks/unstructured_transform_mcp.ipynb | 235 +++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 notebooks/unstructured_transform_mcp.ipynb diff --git a/index.toml b/index.toml index 88948ba..38bc9c8 100644 --- a/index.toml +++ b/index.toml @@ -371,6 +371,12 @@ notebook = "prior_labs_agent.ipynb" new = true topics = ["Agents", "MCP", "Data Processing"] +[[cookbook]] +title = "Document Processing with Unstructured Transform MCP" +notebook = "unstructured_transform_mcp.ipynb" +new = true +topics = ["Agents", "MCP", "Data Processing"] + [[cookbook]] title = "Agentic Itinerary Planning with OpenStreetMap" notebook = "agentic_itinerary_planning_openstreetmap.ipynb" diff --git a/notebooks/unstructured_transform_mcp.ipynb b/notebooks/unstructured_transform_mcp.ipynb new file mode 100644 index 0000000..23f9329 --- /dev/null +++ b/notebooks/unstructured_transform_mcp.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "t1a2b3c4", + "metadata": {}, + "source": [ + "# Document Processing with Unstructured Transform MCP\n", + "\n", + "In this recipe, we connect to [Unstructured Transform](https://docs.unstructured.io/transform/overview)'s hosted MCP server and use a Haystack `Agent` to parse and chunk a document, entirely through MCP tools.\n", + "\n", + "**Services used:**\n", + "- [Unstructured Transform MCP](https://mcp.transform.unstructured.io): document processing (partition, enrich, chunk, embed) exposed as MCP tools\n", + "- [Anthropic Claude](https://www.anthropic.com/): LLM for agent reasoning" + ] + }, + { + "cell_type": "markdown", + "id": "i1nst4ll0", + "metadata": {}, + "source": [ + "## Install dependencies" + ] + }, + { + "cell_type": "code", + "id": "dep5nd001", + "metadata": {}, + "source": [ + "!pip install -q haystack-ai mcp-haystack anthropic-haystack" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "4p1k3ys01", + "metadata": {}, + "source": [ + "## Set up API keys\n", + "\n", + "You'll need two API keys:\n", + "- **Unstructured API key**: get one from the [Transform get-started page](https://transform.unstructured.io/get-started) after signing in. The free tier includes 15,000 pages a month.\n", + "- **Anthropic API key**: get one at [console.anthropic.com](https://console.anthropic.com/)" + ] + }, + { + "cell_type": "code", + "id": "k3ys00cod", + "metadata": {}, + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "if \"UNSTRUCTURED_API_KEY\" not in os.environ:\n", + " os.environ[\"UNSTRUCTURED_API_KEY\"] = getpass(\"Enter your Unstructured API key: \")\n", + "if \"ANTHROPIC_API_KEY\" not in os.environ:\n", + " os.environ[\"ANTHROPIC_API_KEY\"] = getpass(\"Enter your Anthropic API key: \")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "st3p1md01", + "metadata": {}, + "source": [ + "## Step 1: Connect to Unstructured Transform MCP\n", + "\n", + "[Unstructured Transform](https://docs.unstructured.io/transform/overview) exposes its document-processing pipeline (partition, enrich, chunk, embed) as a hosted MCP server at `https://mcp.transform.unstructured.io`. We connect to it with [`MCPToolset`](https://docs.haystack.deepset.ai/docs/mcptoolset), using `StreamableHttpServerInfo`'s native `token` parameter to send the Unstructured API key as an `Authorization: Bearer` header." + ] + }, + { + "cell_type": "code", + "id": "st3p1cod1", + "metadata": {}, + "source": [ + "from haystack_integrations.tools.mcp import MCPToolset, StreamableHttpServerInfo\n", + "from haystack.utils import Secret\n", + "\n", + "server_info = StreamableHttpServerInfo(\n", + " url=\"https://mcp.transform.unstructured.io\",\n", + " token=Secret.from_env_var(\"UNSTRUCTURED_API_KEY\"),\n", + ")\n", + "toolset = MCPToolset(server_info=server_info, eager_connect=True)\n", + "\n", + "for tool in toolset.tools:\n", + " print(f\"{tool.name}: {tool.description}\")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "st3p1md02", + "metadata": {}, + "source": [ + "This exposes four tools:\n", + "- `transform_files`: submits one or more files for processing and returns a `job_id` right away; the job itself runs asynchronously\n", + "- `check_transform_status`: polls a job's status until it reaches `COMPLETED`\n", + "- `get_transform_results`: fetches the rendered output of a completed job, as markdown, JSON, HTML, or plain text\n", + "- `request_file_upload_url`: returns a presigned upload URL for a local file, for inputs that aren't already reachable over HTTPS" + ] + }, + { + "cell_type": "markdown", + "id": "st3p2md01", + "metadata": {}, + "source": [ + "## Step 2: Build a Haystack Agent with the Transform MCP toolset\n", + "\n", + "We give the agent the toolset directly, along with a system prompt describing the asynchronous `transform_files` -> `check_transform_status` -> `get_transform_results` flow, since the agent needs to poll for a result rather than get one back immediately." + ] + }, + { + "cell_type": "code", + "id": "st3p2cod1", + "metadata": {}, + "source": [ + "from haystack.components.agents import Agent\n", + "from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator\n", + "\n", + "agent = Agent(\n", + " chat_generator=AnthropicChatGenerator(\n", + " model=\"claude-opus-4-6\",\n", + " generation_kwargs={\"max_tokens\": 4096},\n", + " ),\n", + " tools=toolset,\n", + " system_prompt=\"\"\"You are a document-processing assistant with access to Unstructured Transform MCP tools.\n", + "\n", + "Transform jobs are asynchronous. When asked to process a document:\n", + "1. Call `transform_files` with the file reference(s) and the requested processing stages. This returns a `job_id` immediately; the job itself runs in the background.\n", + "2. Call `check_transform_status` with that `job_id`, repeating until the status is COMPLETED.\n", + "3. Call `get_transform_results` with the `job_id` to fetch the rendered output, and summarize it for the user.\n", + "\"\"\",\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "st3p3md01", + "metadata": {}, + "source": [ + "## Step 3: Process a document end-to-end\n", + "\n", + "We hand the agent a publicly reachable PDF and ask it to parse and chunk it. No local file or upload step is needed here, since `transform_files` accepts `https://` URLs directly." + ] + }, + { + "cell_type": "code", + "id": "st3p3cod1", + "metadata": {}, + "source": [ + "from haystack.dataclasses import ChatMessage\n", + "\n", + "pdf_url = \"https://arxiv.org/pdf/1706.03762\"\n", + "\n", + "result = agent.run(\n", + " messages=[\n", + " ChatMessage.from_user(\n", + " f\"Parse and chunk the PDF at {pdf_url}. \"\n", + " \"Use the 'hi_res' partition strategy, and chunk with chunk_by_title, \"\n", + " \"max_characters=1000. Once the job is complete, fetch the results as \"\n", + " \"markdown and show me the first two chunks.\"\n", + " )\n", + " ]\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "id": "st3p3cod2", + "metadata": {}, + "source": [ + "print(result[\"last_message\"].text)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "0auth5not", + "metadata": {}, + "source": [ + "### A note on the OAuth fallback\n", + "\n", + "Above, we authenticated with a static Unstructured API key, the simplest path for headless agent frameworks like this one. Transform MCP also supports interactive OAuth/OIDC login for clients that speak remote MCP natively.\n", + "\n", + "If your MCP client only supports local (stdio) servers, or doesn't support browser-based OAuth for remote servers, you can bridge to the hosted server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):\n", + "\n", + "```bash\n", + "npm install -g mcp-remote\n", + "npx -y mcp-remote https://mcp.transform.unstructured.io\n", + "```\n", + "\n", + "This runs a local stdio proxy that handles the browser OAuth flow and forwards requests to `https://mcp.transform.unstructured.io`." + ] + }, + { + "cell_type": "markdown", + "id": "c0ncl0001", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "Unstructured Transform's MCP server brings partitioning, enrichment, chunking, and embedding into a single set of tools an agent can call directly, without wiring up a separate ETL pipeline. Because `transform_files` runs asynchronously and returns a `job_id` right away, a Haystack `Agent` can poll for completion and fetch results the same way it would call any other tool, making it straightforward to drop document processing into a larger agentic workflow." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "3.12.12", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 559978e216151f4299b21fe01b8681ccbe2c1539 Mon Sep 17 00:00:00 2001 From: David Dizon Date: Tue, 28 Jul 2026 09:46:46 -0700 Subject: [PATCH 2/5] Update tool names for Transform MCP rename (transform_files -> start_transform_job, check_transform_status -> check_job_status, get_transform_results -> get_job_results) Co-Authored-By: Claude Sonnet 5 --- notebooks/unstructured_transform_mcp.ipynb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/notebooks/unstructured_transform_mcp.ipynb b/notebooks/unstructured_transform_mcp.ipynb index 23f9329..578eb03 100644 --- a/notebooks/unstructured_transform_mcp.ipynb +++ b/notebooks/unstructured_transform_mcp.ipynb @@ -96,9 +96,9 @@ "metadata": {}, "source": [ "This exposes four tools:\n", - "- `transform_files`: submits one or more files for processing and returns a `job_id` right away; the job itself runs asynchronously\n", - "- `check_transform_status`: polls a job's status until it reaches `COMPLETED`\n", - "- `get_transform_results`: fetches the rendered output of a completed job, as markdown, JSON, HTML, or plain text\n", + "- `start_transform_job`: submits one or more files for processing and returns a `job_id` right away; the job itself runs asynchronously\n", + "- `check_job_status`: polls a job's status until it reaches `COMPLETED`\n", + "- `get_job_results`: fetches the rendered output of a completed job, as markdown, JSON, HTML, or plain text\n", "- `request_file_upload_url`: returns a presigned upload URL for a local file, for inputs that aren't already reachable over HTTPS" ] }, @@ -109,7 +109,7 @@ "source": [ "## Step 2: Build a Haystack Agent with the Transform MCP toolset\n", "\n", - "We give the agent the toolset directly, along with a system prompt describing the asynchronous `transform_files` -> `check_transform_status` -> `get_transform_results` flow, since the agent needs to poll for a result rather than get one back immediately." + "We give the agent the toolset directly, along with a system prompt describing the asynchronous `start_transform_job` -> `check_job_status` -> `get_job_results` flow, since the agent needs to poll for a result rather than get one back immediately." ] }, { @@ -129,9 +129,9 @@ " system_prompt=\"\"\"You are a document-processing assistant with access to Unstructured Transform MCP tools.\n", "\n", "Transform jobs are asynchronous. When asked to process a document:\n", - "1. Call `transform_files` with the file reference(s) and the requested processing stages. This returns a `job_id` immediately; the job itself runs in the background.\n", - "2. Call `check_transform_status` with that `job_id`, repeating until the status is COMPLETED.\n", - "3. Call `get_transform_results` with the `job_id` to fetch the rendered output, and summarize it for the user.\n", + "1. Call `start_transform_job` with the file reference(s) and the requested processing stages. This returns a `job_id` immediately; the job itself runs in the background.\n", + "2. Call `check_job_status` with that `job_id`, repeating until the status is COMPLETED.\n", + "3. Call `get_job_results` with the `job_id` to fetch the rendered output, and summarize it for the user.\n", "\"\"\",\n", ")" ], @@ -145,7 +145,7 @@ "source": [ "## Step 3: Process a document end-to-end\n", "\n", - "We hand the agent a publicly reachable PDF and ask it to parse and chunk it. No local file or upload step is needed here, since `transform_files` accepts `https://` URLs directly." + "We hand the agent a publicly reachable PDF and ask it to parse and chunk it. No local file or upload step is needed here, since `start_transform_job` accepts `https://` URLs directly." ] }, { @@ -207,7 +207,7 @@ "source": [ "## Conclusion\n", "\n", - "Unstructured Transform's MCP server brings partitioning, enrichment, chunking, and embedding into a single set of tools an agent can call directly, without wiring up a separate ETL pipeline. Because `transform_files` runs asynchronously and returns a `job_id` right away, a Haystack `Agent` can poll for completion and fetch results the same way it would call any other tool, making it straightforward to drop document processing into a larger agentic workflow." + "Unstructured Transform's MCP server brings partitioning, enrichment, chunking, and embedding into a single set of tools an agent can call directly, without wiring up a separate ETL pipeline. Because `start_transform_job` runs asynchronously and returns a `job_id` right away, a Haystack `Agent` can poll for completion and fetch results the same way it would call any other tool, making it straightforward to drop document processing into a larger agentic workflow." ] } ], From c3429d89a2a4a1bf5969b0d0af721ca940089c2b Mon Sep 17 00:00:00 2001 From: David Dizon Date: Mon, 10 Aug 2026 10:53:08 -0700 Subject: [PATCH 3/5] Mirror #540 fixes: correct tool count, add wait between status polls, drop unnecessary mcp-remote note Co-Authored-By: Claude Sonnet 5 --- notebooks/unstructured_transform_mcp.ipynb | 23 ++-------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/notebooks/unstructured_transform_mcp.ipynb b/notebooks/unstructured_transform_mcp.ipynb index 578eb03..7a7015c 100644 --- a/notebooks/unstructured_transform_mcp.ipynb +++ b/notebooks/unstructured_transform_mcp.ipynb @@ -95,7 +95,7 @@ "id": "st3p1md02", "metadata": {}, "source": [ - "This exposes four tools:\n", + "The server exposes seven tools; the four below cover the document-processing pipeline this cookbook focuses on (the remaining three, `start_extraction_job`, `suggest_extraction_schema_for_file`, and `get_instructions`, support schema-based structured data extraction and on-demand server guidance, outside the scope here):\n", "- `start_transform_job`: submits one or more files for processing and returns a `job_id` right away; the job itself runs asynchronously\n", "- `check_job_status`: polls a job's status until it reaches `COMPLETED`\n", "- `get_job_results`: fetches the rendered output of a completed job, as markdown, JSON, HTML, or plain text\n", @@ -130,7 +130,7 @@ "\n", "Transform jobs are asynchronous. When asked to process a document:\n", "1. Call `start_transform_job` with the file reference(s) and the requested processing stages. This returns a `job_id` immediately; the job itself runs in the background.\n", - "2. Call `check_job_status` with that `job_id`, repeating until the status is COMPLETED.\n", + "2. Call `check_job_status` with that `job_id`, waiting a few seconds between calls, repeating until the status is COMPLETED.\n", "3. Call `get_job_results` with the `job_id` to fetch the rendered output, and summarize it for the user.\n", "\"\"\",\n", ")" @@ -181,25 +181,6 @@ "outputs": [], "execution_count": null }, - { - "cell_type": "markdown", - "id": "0auth5not", - "metadata": {}, - "source": [ - "### A note on the OAuth fallback\n", - "\n", - "Above, we authenticated with a static Unstructured API key, the simplest path for headless agent frameworks like this one. Transform MCP also supports interactive OAuth/OIDC login for clients that speak remote MCP natively.\n", - "\n", - "If your MCP client only supports local (stdio) servers, or doesn't support browser-based OAuth for remote servers, you can bridge to the hosted server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote):\n", - "\n", - "```bash\n", - "npm install -g mcp-remote\n", - "npx -y mcp-remote https://mcp.transform.unstructured.io\n", - "```\n", - "\n", - "This runs a local stdio proxy that handles the browser OAuth flow and forwards requests to `https://mcp.transform.unstructured.io`." - ] - }, { "cell_type": "markdown", "id": "c0ncl0001", From 87763872ef8a240af0cb29131181675dafef2ef3 Mon Sep 17 00:00:00 2001 From: David Dizon Date: Tue, 11 Aug 2026 17:37:13 -0700 Subject: [PATCH 4/5] Stop hardcoding tool names: describe the pipeline by behavior, discover tools live Co-Authored-By: Claude Sonnet 5 --- notebooks/unstructured_transform_mcp.ipynb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/notebooks/unstructured_transform_mcp.ipynb b/notebooks/unstructured_transform_mcp.ipynb index 7a7015c..397182b 100644 --- a/notebooks/unstructured_transform_mcp.ipynb +++ b/notebooks/unstructured_transform_mcp.ipynb @@ -95,11 +95,7 @@ "id": "st3p1md02", "metadata": {}, "source": [ - "The server exposes seven tools; the four below cover the document-processing pipeline this cookbook focuses on (the remaining three, `start_extraction_job`, `suggest_extraction_schema_for_file`, and `get_instructions`, support schema-based structured data extraction and on-demand server guidance, outside the scope here):\n", - "- `start_transform_job`: submits one or more files for processing and returns a `job_id` right away; the job itself runs asynchronously\n", - "- `check_job_status`: polls a job's status until it reaches `COMPLETED`\n", - "- `get_job_results`: fetches the rendered output of a completed job, as markdown, JSON, HTML, or plain text\n", - "- `request_file_upload_url`: returns a presigned upload URL for a local file, for inputs that aren't already reachable over HTTPS" + "The pipeline runs asynchronously as a job: submit a file for processing, poll until it's done, then fetch the rendered result; a separate helper mints an upload URL for files that aren't already reachable over HTTPS. Unstructured adds tools and capabilities to this server as they ship new features, so rather than list exact tool names and a fixed count here, the cell above discovered the live toolset at connect time, and the agent below matches tools to each step by description." ] }, { @@ -109,7 +105,7 @@ "source": [ "## Step 2: Build a Haystack Agent with the Transform MCP toolset\n", "\n", - "We give the agent the toolset directly, along with a system prompt describing the asynchronous `start_transform_job` -> `check_job_status` -> `get_job_results` flow, since the agent needs to poll for a result rather than get one back immediately." + "We give the agent the toolset directly, along with a system prompt describing the asynchronous submit -> poll -> fetch flow by behavior rather than by hardcoded tool name, since the agent needs to poll for a result rather than get one back immediately, and this way the prompt keeps working as Unstructured renames or adds tools." ] }, { @@ -126,12 +122,12 @@ " generation_kwargs={\"max_tokens\": 4096},\n", " ),\n", " tools=toolset,\n", - " system_prompt=\"\"\"You are a document-processing assistant with access to Unstructured Transform MCP tools.\n", + " system_prompt=\"\"\"You are a document-processing assistant with access to Unstructured Transform MCP tools. Check the tools available to you and use whichever ones match the steps below by description, since exact tool names may change over time.\n", "\n", "Transform jobs are asynchronous. When asked to process a document:\n", - "1. Call `start_transform_job` with the file reference(s) and the requested processing stages. This returns a `job_id` immediately; the job itself runs in the background.\n", - "2. Call `check_job_status` with that `job_id`, waiting a few seconds between calls, repeating until the status is COMPLETED.\n", - "3. Call `get_job_results` with the `job_id` to fetch the rendered output, and summarize it for the user.\n", + "1. Submit the file reference(s) and the requested processing stages to start a processing job. This returns a job ID immediately; the job itself runs in the background.\n", + "2. Check the job's status, waiting a few seconds between checks, until it reports as complete.\n", + "3. Fetch the job's rendered output using its job ID, and summarize it for the user.\n", "\"\"\",\n", ")" ], @@ -145,7 +141,7 @@ "source": [ "## Step 3: Process a document end-to-end\n", "\n", - "We hand the agent a publicly reachable PDF and ask it to parse and chunk it. No local file or upload step is needed here, since `start_transform_job` accepts `https://` URLs directly." + "We hand the agent a publicly reachable PDF and ask it to parse and chunk it. No local file or upload step is needed here, since the job-submission tool accepts `https://` URLs directly." ] }, { @@ -188,7 +184,7 @@ "source": [ "## Conclusion\n", "\n", - "Unstructured Transform's MCP server brings partitioning, enrichment, chunking, and embedding into a single set of tools an agent can call directly, without wiring up a separate ETL pipeline. Because `start_transform_job` runs asynchronously and returns a `job_id` right away, a Haystack `Agent` can poll for completion and fetch results the same way it would call any other tool, making it straightforward to drop document processing into a larger agentic workflow." + "Unstructured Transform's MCP server brings partitioning, enrichment, chunking, and embedding into a single set of tools an agent can call directly, without wiring up a separate ETL pipeline. Because job submission runs asynchronously and returns a job ID right away, a Haystack `Agent` can poll for completion and fetch results the same way it would call any other tool, making it straightforward to drop document processing into a larger agentic workflow." ] } ], From a43991066975a5ebb977d29e0191886343ee9fe4 Mon Sep 17 00:00:00 2001 From: David Dizon Date: Wed, 19 Aug 2026 09:30:56 -0700 Subject: [PATCH 5/5] Mirror #540 fix: use a real wait tool instead of asking the model to pause Co-Authored-By: Claude Sonnet 5 --- notebooks/unstructured_transform_mcp.ipynb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/notebooks/unstructured_transform_mcp.ipynb b/notebooks/unstructured_transform_mcp.ipynb index 397182b..97f170d 100644 --- a/notebooks/unstructured_transform_mcp.ipynb +++ b/notebooks/unstructured_transform_mcp.ipynb @@ -113,20 +113,33 @@ "id": "st3p2cod1", "metadata": {}, "source": [ + "import time\n", + "from typing import Annotated\n", + "\n", "from haystack.components.agents import Agent\n", + "from haystack.tools import tool\n", "from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator\n", "\n", + "\n", + "@tool\n", + "def wait(seconds: Annotated[int, \"How many seconds to pause before the next tool call\"]) -> str:\n", + " \"\"\"Pause execution for the given number of seconds. A chat generator can't control timing on\n", + " its own, so call this between status checks instead of just waiting in the response text.\"\"\"\n", + " time.sleep(seconds)\n", + " return f\"Waited {seconds} second(s).\"\n", + "\n", + "\n", "agent = Agent(\n", " chat_generator=AnthropicChatGenerator(\n", " model=\"claude-opus-4-6\",\n", " generation_kwargs={\"max_tokens\": 4096},\n", " ),\n", - " tools=toolset,\n", + " tools=toolset + wait,\n", " system_prompt=\"\"\"You are a document-processing assistant with access to Unstructured Transform MCP tools. Check the tools available to you and use whichever ones match the steps below by description, since exact tool names may change over time.\n", "\n", "Transform jobs are asynchronous. When asked to process a document:\n", "1. Submit the file reference(s) and the requested processing stages to start a processing job. This returns a job ID immediately; the job itself runs in the background.\n", - "2. Check the job's status, waiting a few seconds between checks, until it reports as complete.\n", + "2. Check the job's status. If it isn't complete yet, call the wait tool for a few seconds, then check again, repeating until it reports as complete.\n", "3. Fetch the job's rendered output using its job ID, and summarize it for the user.\n", "\"\"\",\n", ")"