feat(advanced): download chat attachments into the conversational workspace - #1079
Open
radu-mocanu wants to merge 5 commits into
Open
feat(advanced): download chat attachments into the conversational workspace#1079radu-mocanu wants to merge 5 commits into
radu-mocanu wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
🟡 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.CancelledErrorinherits fromBaseException, 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 surfacesfile_path, but the agent's file tools cannot read the directory; cache only regular files (the output-file path handling similarly checksis_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.
radu-mocanu
force-pushed
the
feat/conv-advanced-chat-attachments
branch
from
September 11, 2026 15:42
3fe86e3 to
43d4a68
Compare
…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
force-pushed
the
feat/conv-advanced-chat-attachments
branch
from
September 12, 2026 21:18
1d02ca2 to
9863f76
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
An advanced conversational agent has a run workspace backed by a deepagents
FilesystemBackend, and files passed as input arguments are downloaded into it solsandread_filecan open them. Files the end user attaches in the chat window were not.The attachment arrives as a content part on the incoming message:
UiPathChatMessagesMapperturns that into a<uip:attachments>block carryingid,full_nameandmime_type, with no path, because nothing had downloaded the file. In a real run the model calledread_file(file_path="47e38533-ddce-4ac3-913d-08df0ff90f7b"), thenls /, 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 ofcreate_conversational_advanced_agent_graph, now does what the autonomous siblingtransform_input_asyncalready did throughresolve_input_attachments.The new
resolve_message_attachments:<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),file_pathto that attachment's entry in the<uip:attachments>block, so the model reads the path instead of guessing,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.pyso 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
FilesystemBackendhas nowhere to download to. This logs a warning and returns, matching howmemory_sourcesin the same builder already degrades, rather than raising the wayresolve_input_attachmentsdoes.resolve_input_attachments, so input-schema attachments stay unresolved on that path.