Framework Adapter Comparison
Edictum ships eight framework adapters.
Right page if: you are comparing adapters to understand capability differences -- especially PII redaction support, block mechanisms, and postcondition effect limitations. Wrong page if: you already know your framework and need setup instructions -- go to its adapter page (e.g., https://docs.edictum.ai/docs/adapters/langchain). For a quick table of all adapters, see https://docs.edictum.ai/docs/adapters/overview. Gotcha: only LangChain, Agno, Semantic Kernel, Nanobot, and Google ADK can intercept tool results before the LLM sees them. CrewAI, Claude SDK, and OpenAI Agents are side-effect only for postconditions.
Edictum ships eight framework adapters. This guide helps you choose the right one and understand the tradeoffs.
Quick Comparison
| Framework | Integration Method | Can Redact Before LLM | Block Mechanism | Cost (same task) |
|---|---|---|---|---|
| LangChain | as_tool_wrapper() | Yes | Return "DENIED: reason" as ToolMessage | $0.025 |
| OpenAI Agents | as_guardrails() | Block only (reject_content) | reject_content(reason) | $0.018 |
| CrewAI | register() | No (side-effect only) | before_hook returns "DENIED: reason" | $0.040 |
| Agno | as_tool_hook() | Yes (hook wraps execution) | Hook returns block string | N/A |
| Google ADK | as_plugin() / as_agent_callbacks() | Yes | Plugin/callback returns block payload | N/A |
| Semantic Kernel | register(kernel) | Yes (filter modifies FunctionResult) | Filter sets terminate (configurable) + error | $0.008 |
| Claude SDK | to_hook_callables() | No (side-effect only) | Returns a deny response to the SDK | N/A |
| Nanobot | wrap_registry() | Yes (wraps execute) | Returns "[DENIED] reason" string | N/A |
Cost column reflects benchmarks from edictum-demo using each framework's default model. N/A indicates no published benchmark data.
Which Adapter Should I Use?
- Need full PII interception? -- Use LangChain, Agno, Semantic Kernel, Nanobot, or Google ADK. These adapters can replace the tool result before the LLM sees it.
- Cheapest per-task cost? -- Semantic Kernel ($0.008 per task in benchmarks).
- Simplest integration? -- Claude SDK or Agno. Both require minimal wiring.
- Need ADK plugin/callback enforcement? -- Use Google ADK adapter.
- Using CrewAI? -- CrewAI adapter is the only option. Note that CrewAI hooks are global (applied to every tool across all agents in the crew).
Per-Adapter Snippets
LangChain
from edictum import Edictum, Principal
from edictum.adapters.langchain import LangChainAdapter
from langgraph.prebuilt import ToolNode
guard = Edictum.from_yaml("rules.yaml")
adapter = LangChainAdapter(guard=guard, principal=Principal(role="analyst"))
wrapper = adapter.as_tool_wrapper()
# Pass to: ToolNode(tools=tools, wrap_tool_call=wrapper)OpenAI Agents
from edictum import Edictum, Principal
from edictum.adapters.openai_agents import OpenAIAgentsAdapter
from agents import function_tool
guard = Edictum.from_yaml("rules.yaml")
adapter = OpenAIAgentsAdapter(guard=guard, principal=Principal(role="assistant"))
input_gr, output_gr = adapter.as_guardrails()
# Pass to: @function_tool(tool_input_guardrails=[input_gr], tool_output_guardrails=[output_gr])CrewAI
from edictum import Edictum, Principal
from edictum.adapters.crewai import CrewAIAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = CrewAIAdapter(guard=guard, principal=Principal(role="researcher"))
adapter.register()
# Hooks are now globally registered for all CrewAI tool callsAgno
from edictum import Edictum, Principal
from edictum.adapters.agno import AgnoAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = AgnoAdapter(guard=guard, principal=Principal(role="assistant"))
hook = adapter.as_tool_hook()
# Pass to: Agent(tool_hooks=[hook])Semantic Kernel
from edictum import Edictum, Principal
from edictum.adapters.semantic_kernel import SemanticKernelAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = SemanticKernelAdapter(guard=guard, principal=Principal(role="analyst"))
adapter.register(kernel)
# Filter is now registered on the kernel instanceGoogle ADK
from edictum import Edictum, Principal
from edictum.adapters.google_adk import GoogleADKAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = GoogleADKAdapter(guard=guard, principal=Principal(role="analyst"))
plugin = adapter.as_plugin()
# Pass to your ADK Runner: plugins=[plugin]Claude SDK
from edictum import Edictum, Principal
from edictum.adapters.claude_agent_sdk import ClaudeAgentSDKAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = ClaudeAgentSDKAdapter(guard=guard, principal=Principal(role="sre"))
hooks = adapter.to_hook_callables()
# Use in your agent loop — see bridge recipe in Claude SDK adapter docsNanobot
from edictum import Edictum, Principal
from edictum.adapters.nanobot import NanobotAdapter
guard = Edictum.from_yaml("rules.yaml")
adapter = NanobotAdapter(guard=guard, principal=Principal(role="user"))
governed_registry = adapter.wrap_registry(agent.tool_registry)
# Replace: agent.tool_registry = governed_registryKnown Limitations
LangChain
as_middleware()is sync-only. If an asyncio event loop is already running (Jupyter, FastAPI), useas_tool_wrapper()(handles nested loops via ThreadPoolExecutor) oras_async_tool_wrapper(). See LangChain adapter docs.
OpenAI Agents
- Guardrails are per-tool via
@function_tool(), not per-agent. Input and output guardrails are separate functions; the adapter correlates them using insertion-order (FIFO), which assumes sequential tool execution. See OpenAI Agents adapter docs.
CrewAI
- Hooks are global -- they apply to every tool across all agents in the crew. There is no per-agent hook scoping. See CrewAI adapter docs.
- Side-effect only --
on_postcondition_warncallbacks fire for side effects (logging, alerting) but cannot replace the tool result. Postconditionredact/blockeffects setPostCallResult.resultfor wrapper consumers but cannot modify what CrewAI passes to the model.
Agno
- Tool callables must accept keyword arguments (the adapter spreads the args dict with
**arguments). See Agno adapter docs.
Google ADK
- Use plugin path for runner-wide enforcement and callback path for live/streaming mode. See Google ADK adapter docs.
Semantic Kernel
- By default,
context.terminate = Trueon block stops all auto-invocations in the current turn, not just the blocked tool. Setterminate_on_deny=Falseto allow remaining tool calls to proceed. See Semantic Kernel adapter docs.
Claude SDK
- Side-effect only -- the hook callables (
to_hook_callables()) cannot replace the tool result. PII detection is logged but not intercepted before reaching the model. Postconditionredact/blockeffects setPostCallResult.resultfor wrapper consumers but cannot modify the SDK's result flow. A warning is logged at adapter construction when postconditions declare these effects. See Claude SDK adapter docs.
Nanobot
GovernedToolRegistry.execute()always returns a string (matching nanobot'sToolRegistryrule). Non-string results from the inner registry are converted viastr(). See Nanobot adapter docs.
OpenAI Agents (postcondition enforcement)
- The output guardrail can
.allow()or.reject_content()but cannot substitute the tool result. With one pending tool call, postconditionaction: blockon pure/read tools returns.reject_content(). When calls overlap, the native output guardrail skips postcondition evaluation because it cannot identify which tool produced the output. Use the wrapper path with explicit call IDs for guaranteed postcondition enforcement under concurrency. Postconditionaction: redactis not supported because native guardrails cannot transform tool results.
Last updated on