Skip to content

feat(advanced): download chat attachments into the conversational workspace - #1079

Open
radu-mocanu wants to merge 5 commits into
mainfrom
feat/conv-advanced-chat-attachments
Open

feat(advanced): download chat attachments into the conversational workspace#1079
radu-mocanu wants to merge 5 commits into
mainfrom
feat/conv-advanced-chat-attachments

Conversation

@radu-mocanu

@radu-mocanu radu-mocanu commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Problem

An advanced conversational agent has a run workspace backed by a deepagents FilesystemBackend, and files passed as input arguments are downloaded into it so ls and read_file can open them. Files the end user attaches in the chat window were not.

The attachment arrives as a content part on the incoming message:

mimeType: text/markdown
data.uri: urn:uipath:cas:file:orchestrator:47e38533-ddce-4ac3-913d-08df0ff90f7b
name:     uipath_company_report.md

UiPathChatMessagesMapper turns that into a <uip:attachments> block carrying id, full_name and mime_type, with no path, because nothing had downloaded the file. In a real run the model called read_file(file_path="47e38533-ddce-4ac3-913d-08df0ff90f7b"), then ls /, then told the user the attachment was not accessible and asked them to re-upload it. Agents only coped when they happened to have a separate file-analysis tool.

Change

capture_exchange_start, the entry node of create_conversational_advanced_agent_graph, now does what the autonomous sibling transform_input_async already did through resolve_input_attachments.

The new resolve_message_attachments:

  • streams each attachment to <backend.cwd>/<id>_<basename>, the same naming rule input attachments use (now shared as _workspace_file_name, basename only so a caller-controlled name cannot escape the workspace),
  • adds file_path to that attachment's entry in the <uip:attachments> block, so the model reads the path instead of guessing,
  • skips anything already in the workspace, so replaying the conversation history on later exchanges downloads nothing,
  • logs and skips an attachment it cannot download rather than failing the exchange, and unlinks the partially written destination so a later exchange retries instead of reading a truncated file.

Once the file is in the workspace the existing conversational workspace hydration carries it across suspend and resume with no special case.

The <uip:attachments> renderer moves to _utils/_attachments.py so the mapper and the advanced agent share one definition of the block rather than one writing it and the other pattern-matching a literal.

Notes

  • Messages whose content is a plain string (assistant messages) are skipped: there is no block to annotate, so queuing a download for them only moved bytes nobody could reach.
  • A backend that is not a FilesystemBackend has nowhere to download to. This logs a warning and returns, matching how memory_sources in the same builder already degrades, rather than raising the way resolve_input_attachments does.
  • Not addressed here: conversational advanced agents also never call resolve_input_attachments, so input-schema attachments stay unresolved on that path.
image

Copilot AI lite review requested due to automatic review settings September 11, 2026 14:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Three moderate issues remain in attachment resolution.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds chat-attachment downloading for conversational advanced agents, exposing workspace paths to the model.

Changes:

  • Downloads and sanitizes attachments in the workspace.
  • Shares attachment rendering between runtime and agent code.
  • Adds unit and integration coverage and updates package metadata.
File summaries
File Summary
uv.lock Updates locked package metadata.
tests/agent/advanced/test_utils.py Tests attachment resolution behavior.
tests/agent/advanced/test_conversational_advanced_agent_graph.py Tests graph integration.
src/uipath_langchain/runtime/messages.py Uses shared attachment rendering.
src/uipath_langchain/agent/advanced/utils.py Resolves and downloads attachments; follow-ups remain for stale paths, cancellation handling, and directory destinations.
src/uipath_langchain/agent/advanced/agent.py Integrates attachment hydration at exchange start.
src/uipath_langchain/_utils/_attachments.py Provides shared attachment rendering.
pyproject.toml Updates package version metadata.
Review details

Suppressed comments (2)

src/uipath_langchain/agent/advanced/utils.py:181

  • asyncio.CancelledError inherits from BaseException, so a cancelled attachment download is treated as an ordinary failed download here. The resolver then unlinks the destination and returns normally, allowing a cancelled graph exchange to continue instead of propagating cancellation; re-raise cancellation before handling ordinary download exceptions.
        if isinstance(outcome, BaseException):
            logger.warning("Attachment %s could not be downloaded: %s", key, outcome)

src/uipath_langchain/agent/advanced/utils.py:165

  • Path.exists() treats a directory at the deterministic destination as an already-downloaded attachment. In that case the code skips the download and surfaces file_path, but the agent's file tools cannot read the directory; cache only regular files (the output-file path handling similarly checks is_file()).
    missing = {key: path for key, path in paths.items() if not path.exists()}
  • Files reviewed: 7/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/uipath_langchain/agent/advanced/utils.py Outdated
@radu-mocanu
radu-mocanu force-pushed the feat/conv-advanced-chat-attachments branch from 3fe86e3 to 43d4a68 Compare September 11, 2026 15:42
…kspace

A file attached in the chat window of an advanced conversational agent was
never downloaded into the run workspace, so the built-in filesystem tools
could not open it. The model saw only the attachment id in the attachment
block and guessed it as a path, then reported the file as missing.

The entry node of the conversational advanced graph now does what its
autonomous sibling already did through resolve_input_attachments: each
attachment is streamed into the FilesystemBackend at /<id>_<name>, the same
layout input attachments use, and its entry in the attachment block gains a
file_path the agent can open with read_file. Files already in the workspace
are left alone, so replaying the conversation history on later exchanges
downloads nothing, and the existing conversational workspace hydration
carries them across suspend and resume.

An attachment that cannot be downloaded is logged and left without a path
rather than failing the exchange, and its partially written destination is
removed so a later exchange retries instead of reading a truncated file.
…nt file

An attachment dict reaching the node with a file_path from an earlier
exchange kept it when the current resolution produced no path, so a failed
retry for a file no longer in the workspace pointed the model at something
that is not there. The no-path branch now strips the key instead of passing
the attachment through.
BaseMessage.content is str or a block list, so indexing it by key needs the
list narrowing that the rest of the suite already applies.
deepagents 0.7.11 no longer exports BackendFactory, and resolve_input_attachments
already takes BackendProtocol alone. Match it so the two attachment resolvers
keep the same backend type.
@radu-mocanu
radu-mocanu force-pushed the feat/conv-advanced-chat-attachments branch from 1d02ca2 to 9863f76 Compare September 12, 2026 21:18
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants