Skip to content

feat(bedrock): attach Dify app_id as Converse requestMetadata (opt-in)#3201

Draft
mas-sakai wants to merge 12 commits into
langgenius:mainfrom
mas-sakai:feat/bedrock-app-id-request-metadata
Draft

feat(bedrock): attach Dify app_id as Converse requestMetadata (opt-in)#3201
mas-sakai wants to merge 12 commits into
langgenius:mainfrom
mas-sakai:feat/bedrock-app-id-request-metadata

Conversation

@mas-sakai
Copy link
Copy Markdown

Summary

Add opt-in support for forwarding the Dify app_id to Amazon Bedrock's Converse API requestMetadata field so CloudWatch invocation logs can be filtered per Dify app for cost and usage tracking.

  • New provider credential enable_request_metadata (select, default disabled) exposed in provider/bedrock.yaml.
  • When enabled, _generate_with_converse reads app_id from the current Dify session via dify_plugin.get_current_session() and attaches {dify_app_id, dify_source} to requestMetadata on Converse / ConverseStream calls.
  • New helper module models/llm/_metadata.py with normalize_metadata_value (sanitizes to the Bedrock-allowed character set [a-zA-Z0-9\s:_@$#=/+,-.], truncates to 256 chars, preserves mixed case) and build_dify_request_metadata (returns None for empty app_id so the caller can skip attaching).
  • Scope: Converse route only. The legacy InvokeModel path does not support requestMetadata and is intentionally untouched.
  • Graceful degradation: dify_plugin is imported lazily inside a try/except ImportError, so SDK versions without get_current_session (currently anything before sdks#313 ships) just skip metadata attachment without raising.
  • Version bump 0.0.660.0.67.

Why opt-in

Default disabled to preserve current behavior for tenants who don't want Dify identifiers landing in their CloudWatch logs. Operators opt in per-credential in the Dify console.

Temporary SDK pin

pyproject.toml points dify_plugin at the feat/pass-session-to-model-plugins branch of ryuta-kobayashi-ug/dify-plugin-sdks (PR langgenius/dify-plugin-sdks#311 / commit reference #313) because get_current_session() is not yet available in a released SDK. The branch SDK is 0.9.0 and requires tiktoken>=0.12.0, so the bedrock tiktoken constraint is bumped accordingly and uv.lock is refreshed.

Action item before merge: once the SDK PR is merged and a new release is published, revert this branch pin back to a normal release-version constraint. Calling out explicitly so it isn't shipped as-is.

Files

  • models/bedrock/provider/bedrock.yaml — new enable_request_metadata credential entry.
  • models/bedrock/models/llm/_metadata.py — new helpers + tests.
  • models/bedrock/models/llm/llm.py — gated metadata attachment in _generate_with_converse.
  • models/bedrock/tests/test_metadata.py — unit tests for the normalize + build helpers.
  • models/bedrock/manifest.yaml — version bump.
  • models/bedrock/pyproject.toml / uv.lock — temporary SDK branch pin + tiktoken bump.

Test plan

  • pytest models/bedrock/tests/test_metadata.py — UUID passthrough, invalid-char replacement, allowed-special-char preservation, mixed-case preservation, 256-char truncation, empty input, non-ASCII replacement, None/"" short-circuit, source marker present.
  • Manual: in a Dify instance running this plugin with the patched SDK, enable enable_request_metadata, invoke a Bedrock Converse model, and verify the CloudWatch model-invocation log entry carries dify_app_id matching the calling app and dify_source=dify.
  • Manual: with enable_request_metadata=disabled (default), confirm no requestMetadata field is sent on the Converse request (regression check via packet capture or model invocation logs).
  • Manual: with an older dify_plugin SDK that lacks get_current_session, confirm the plugin still loads and Converse calls succeed without metadata (graceful degradation).

Refs

🤖 Generated with Claude Code

mas-sakai and others added 5 commits May 25, 2026 17:54
Add `_metadata.py` with `normalize_metadata_value` and
`build_dify_request_metadata` helpers that produce Bedrock-compliant
requestMetadata dicts. UUID app_ids pass through unchanged; other
strings are restricted to `[a-zA-Z0-9\s:_@$#=/+,-.]` (per the Converse
API constraint) and truncated to 256 characters. Mixed case is
preserved — unlike Vertex AI labels, Bedrock requestMetadata accepts
uppercase.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In `_generate_with_converse`, when the `enable_request_metadata`
credential is set to `enabled`, read `app_id` from the current Dify
session (via `dify_plugin.get_current_session`) and attach it as
`{dify_app_id, dify_source}` on the Converse API `requestMetadata`
field. CloudWatch invocation logs then carry the marker, enabling
per-app filtering for cost and usage tracking.

Scoped to the Converse route only — the legacy InvokeModel path does
not support requestMetadata. `dify_plugin` is imported lazily and
wrapped in `try/except ImportError` so older SDK versions degrade
gracefully (no metadata attached). Tests cover the normalize helper
and `build_dify_request_metadata` short-circuits.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surface `enable_request_metadata` as a provider-level select (default
`disabled`) in `provider/bedrock.yaml`. When enabled, the Converse
route attaches Dify metadata to Bedrock requestMetadata for CloudWatch
log filtering. Description is on the `help` field so it renders below
the input in the Dify console.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Point `dify_plugin` at the `feat/pass-session-to-model-plugins` branch
of `ryuta-kobayashi-ug/dify-plugin-sdks` (PR langgenius#313) so the Converse
route can import `get_current_session()`. The branch SDK is version
0.9.0, which requires `tiktoken>=0.12.0` — bump bedrock's tiktoken
constraint accordingly. Refresh `uv.lock`. This pin is temporary for
the review period; revert to a release-version constraint once langgenius#313 is
merged and a new SDK is published.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mas-sakai mas-sakai temporarily deployed to models/bedrock May 26, 2026 02:43 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for attaching Dify metadata (such as dify_app_id and dify_source) to Amazon Bedrock Converse API requests as requestMetadata for CloudWatch logging, controlled by a new enable_request_metadata credential setting. The review feedback suggests catching generic exceptions instead of just ImportError during session retrieval to prevent crashes, adding Chinese translations for the new credential configuration, and ensuring robust string conversion in the metadata normalization helper.

Comment thread models/bedrock/models/llm/llm.py Outdated
Comment thread models/bedrock/provider/bedrock.yaml Outdated
Comment thread models/bedrock/models/llm/_metadata.py Outdated
mas-sakai and others added 2 commits May 26, 2026 11:54
…view

Broaden the exception catch around dify_plugin.get_current_session() to
`Exception` so that contextvars LookupError, RuntimeError, or any other
runtime issue in best-effort telemetry cannot break model generation
(previously only ImportError was caught, which left the runtime path
unprotected).

In normalize_metadata_value, coerce non-string input via str() before
the empty-check so numeric `0` becomes "0" instead of being dropped as
falsy. Adds a regression test.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surface zh_Hans copy for the new credential's label, enabled/disabled
option labels, and help text, and switch help to the `help.text.<lang>`
form used by sibling entries (endpoint_url, proxy_url) so structure is
consistent across the provider schema.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mas-sakai mas-sakai temporarily deployed to models/bedrock May 26, 2026 02:54 — with GitHub Actions Inactive
Resolve conflicts in bedrock pyproject/uv.lock by taking upstream's
boto3/botocore/tiktoken bumps while retaining the sdks#313 branch pin
for dify_plugin (get_current_session is not in a released SDK yet).
…questMetadata per review

- Widen `normalize_metadata_value` / `build_dify_request_metadata` type
  hints to `Any` to match the str() coercion the implementations
  already do.
- Reject only `None` and `""` in `build_dify_request_metadata`; other
  falsy values (e.g. numeric 0) flow through to normalization rather
  than being silently dropped, matching the design intent for the
  helper.
- Centralize the session lookup + metadata injection in a new
  `apply_dify_request_metadata_if_enabled` helper that merges into
  existing `parameters["requestMetadata"]` instead of overwriting it.
  Aligns with the OpenAI plugin's `apply_dify_metadata_if_enabled`
  structure.
- Add tests covering the merge-with-existing path, the non-dict
  fallback, the disabled/missing credential no-ops, the session-lookup
  failure swallowing, and the falsy-but-non-empty input case.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mas-sakai mas-sakai temporarily deployed to models/bedrock May 26, 2026 04:59 — with GitHub Actions Inactive
Marketplace rejects duplicate versions; main is at 0.0.67, so bump to
0.0.68. uv.lock already tracks the dify_plugin git+ fork; rerunning
uv lock confirms it stays resolved against sdks langgenius#313.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mas-sakai mas-sakai temporarily deployed to models/bedrock May 27, 2026 01:08 — with GitHub Actions Inactive
Applies the same ordering fix discussed on langgenius#3168 to the Bedrock helper:
slice down to 256 chars before the regex sub() so pathological inputs do
not incur unbounded regex work. The character set used by sub() replaces
1:1 by length, so the trailing truncation is no longer needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rmalize per review

Per gemini-code-assist on langgenius#3233. Aligns 4 plugins on the same
'no side effects on existing args' principle.

Refs: langgenius/dify#35772, langgenius/dify-plugin-sdks#311

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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