Skip to content

feat(apple): add guardrails option to createAppleProvider - #228

Open
robertherber wants to merge 3 commits into
callstackincubator:mainfrom
robertherber:feat/apple-guardrails-option
Open

feat(apple): add guardrails option to createAppleProvider#228
robertherber wants to merge 3 commits into
callstackincubator:mainfrom
robertherber:feat/apple-guardrails-option

Conversation

@robertherber

Copy link
Copy Markdown

Summary

Apps that transform legitimate but sensitive content — health data in our case — frequently hit guardrailViolation errors with the default Foundation Models guardrails. Apple provides a permissive guardrails mode for exactly this scenario (Improving the safety of generative model output).

This PR exposes it as a provider-level option:

const apple = createAppleProvider({
  guardrails: 'permissiveContentTransformations'
})

Changes

  • createAppleProvider accepts guardrails?: 'default' | 'permissiveContentTransformations', stored on AppleLLMChatLanguageModel and passed with every generateText/generateStream native call
  • AppleGenerationOptions gains an optional guardrails string field
  • AppleLLMImpl.swift builds the session model via a createModel(from:) helper: SystemLanguageModel(guardrails: .permissiveContentTransformations) when requested, SystemLanguageModel.default otherwise
  • Docs: new Guardrails section in website/src/docs/apple/generating.md

Defaults are unchanged — omitting the option keeps SystemLanguageModel.default. Guardrails.permissiveContentTransformations is available on all OS versions the framework supports (verified against the iOS 26.5 and 27.0 SDK swiftinterfaces), so no additional availability gating is needed.

Testing

  • tsc --noEmit passes for @react-native-ai/apple
  • Exercised the equivalent Swift change on-device (iPhone 16 Pro, iOS 27) in a health app that previously hit guardrail refusals

🤖 Generated with Claude Code

Apps transforming legitimate but sensitive content (for example health
data) hit guardrailViolation errors with the default Foundation Models
guardrails. Apple provides a permissive guardrails mode for exactly this
case; expose it as a provider-level option threaded through to the
LanguageModelSession's SystemLanguageModel:

  createAppleProvider({ guardrails: 'permissiveContentTransformations' })

Defaults to the system guardrails when omitted.

https://developer.apple.com/documentation/foundationmodels/improving-the-safety-of-generative-model-output
Copilot AI lite review requested due to automatic review settings August 12, 2026 11:53
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@robertherber is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an opt-in Apple Foundation Models guardrails mode at the provider level so apps that transform legitimate but sensitive content (e.g., health data) can reduce guardrailViolation refusals while keeping the default behavior unchanged when the option is omitted.

Changes:

  • Adds a guardrails option to createAppleProvider, stores it on AppleLLMChatLanguageModel, and forwards it on each native generateText / generateStream call.
  • Extends the native generation options to include an optional guardrails field and uses it to create the appropriate SystemLanguageModel in Swift.
  • Documents the new option in the Apple “Generating” docs.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
website/src/docs/apple/generating.md Adds documentation for the new provider-level guardrails option and links to Apple’s guidance.
packages/apple-llm/src/NativeAppleLLM.ts Extends TurboModule options with a guardrails field passed across the JS↔native boundary.
packages/apple-llm/src/ai-sdk.ts Adds guardrails to createAppleProvider and forwards it through AppleLLMChatLanguageModel to native calls.
packages/apple-llm/ios/AppleLLMImpl.swift Creates a SystemLanguageModel based on the requested guardrails mode instead of always using .default.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/apple-llm/src/NativeAppleLLM.ts

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is one blocking issue in the native bridge:

[P1] Forward guardrails through AppleLLM.mm

NativeAppleLLM.ts now includes guardrails in AppleGenerationOptions, and the AI SDK passes it to both native calls, but packages/apple-llm/ios/AppleLLM.mm reconstructs the NSDictionary *opts manually in both generateText and generateStream. Neither dictionary copies options.guardrails().

As a result, AppleLLMImpl.createModel(from:) never receives a "guardrails" key and always returns SystemLanguageModel.default; selecting permissiveContentTransformations currently has no effect for either API path.

Please add the generated guardrails value to both options dictionaries (with the appropriate generated enum/string conversion), and ideally cover this bridge path with a test or native build/codegen check.

The generateText/generateStream bridge rebuilds the options dictionary
manually, so the guardrails value never reached AppleLLMImpl and the
option was a no-op. Copy it in both paths, mirroring schema/tools.
@robertherber

robertherber commented Aug 12, 2026

Copy link
Copy Markdown
Author

Good catch, thank you — the option was indeed a no-op through the bridge. Fixed in the latest commit: both generateText and generateStream in AppleLLM.mm now copy options.guardrails() ?: [NSNull null] into the options dictionary, mirroring the schema/tools fields.

On verification: I ran generate-codegen-artifacts.js against the example app to confirm the generated accessor is a nullable NSString *guardrails() (string-literal unions degrade to String in codegen), so the ?: pattern matches the generated header. To be transparent about test coverage: I've verified the permissive-guardrails Swift behavior itself on-device (iOS 27, health app that previously hit guardrail refusals), but via an equivalent hardcoded patch — not this exact option plumbing end-to-end. There's no existing native test harness for the bridge in the repo that I could extend — happy to add one if you can point me at the shape you'd want.

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.

3 participants