Open UI + Langgraph Runbook (Langgraph)
create_deep_agent
기존의 단순 ReAct 에이전트보다 복잡하고 심층적인 분석을 수행하는 에이전트를 쉽게 구축할 수 있는 구조를 제공한다.
- Deep reasoning 지원(복잡한 문제 해결을 위한 계획 수립, 도구 호출, 평가 루프 등)
- 서브 에이전트 위임, 파일/컨텍스트 관리 기능 내장
PROMPT_PATH = Path(__file__).with_name("system-prompt.txt")
model = ChatOpenAI(
model=os.environ.get("OPENAI_MODEL", "gpt-4.1-mini"),
streaming=True,
use_responses_api=False,
)
graph = create_deep_agent(
model=model,
tools=[get_weather, get_stock_price, search_web],
system_prompt=PROMPT_PATH.read_text(encoding="utf-8")
+ "\n"
+ (
"You are an OpenUI assistant with weather, finance, and research tools. "
"Use the tools when they help answer the user's request, then answer only in OpenUI Lang. "
"Weather, stock and search tools return demo data; do not present it as live data."
),
)
# AG-UI 변환 레이어
def stream_transformers():
"""LangGraph API creates a fresh transformer per run (protocol v2)."""
return [OpenUIStreamTransformer]
Middleware
LangGraph 구조에 훅이 추가되었다.
- before agent, after agent: 그래프 실행 전후에 미들웨어로 개입할 수 있다.
- before/after llm, tool도 가능하다.
Deep Agent를 사용하면 기본적으로 아래 미들웨어가 제공된다.
deepagent_middleware.extend([
#비동기 하위 에이전트가 끝나면 자신을 실행한 부모 에이전트에게 결과·실패를 알림
_ParentNotifierMiddleware(),
# 대화가 길어지면 이전 내용을 요약해 모델 입력 크기를 관리
create_summarization_middleware(model, backend),
# 도구 호출은 있는데 결과가 빠져 있는지 검사해서 누락된 도구 호출 이력을 보충 (tool다시 실행을 하지는 않는다.)
PatchToolCallsMiddleware()
])
@tool 데코레이터
에이전트가 사용할 수 있는 도구를 정의하는 방법이다.
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
...
모델에게는 대략 다음 정보가 전달됩니다.
{
"name": "get_weather", // 함수 이름
"description": "Get the current weather for a city.", // 하위 주석
"parameters": { // 넘겨야 하는 인자
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
AG-UI OpenUIStreamTransformer
목적: 모델·도구 실행 이벤트를 OpenUI 프런트엔드가 이해하는 AG-UI 이벤트로 변환한다.
OpenUIStreamTransformer는 LangGraph의StreamTransformer를 상속한다.- 단,
langgraph dev로 실행한langgraph-api서버에서 사용하는 경우다.
이벤트 규약
| 실행 중 발생한 내용 | AG-UI 이벤트 | 화면에서 가능한 처리 |
|---|---|---|
| 모델 응답 시작 | TEXT_MESSAGE_START | 새 답변 영역 생성 |
| 텍스트 조각 | TEXT_MESSAGE_CONTENT | 답변을 실시간으로 이어 붙이기 |
| 모델 응답 종료 | TEXT_MESSAGE_END | 해당 메시지 출력 완료 처리 |
| 도구 호출 시작 | TOOL_CALL_START | 호출할 도구 표시 |
| 도구 인자 조각 | TOOL_CALL_ARGS | 호출 인자 누적 |
| 도구 호출 정보 전송 종료 | TOOL_CALL_END | 인자 수신 완료 처리 |
| 도구 실행 결과 | TOOL_CALL_RESULT | 결과 표시 및 실행 상태 갱신 |
TOOL_CALL_END는 도구 실행 완료를 의미하지 않는다.- TOOL_CALL_START -> TOOL_CALL_ARGS -> TOOL_CALL_END -> TOOL_CALL_RESULT 순서다.
Langgraph 출력 결과물 w/Open lang
root = Stack([card])
card = Card([header, price, change, details], "card", "column", "m", "start", "center")
header = TextContent("Apple Stock (AAPL)", "large-heavy")
price = TextContent("$189.32", "large-heavy")
change = TextContent("-0.52 (-0.27%)", "default")
details = EntityList([
{left: "Volume", right: "45.1M"},
{left: "Day High", right: "$191.86"},
{left: "Day Low", right: "$188.12"}
], "default")
{
"values": {
"messages": [
{
"content": [
{
"type": "text",
"text": "What's the current Apple stock price?\n"
}
],
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": "01a16e43-b3f3-769c-af68-a05594329ebd"
},
{
"content": "",
"additional_kwargs": {
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 11323,
"output_tokens": 16,
"total_tokens": 11339,
"input_token_details": {
"audio": 0,
"cache_read": 11264
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
},
"response_metadata": {
"finish_reason": "tool_calls",
"model_name": "gpt-4.1-mini-2025-04-14",
"system_fingerprint": "fp_98a8cc63cd",
"service_tier": "default",
"model_provider": "openai"
},
"type": "ai",
"name": null,
"id": "lc_run--01a16e43-b7d1-7f13-a6eb-0354be053d77",
"tool_calls": [
{
"name": "get_stock_price",
"args": {
"symbol": "AAPL"
},
"id": "call_3kV5vbHcpu6NB6d58d4LBSBx",
"type": "tool_call"
}
],
"invalid_tool_calls": [],
"usage_metadata": null
},
{
"content": "{\"symbol\": \"AAPL\", \"price\": 189.32, \"change\": -0.52, \"change_percent\": -0.27, \"volume\": \"45.1M\", \"day_high\": 191.86, \"day_low\": 188.12}",
"additional_kwargs": {},
"response_metadata": {},
"type": "tool",
"name": "get_stock_price",
"id": "feb2e6ea-524b-4fb7-a418-c5f63219651b",
"tool_call_id": "call_3kV5vbHcpu6NB6d58d4LBSBx",
"artifact": null,
"status": "success"
},
{
"content": "root = Stack([card])\ncard = Card([header, price, change, details], \"card\", \"column\", \"m\", \"start\", \"center\")\nheader = TextContent(\"Apple Stock (AAPL)\", \"large-heavy\")\nprice = TextContent(\"$189.32\", \"large-heavy\")\nchange = TextContent(\"-0.52 (-0.27%)\", \"default\")\ndetails = EntityList([\n {left: \"Volume\", right: \"45.1M\"},\n {left: \"Day High\", right: \"$191.86\"},\n {left: \"Day Low\", right: \"$188.12\"}\n], \"default\")",
"additional_kwargs": {
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 11407,
"output_tokens": 133,
"total_tokens": 11540,
"input_token_details": {
"audio": 0,
"cache_read": 11264
},
"output_token_details": {
"audio": 0,
"reasoning": 0
}
}
},
"response_metadata": {
"finish_reason": "stop",
"model_name": "gpt-4.1-mini-2025-04-14",
"system_fingerprint": "fp_98a8cc63cd",
"service_tier": "default",
"model_provider": "openai"
},
"type": "ai",
"name": null,
"id": "lc_run--01a16e43-bc67-7651-a319-a3794165e3dc",
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
],
"files": {}
},
}
한계점 및 운영 시 고려 사항
OpenUI 응답을 일반 텍스트로 요약하면 구조 정보가 손실될 위험이 있다.
Autofix: 생성 완료 후 유효하지 않은 OpenUI Lang 코드를 단 한 번의 API 호출로 자동으로 수정한다(Repair). 기존 모델 공급자, 추론 환경, 에이전트 프레임워크를 그대로 유지한 채 적용 가능하다.
신뢰성 모니터링 (Reliability Monitoring): 생성된 인터페이스의 실패율, 반복되는 에러 유형, 실패 원인이 된 생성 내역을 추적·분석한다. Gateway/Autofix 사용 시 자동 활성화되며, 자체 추론 환경에서는 Observability SDK를 통해 연동할 수 있다.
플리커링 이슈 및 validator failover 이슈(오픈소스의 프리미엄 영역은 모니터링 부분이다.)
LangGraph JS 예제에는 LLM이 만든 OpenUI Lang을 서버에서 검증하고, 오류를 모델에 돌려보내 수정시키는 단계는 없다. (Gateway에서 검증·보정을 맡는 구조로, 유료 서비스 이용이 필요하다.)
- 모델이 잘못된 UI 크기를 출력한 경우에도 검증이 가능한지는 모르겠다. width, height가 명시된 경우 validator를 통과한다. 그러나 높이와 너비가 적절하지 않으면 화면이 깨질 수 있다.
- 렌더링 성능을 측정할 필요가 있다.