Skip to content

Move online ingredient analysis from OpenRouter to DeepSeek - #4

Merged
HedgehogsGX merged 1 commit into
mainfrom
switch-analysis-provider-to-deepseek
Sep 16, 2026
Merged

HedgehogsGX merged 1 commit into
mainfrom
switch-analysis-provider-to-deepseek

Conversation

@HedgehogsGX

Copy link
Copy Markdown
Owner

Switches the online analysis provider from OpenRouter to DeepSeek's official API, and the model from z-ai/glm-5.3-flash to deepseek-flash.

The /models endpoint on the current key serves exactly two models — deepseek-flash and deepseek-v4-pro — so flash is the like-for-like replacement for the tier we were on.

Privacy claim removed rather than restated

reasoning and provider are dropped from the request body, along with the X-OpenRouter-Title header. Both are OpenRouter-only knobs that DeepSeek silently ignores.

provider.data_collection=deny matters more than the other two: it was being cited in the privacy policy and README as a limit on upstream retention. Sending a field that does nothing while advertising it to users would be a guarantee with nothing behind it, so the claim is deleted from the UI copy and the README instead of reworded.

Consent and cached credentials

OnlineAnalysisConsent.currentVersion goes to 2. The upstream provider changed, so every existing online consent is void and falls back to local analysis until the user re-confirms against the new data boundary — this is the behaviour ScanStore already implements for a version mismatch.

The Keychain slot moves to v3 for the same reason, so an OpenRouter key cached by an earlier Debug build can never be replayed against DeepSeek.

Scope

  • Transport allowlist narrowed to api.deepseek.com + /chat/completions; every other host still fails as invalidEndpoint.
  • OPENROUTER_API_KEY renamed to DEEPSEEK_API_KEY across xcconfigs, project.yml, the scheme and the tests. The local credential still lives only in the gitignored Config/Secrets.xcconfig and appears in no tracked file.
  • The system prompt is unchanged, and so is the strict response contract (exact field set, byte-for-byte source_text, one result per id).
  • Swift type names (OpenRouterConfiguration and friends) are deliberately left alone — renaming them is cosmetic churn across ~10 files and belongs in its own PR.

Verification

  • Full suite: 83 tests, 0 failures.
  • Live smoke test against DeepSeek through the app's real code path: HTTP 200, finish_reason=stop.
  • A real Pepsi bottle label driven end to end — Vision OCR, local parser, GB 2760 matcher, live model — returning all 11 insights with the contract intact.

Tests/LabelPipelineHarnessTests is also fixed here: it used XCTUnwrap where it meant XCTSkip, so it failed the default suite whenever no photo was supplied.

Known issue, not fixed here

Driving the real label through the pipeline surfaced a pre-existing parser bug unrelated to this switch: IngredientTextParser.truncateAtMetadataHeading does not cut compound headings like 生产商及委托方:, so a manufacturer's company name is sent to the model even though the privacy copy promises only ingredient names leave the device. 生产商 is followed by and 委托方 is preceded by , so both of the function's boundary guards reject. Tracked separately.

Point the analysis provider at DeepSeek's official Chat Completions
endpoint and the deepseek-flash model, replacing OpenRouter and
z-ai/glm-5.3-flash. The /models endpoint on the current key serves only
deepseek-flash and deepseek-v4-pro, so flash is the like-for-like tier.

Drop the reasoning and provider fields from the request body along with
the X-OpenRouter-Title header. Both are OpenRouter-only knobs that
DeepSeek silently ignores, and provider.data_collection=deny was being
cited in the privacy policy as a limit on upstream retention. Continuing
to send a field that does nothing while advertising it to users would
have been a guarantee with nothing behind it, so the claim is removed
from the UI copy and the README rather than restated.

Because the upstream provider changed, bump OnlineAnalysisConsent to
version 2. Every existing online consent is void and falls back to local
analysis until the user re-confirms against the new data boundary. Move
the Keychain slot to v3 for the same reason, so an OpenRouter key cached
by an earlier build can never be replayed against DeepSeek.

Narrow the transport allowlist to api.deepseek.com/chat/completions and
rename OPENROUTER_API_KEY to DEEPSEEK_API_KEY across the xcconfigs,
scheme and tests. The local credential still lives only in the gitignored
Config/Secrets.xcconfig.

Also fix Tests/LabelPipelineHarnessTests, which used XCTUnwrap where it
meant XCTSkip and so failed the default suite whenever no photo was
supplied.

Verified with the full suite (83 tests), the live smoke test against
DeepSeek, and a real Pepsi label driven end to end through OCR, the local
parser and the live model.
@capy-ai

capy-ai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Capy couldn't review this pull request because not cat's workspace is out of credits, add credits or enable auto-reload to resume automatic reviews.

Open in Capy

@HedgehogsGX
HedgehogsGX merged commit 5156a31 into main Sep 16, 2026
2 checks passed
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.

1 participant