ag-ui-dify-adapter

May 28, 2026 · View on GitHub

AG-UI protocol adapter for Dify — translates Dify API responses to AG-UI streaming events, enabling Dify-powered AI agents to integrate with any AG-UI-compatible frontend.

Features

  • All 5 Dify app types: Chatbot, Chatflow, Agent, Workflow, Completion
  • 24+ AG-UI event types: TEXT_MESSAGE, TOOL_CALL, TOOL_CALL_RESULT, REASONING, STATE_SNAPSHOT, MESSAGES_SNAPSHOT, STEP, CUSTOM, RAW, RUN
  • Tool call lifecycle: TOOL_CALL_STARTARGSENDRESULT for Agent ReAct loops
  • Reasoning events: <think> tag streaming detection → REASONING_START/MESSAGE_START/CONTENT/MESSAGE_END/END
  • Snapshots: MESSAGES_SNAPSHOT + STATE_SNAPSHOT emitted at start of every run
  • Streaming: Real-time text streaming with <think> block separation
  • Multi-turn conversation: thread_idconversation_id tracking
  • State & context: AG-UI state/context → Dify input variables
  • Single-port multi-agent: One server, multiple Dify apps routed by path
  • YAML config: Clean config.yaml — no JSON crammed into env vars
  • .env auto-load: Reads .env file automatically via python-dotenv
  • RAW passthrough: Unrecognized Dify events forwarded as RAW, never dropped
  • Async: Full async support with httpx

Installation

pip install ag-ui-dify-adapter

For the HTTP server:

pip install ag-ui-dify-adapter[server]

Quick Start

Library

import asyncio
from ag_ui_dify import DifyAgent, DifyConfig, DifyAppType
from ag_ui.core import RunAgentInput, UserMessage

async def main():
    agent = DifyAgent(DifyConfig(
        api_key="app-xxx",
        base_url="https://api.dify.ai/v1",
        app_type=DifyAppType.AGENT,
    ))

    input = RunAgentInput(
        thread_id="thread-1",
        run_id="run-1",
        state=None,
        messages=[UserMessage(id="u1", role="user", content="Hello!")],
        tools=[], context=[], forwarded_props={},
    )

    async for event in agent.run(input):
        print(event.model_dump_json(by_alias=True))

asyncio.run(main())

HTTP Server

Three ways to configure agents — pick one:

YAML config file (recommended):

# config.yaml
base_url: http://localhost/v1
agents:
  agent-a:
    key: app-xxx
    type: agent
  wf-b:
    key: app-yyy
    type: workflow
uvicorn ag_ui_dify:create_app --port 8080

Environment variables:

# Single agent
DIFY_API_KEY=app-xxx DIFY_APP_TYPE=agent \
  uvicorn ag_ui_dify:create_app --port 8080

# Multi-agent (single port)
DIFY_AGENTS='{"agent-a":{"key":"app-xxx","type":"agent"}}' \
  uvicorn ag_ui_dify:create_app --port 8080

.env file (auto-loaded):

# .env
DIFY_AGENTS={"agent-a":{"key":"app-xxx","type":"agent"}}
uvicorn ag_ui_dify:create_app --port 8080

API keys stay server-side — never exposed to clients.

# Endpoints
curl -X POST http://localhost:8080/agent-a \
  -H "Content-Type: application/json" \
  -d '{"threadId":"t1","runId":"r1","messages":[{"id":"u1","role":"user","content":"Hello"}],"tools":[],"context":[]}'

curl http://localhost:8080/health   # → {"status":"ok"}
curl http://localhost:8080/info     # → agent discovery

Dify → AG-UI Event Mapping

Agent App

Dify SSE EventAG-UI Event(s)
agent_thought (with thought)STEP_STARTED + CUSTOM (thought)
agent_thought (with tool)TOOL_CALL_START + TOOL_CALL_ARGS + TOOL_CALL_END
agent_thought (with observation)TOOL_CALL_RESULT
agent_message (first)TEXT_MESSAGE_START
agent_messageTEXT_MESSAGE_CONTENT (with <think>REASONING)
message_replaceCUSTOM
message_fileCUSTOM
message_endTEXT_MESSAGE_END + RUN_FINISHED

Workflow App

Dify SSE EventAG-UI Event(s)
workflow_startedRUN_STARTED + TEXT_MESSAGE_START
node_started / node_retrySTEP_STARTED
node_finishedSTEP_FINISHED
agent_logSTEP_STARTED / STEP_FINISHED
iteration_started/completedSTEP_STARTED / STEP_FINISHED
loop_started/completedSTEP_STARTED / STEP_FINISHED
text_chunkTEXT_MESSAGE_CONTENT (with <think>REASONING)
text_replaceCUSTOM
workflow_pausedCUSTOM + RUN_FINISHED
human_input_*CUSTOM
workflow_finishedTEXT_MESSAGE_END + RUN_FINISHED

Chatbot / Chatflow / Completion App

Chatbot (chat), Chatflow (advanced-chat), and Completion share the same event format — message for text, message_end to finish.

Dify SSE EventAG-UI Event(s)
message (first)TEXT_MESSAGE_START
messageTEXT_MESSAGE_CONTENT (with <think>REASONING)
message_replaceCUSTOM
message_fileCUSTOM
tts_message / tts_message_endCUSTOM
message_endTEXT_MESSAGE_END + RUN_FINISHED

All app types: MESSAGES_SNAPSHOT + STATE_SNAPSHOT at start, RUN_STARTED, RUN_ERROR on error, ping ignored, unknown events → RAW.

API Reference

DifyAgent

agent = DifyAgent(DifyConfig(
    api_key="app-xxx",          # Required: Dify API key
    base_url="...",             # Default: https://api.dify.ai/v1
    app_type=DifyAppType.AGENT, # Auto-detected if omitted
    user="ag-ui-user",          # Default user identifier
    timeout=120.0,              # HTTP timeout in seconds
))
async for event in agent.run(run_input):
    ...

HTTP Server

from ag_ui_dify import create_app, load_agents
import uvicorn

# Programmatic
agents = load_agents()  # reads DIFY_AGENTS / DIFY_API_KEY from env
app = create_app()      # Starlette app with /info, /health, /<agent>
uvicorn.run(app, port=8080)
Routes:
  POST /<agent-name>   AG-UI RunAgentInput → SSE stream
  GET  /info           Agent discovery
  GET  /health         Health check

DifyClient (low-level)

client = DifyClient(config)
async for evt in client.stream_chat(query="Hello", inputs={}): ...
async for evt in client.stream_workflow(inputs={"url": "..."}): ...
async for evt in client.stream_completion(inputs={}): ...
await client.stop_chat(task_id="...")

Project Structure

ag_ui_dify/
├── __init__.py           # Package exports
├── types.py              # Dify type definitions (Pydantic models)
├── dify_client.py        # Async HTTP client for all Dify endpoints
├── event_translator.py   # Event translators (Chat/Agent/Workflow/Completion)
├── agent.py              # DifyAgent main adapter
└── server.py             # Starlette single-port multi-agent server

Verification Status

All 4 Dify app types verified against a real Dify instance:

App TypeStatusCoverage
AgentTool calls, reasoning chain, multi-turn conversation
WorkflowNode execution, agent_log sub-steps, text output
ChatStreaming text, message lifecycle
CompletionStreaming text, <think> → REASONING events

Requirements

  • Python >= 3.9
  • ag-ui-protocol >= 0.1.17
  • httpx >= 0.27.0
  • pydantic >= 2.11.0
  • starlette >= 0.40.0 (optional, for HTTP server)
  • uvicorn (optional, for HTTP server)

License

MIT