AG-UI protocol adapter for Dify — translates Dify API responses to AG-UI streaming events
Project description
ag-ui-dify-adapter
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 4 Dify app types: Chat, 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_START→ARGS→END→RESULTfor Agent ReAct loops - Reasoning events:
<think>tag streaming detection →REASONING_START/MESSAGE_START/CONTENT/MESSAGE_END/END - Snapshots:
MESSAGES_SNAPSHOT+STATE_SNAPSHOTemitted at start of every run - Streaming: Real-time text streaming with
<think>block separation - Multi-turn conversation:
thread_id↔conversation_idtracking - 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
.envfile 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 Event | AG-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_message |
TEXT_MESSAGE_CONTENT (with <think> → REASONING) |
message_replace |
CUSTOM |
message_file |
CUSTOM |
message_end |
TEXT_MESSAGE_END + RUN_FINISHED |
Workflow App
| Dify SSE Event | AG-UI Event(s) |
|---|---|
workflow_started |
RUN_STARTED + TEXT_MESSAGE_START |
node_started / node_retry |
STEP_STARTED |
node_finished |
STEP_FINISHED |
agent_log |
STEP_STARTED / STEP_FINISHED |
iteration_started/completed |
STEP_STARTED / STEP_FINISHED |
loop_started/completed |
STEP_STARTED / STEP_FINISHED |
text_chunk |
TEXT_MESSAGE_CONTENT (with <think> → REASONING) |
text_replace |
CUSTOM |
workflow_paused |
CUSTOM + RUN_FINISHED |
human_input_* |
CUSTOM |
workflow_finished |
TEXT_MESSAGE_END + RUN_FINISHED |
Chat / Completion App
| Dify SSE Event | AG-UI Event(s) |
|---|---|
message (first) |
TEXT_MESSAGE_START |
message |
TEXT_MESSAGE_CONTENT (with <think> → REASONING) |
message_replace |
CUSTOM |
message_file |
CUSTOM |
tts_message / tts_message_end |
CUSTOM |
message_end |
TEXT_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 Type | Status | Coverage |
|---|---|---|
| Agent | ✓ | Tool calls, reasoning chain, multi-turn conversation |
| Workflow | ✓ | Node execution, agent_log sub-steps, text output |
| Chat | ✓ | Streaming text, message lifecycle |
| Completion | ✓ | Streaming 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
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ag_ui_dify_adapter-0.1.4.tar.gz.
File metadata
- Download URL: ag_ui_dify_adapter-0.1.4.tar.gz
- Upload date:
- Size: 59.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
696b6ec575d33bd65c7f7e385cb65c9c87582ebf9258aa0fbdefc823f93d2bba
|
|
| MD5 |
0ce808caf57b37aec6e63f1c1e48b6f0
|
|
| BLAKE2b-256 |
b997a84070681d076ac3de5bdc687a3e7f963abd473cda8a7508d0ebb6980879
|
File details
Details for the file ag_ui_dify_adapter-0.1.4-py3-none-any.whl.
File metadata
- Download URL: ag_ui_dify_adapter-0.1.4-py3-none-any.whl
- Upload date:
- Size: 44.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f77150aef166ba46c4599967089089b24072e0502db2a7680cff22b6e40dd22
|
|
| MD5 |
84b42c716a148168e5166946911e35ba
|
|
| BLAKE2b-256 |
54173bd35ab484c73a3cca1e8263e06fdb7faf5158f167e6f0f30b36a6d1311f
|