feat(stock-watchlist-agent-js): accept image inputs and annotate them on spans - #91
Open
cdfox wants to merge 6 commits into
Open
feat(stock-watchlist-agent-js): accept image inputs and annotate them on spans#91cdfox wants to merge 6 commits into
cdfox wants to merge 6 commits into
Conversation
… on spans
Adds an image input path to the JS stock watchlist demo. Arguments that look
like images (by extension, http(s) URL, or data: URL) are translated to ticker
symbols by an LLM before research begins, and the image bytes are attached to
the resulting llm span via imageParts.
- vision.js: identify_ticker is an llm-kind span annotated with
imageParts: [{mimeType, content}]; images are always materialized as base64
(URLs downloaded) so the same bytes go to OpenAI and onto the span
- orchestrator: image resolution runs inside the root analyze_portfolio span
so vision and research share one trace
- main.js: mixed ticker/image args, --image override, resolution summary
- logos/: three small wordmark images so the image path is runnable as-is
- default model for all call sites is now gpt-5.4-nano
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…arts Image parts on LLMObs spans shipped in dd-trace 6.10.0. The previous lockfile pinned 6.4.0, so a fresh install resolved a version that silently ignores imageParts and rendered no image on the span. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e trace view Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops URL and data-URL image inputs to keep the sample small. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… of OPENAI_MODEL Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
Adds an image input path to the JS stock watchlist demo, and attaches the input image to the LLM Observability span that consumes it.
Inputs can now be ticker symbols, local image files, or a mix of both:
When images are given, a vision step runs first: one LLM call per image identifies the public company shown and returns its ticker symbol. Resolved tickers are merged with any tickers passed directly, deduped, and handed to the existing orchestrator. Images that match no public company come back as
UNKNOWNand are skipped.Changes
src/agents/vision.js(new) —resolveTickersFromImages()fans out oneidentify_tickerspan per image. That span iskind: 'llm'and is annotated with a user message carryingimageParts: [{ mimeType, content }], so the image renders on the span. The image file is read as base64 so the same bytes are sent to OpenAI and attached to the trace.src/agents/orchestrator.js— image resolution happens inside the rootanalyze_portfolioagent span, so image translation and research share a single trace rather than producing two.src/main.js— argument parsing accepts local image files by extension, plus an explicit--image <path>override. Prints what each image resolved to. Evaluations run against the resolved ticker list.src/models.js—tickerFromImageSchema+validateTickerFromImage.logos/— three small wordmark PNGs (Apple, Google, NVIDIA; ~900 bytes each, 12K total) so the image path is runnable without supplying your own files.gpt-5.4-nano, matching the direction of Unpin openai, trim requirements, and update notebooks to gpt-5.4-nano #78. All remain overridable viaOPENAI_MODEL,OPENAI_VISION_MODEL,OPENAI_SEARCH_MODEL,OPENAI_EVAL_MODEL.Trace shape
Testing
Tested against @joizddog's dd-trace-js branch for image support, dd-trace-js#9684 (
jose/mlob-7916-llmobs-image-parts), installed as a local file dependency:The Apple wordmark renders on the
identify_tickerspan in the LLM Observability trace view, alongside the user message and the assistant's structured{ticker: "AAPL", ...}output.Two findings from that testing shaped this implementation:
OpenAI.createResponsechild span carries no image. The image has to be annotated by hand.imagePartsis only honored onllm-kind spans. Insdk.js, onlyspanKind === 'llm'routes totagLLMIO→tagMessages, which is the sole reader ofimageParts. Ontask/workflow/agentspans the input goes throughtagTextIOand the whole message array is stringified into an opaque value —imagePartspasses through as inert text with no warning.identify_tickerwas originally ataskspan, which is why no image appeared at first.Also verified end to end:
npm start -- AAPL GOOGL NVDA— unchanged text-only pathnpm start -- logos/apple.png logos/google.png NVDA— mixed path; resolves AAPL + GOOGL, analyzes three tickers, evals submittedtrace_idNote on dd-trace version
Image parts on LLMObs spans shipped in
dd-trace6.10.0, sopackage.jsonnow requires^6.10.0and the lockfile is bumped to match.The old lockfile pinned 6.4.0, which silently ignores
imagePartsand renders no image, so a plainnpm installon the previous revision resolved a version that could not show the feature even though the"latest"spec suggested otherwise. Re-verified on the published 6.10.0 after removing the local-checkout swap: theidentify_tickerspan carriesimage_parts: [{mime_type: "image/png", content: "<base64>"}]and the image renders in the trace view. The local-checkout dependency swap used during development is deliberately not committed.🤖 Generated with Claude Code