[FEATURE] Preserve external document URLs as input_file.file_url in the Responses protocol
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
Research direction
Start with RubyLLM::Protocols::Responses::Media#render_document and the provider capability handling that decides which document representations are supported. Read the related attachment URL handling and add or inspect a spec for rendering a URL-backed PDF. Done means supported providers send file_url without downloading the document, while local files, uploaded files, and unsupported providers retain their stated behavior.
Written by the indexing model from the issue text.
Description
Scope check
- This is core LLM communication (not application logic)
- This benefits most users (not just my use case)
- This can't be solved in application code with current RubyLLM
- I read the Contributing Guide
Due diligence
- I searched existing issues
- I checked the documentation
What problem does this solve?
RubyLLM accepts HTTP(S) URLs as attachment sources, but URL-backed documents are downloaded by the application and converted to Base64 when rendered for a Responses API request.
For example:
attachment = RubyLLM::Attachment.new(
"https://example.com/signed/report.pdf",
filename: "report.pdf"
)
chat.ask("Summarize this document", with: attachment)
RubyLLM::Protocols::Responses::Media#render_document currently renders every non-provider document as:
{
type: "input_file",
filename: document.filename,
file_data: document.for_llm
}
For a URL-backed attachment, document.for_llm reads the remote file into the application process and Base64-encodes it.
The OpenAI Responses API supports external document URLs directly:
{
"type": "input_file",
"file_url": "https://example.com/signed/report.pdf"
}
Passing the URL through would allow the provider to download the document directly. This avoids:
- Downloading the entire document into application memory
- Base64 encoding overhead
- Increasing the request body by approximately 33%
- Relaying file bytes through the application server
- Unnecessary latency and network traffic for applications using S3, CloudFront, or another object store
This is especially useful for Rails applications that generate short-lived authorized URLs for private uploads.
RubyLLM already preserves external URLs for images through Attachment#url_or_data_uri, so document URLs currently behave differently from image URLs.
In one sentence: preserve URLs when the provider supports them
I found two related issues, but neither appears to address this behavior:
- #134 added attachment support to
acts_as_chat; it does not preserve document URLs during Responses serialization. - #1261 concerns multipart ordering when uploading bytes to OpenAI's
/v1/filesendpoint. Passingfile_urlto/v1/responseswould not use that upload endpoint.
This behavior is present in RubyLLM 2.1.1 and appears to remain present on main.
Proposed solution
When a document attachment is backed by an HTTP(S) URL and the selected Responses provider supports external file URLs, render it using file_url instead of downloading and encoding it as file_data.
Conceptually:
def render_document(document)
if document.url?
{
type: "input_file",
file_url: document.source.to_s
}
else
{
type: "input_file",
filename: document.filename,
file_data: document.for_llm
}
end
end
The exact implementation may need to be provider-aware because not every Responses-compatible provider necessarily supports external document URLs. Following RubyLLM's provider/protocol design, unsupported providers could retain the existing file_data behavior.
Expected behavior:
- URL-backed document supported by the provider: send
file_url - Local path, IO, or Active Storage object: continue sending
filenameandfile_data RubyLLM::UploadedFile: continue sendingfile_id- URL-backed image: preserve the existing behavior
- Providers without URL-backed document support: retain the appropriate provider-specific fallback
A spec could construct a URL-backed PDF with an explicit filename, render the request, and assert that:
- The payload contains
file_url - The payload does not contain
file_data - The URL is not downloaded by RubyLLM
I would be happy to help test or contribute the implementation.
Why this belongs in RubyLLM
This is request translation for a provider API, not application workflow logic.
An application can technically work around the behavior by monkey-patching RubyLLM::Protocols::Responses::Media, defining a custom protocol/provider, or bypassing RubyLLM for these requests. However, there is currently no supported public API that lets an application choose file_url for a document attachment.
A monkey patch would depend on internal classes and would have to reproduce provider capability decisions. Bypassing RubyLLM would also lose the consistent chat, usage, instrumentation, tool, and provider behavior that the gem provides.
RubyLLM already owns:
- Attachment source detection
- Provider capability handling
- Responses request serialization
- The choice between
file_id,file_data, and provider-specific representations
Choosing file_url for a URL-backed document is therefore part of RubyLLM's core LLM communication responsibility. It benefits any application using remote object storage or signed document URLs, rather than being specific to one application's domain.
- Dominant language
- Ruby
- Stars
- 4.5k
- Forks
- 515
- Avg merge
- 9h 31m
- Merged PRs (30d)
- 220
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from crmne/ruby_llm
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
crmne/ruby_llm#1244 · 1 reaction ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 55/100
crmne/ruby_llm#1261 · 1 reaction ·
Maintainers usually reply within 1 day
-
[FEATURE] Continue Gemini long-decode responses as one generationPossibly taken @yorzi claimed this 1 day ago. Openenhancement
Difficulty 5/5 Over a week Newbie friendliness 35/100
crmne/ruby_llm#1246 · 1 reaction ·
Maintainers usually reply within 1 day
-
[FEATURE] Preserve native client-toolset identity and resultsPossibly taken @yorzi claimed this 1 day ago. Openenhancement
Difficulty 5/5 Over a week Newbie friendliness 20/100
crmne/ruby_llm#1245 · 1 reaction ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
glossarist/glossarist-ruby#247 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
🐛 bug 🧑💼 conseillers
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
betagouv/conseillers-entreprises#4768 ·
Maintainers usually reply within 1 day
-
`github-events` aborts the repository scan when a closed pull's API payload has a null `head`Possibly taken @renyamamoto2026-cmyk claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
zerocracy/judges-action#2823 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
Maintainers usually reply within 1 day