Skip to content

Add 7 agent-framework integrations (OpenAI Agents, LangChain, ADK, smolagents, DSPy, Strands, Letta) - #53

Merged
chiruu12 merged 2 commits into
devfrom
feat/integrations-openai-langchain-adk-smolagents
Jun 27, 2026
Merged

Add 7 agent-framework integrations (OpenAI Agents, LangChain, ADK, smolagents, DSPy, Strands, Letta)#53
chiruu12 merged 2 commits into
devfrom
feat/integrations-openai-langchain-adk-smolagents

Conversation

@chiruu12

@chiruu12chiruu12 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds seven widely-used agent frameworks to the integration hub, each following the existing Haystack-style split: a framework-free core (matrix-tested, no SDK needed) plus lazy native wiring (live-tested against the real SDK). Tool policy is always enforced locally (fail-closed).

FrameworkExtraModuleIntegration surface
OpenAI Agents SDKopenai-agentsopenai_agents.pynative input/output guardrails (tripwire_triggered) + local tool guard
LangChainlangchainlangchain.pyLCEL RunnableLambda input/output guards + on_tool_start callback handler
Google ADKgoogle-adkgoogle_adk.pybefore_model_callback (→LlmResponse) + before_tool_callback (→block dict)
smolagentssmolagentssmolagents.pytask gate + final_answer_checks + tool guard
DSPydspydspy.pyunplug_guard_module (wraps any dspy.Module) + dspy_guard_tool for dspy.ReAct
Strands Agentsstrandsstrands.pyUnplugHookProvider cancels destructive tool calls via event.cancel_tool
Lettalettaletta.pyclient-boundary input guard + scan_letta_response over response.messages

Per framework: optional extra (folded into the integrations meta-extra), a guide under integrations/<name>/, a runnable examples/<name>_hooks_demo.py, a live test, and security-matrix cases.

Docs & linking

  • integrations/README.md + docs/INTEGRATIONS.md framework tables and demo lists updated
  • Security matrix extended 40 → 62 angles (integrations/TESTING.md)
  • pyproject.toml extras + uv.lock re-locked (added: openai-agents 0.17.7, google-adk 2.3.0, smolagents 1.26.0, dspy 3.2.1, strands-agents 1.45.0, letta-client 1.12.1)
  • .github/workflows/integrations-live.yml: 7 new live-CI legs
  • CHANGELOG.md[Unreleased] entry

Test plan

  • ruff check + ruff format --check clean across adapters, examples, and tests
  • 119 framework-free matrix + adapter tests pass (62-angle matrix + extended adapter suite)
  • All 7 demos run with correct allow/block output
  • DSPy / Strands / Letta live tests pass against the real installed SDKs (18 live tests: dspy 3.2.1, strands-agents 1.45.0, letta-client 1.12.1)
  • OpenAI Agents / LangChain / ADK / smolagents live tests collect + skip cleanly without frameworks (exit-5 tolerated; each runs in its own CI leg)
  • CI: Integrations (live) legs for all 7 extras

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown

coverage

SDK Coverage •
FileStmtsMissCoverMissing
src/unplug/integrations
dspy.py61296%67, 114
google_adk.py45393%71–73
langchain.py58493%66, 152–154
letta.py41978%67, 69–72, 74, 97, 105–106
openai_agents.py661971%68–72, 74–82, 89–93
smolagents.py31487%51–54
strands.py531179%57, 59–62, 64, 85–86, 101–102, 127
TOTAL6765116282%

TestsSkippedFailuresErrorsTime
103025 💤0 ❌0 🔥28.990s ⏱️

@greptile-apps

greptile-appsBot commented Jun 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds several agent framework integrations to the SDK. The main changes are:

  • New adapters for OpenAI Agents, LangChain, Google ADK, smolagents, DSPy, Strands, and Letta.
  • Optional extras, live test jobs, and lockfile updates for the new frameworks.
  • New framework guides, demos, and integration docs.
  • Expanded security matrix coverage for the added adapters.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

FilenameOverview
sdk/src/unplug/integrations/google_adk.pyAdds ADK request scanning and before-model/before-tool callback helpers.
sdk/src/unplug/integrations/langchain.pyAdds LangChain input/output runnable guards and a tool-start callback handler.
sdk/src/unplug/integrations/openai_agents.pyAdds OpenAI Agents input/output guardrails and a local tool guard.
sdk/src/unplug/integrations/smolagents.pyAdds smolagents task, final-answer, and tool guards.

Reviews (2): Last reviewed commit: "Add DSPy, Strands, Letta integrations" | Re-trigger Greptile

Comment threadsdk/src/unplug/integrations/google_adk.py
Comment on lines +132 to +133
name = (serialized or {}).get("name", "tool")
decision = self._hooks.before_tool_call(name, {"input": input_str})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1securityTool Arguments Lose Structure

LangChain passes this callback the tool input, but the adapter wraps it as {"input": input_str} before policy evaluation. For structured tool calls, the guard no longer sees fields like command, query, path, or url, so argument-sensitive blocking can miss a dangerous payload that would have been blocked with the original args.

Fix in Claude Code

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Fixed in #59. on_tool_start now prefers the structured inputs dict that newer LangChain forwards (resolved in _tool_call_args), so field-sensitive policy sees command / query / path / url; it falls back to {"input": input_str} only when structured args aren't provided. Unit test covers both paths.

Comment on lines +85 to +93
def _coerce_output_text(value: Any) -> str:
"""Flatten an agent output (``str``, message object, or model) to text."""
if isinstance(value, str):
return value
for attr in ("response", "content", "text", "output", "final_output"):
inner = getattr(value, attr, None)
if isinstance(inner, str):
return inner
return str(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2securityStructured Output Can Evade Scan

When an OpenAI agent returns structured output, this path only checks a few attributes and then scans str(value). If the secret or unsafe content is stored in another field and the object's string form redacts or omits it, the output guardrail can allow content that is still returned to the caller.

Fix in Claude Code

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Fixed in #59. _coerce_output_text now flattens the whole object via flatten_text instead of returning the first .content/.text attribute, so a secret stashed in another field is scanned rather than omitted by the object's primary string form.

Comment on lines +48 to +54
def _coerce_text(value: Any) -> str:
if isinstance(value, str):
return value
text = getattr(value, "text", None)
if isinstance(text, str):
return text
return str(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1securityStructured Answers Lose Fields

The smolagents final-answer path only scans .text or str(value). If a final answer is a dict, model, or tool result whose string form hides a secret field, Unplug scans the harmless representation while the real final answer can still contain the unsafe value.

Fix in Claude Code

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Fixed in #59. _coerce_text now flattens structured answers (dict / model / tool result) via flatten_text rather than trusting .text or str(value), so a secret hidden in a sibling field is included in what gets scanned. Regression test added.

Comment on lines +84 to +86
decision = h.scan_agent_output(_coerce_text(final_answer))
require_allowed(decision)
return True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2Blocked Answers Become Silent Failures

This check raises on a blocked final answer, and smolagents treats check exceptions as failed checks that can leave the final answer as None. A user can receive an unexplained empty result instead of a clear blocked response, making guardrail blocks look like agent failures.

Fix in Claude Code

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Intentional. Raising in a final_answer_checks validator is smolagents' documented way to fail a check, and require_allowed raises with a descriptive message (Output blocked: …) so the block is explainable rather than a silent None. Keeping the fail-closed behavior — surfacing the message is a framework-side concern, not a reason to allow the answer through.

@chiruu12chiruu12 changed the title Add OpenAI Agents, LangChain, Google ADK & smolagents integrationsAdd 7 agent-framework integrations (OpenAI Agents, LangChain, ADK, smolagents, DSPy, Strands, Letta)Jun 26, 2026
@chiruu12
chiruu12 merged commit f00049e into devJun 27, 2026
19 checks passed
This was referenced Jun 27, 2026
chiruu12 added a commit that referenced this pull request Jun 27, 2026
version 0.4.1 -> 0.5.0; CHANGELOG [Unreleased] -> [0.5.0]. Ships 10 framework integrations (#53 + #56) and the 72-angle security matrix.
@chiruu12chiruu12 mentioned this pull request Jun 27, 2026
chiruu12 added a commit that referenced this pull request Jun 27, 2026
version 0.4.1 -> 0.5.0; CHANGELOG [Unreleased] -> [0.5.0]. Ships 10 framework integrations (#53 + #56) and the 72-angle security matrix.
Sign up for freeto 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

@chiruu12