v0.2.0
신규 기능
섹션 제목: “신규 기능”| 영역 | 변경 | 분류 |
|---|---|---|
| A2A 에러 처리 | llamon_agent.core.errors 모듈 신설 — ErrorCode enum + Pydantic 스키마 + raise_application_error() helper + Python 예외 자동 매핑. 플로우 중간 노드 실패 시 artifacts 자동 보존, SSE 중간 실패 자동 failed 마감 | 신규 API (권장) · BREAKING (응답 형태 전환) |
| A2A DataPart 보존 | LangGraphAgent 경로 (create_server(agent=None) 또는 flow)에서 상위 에이전트의 DataPart/FilePart 가 최종 응답에 유실되던 버그 수정. executor에 isinstance-gated passthrough helper 추가로 해결. RuntimeAdapter 경로는 바이트 단위로 동일. 스트리밍도 토큰 실시간성 유지하며 최종 청크에 DataPart append. 에이전트 프로젝트 수정 불필요 | 버그 수정 |
ExtensionConfig.artifact_name / artifact_description | config.py 한 곳에서 선언적으로 A2A artifact 메타 지정. Registry/Flow/Adapter 3경로 모두 지원. Priority chain: result-level > Adapter ClassVar > ExtensionConfig > fallback heuristic. artifact_name 단독 목적으로 runtime_adapter.py 파일 작성 불필요. 미설정 시 기존 동작과 바이트 단위로 동일. agent-composition 가이드 참조 | 신규 API (DX 개선) |
| Flow 노드 DX | call_llm(agent, prompt, *, state) / call_agent_auto(url, query, *, state) — state.metadata.is_stream_request 자동 감지로 stream/invoke 분기. 기존 10줄 분기 → 1줄 | DX 개선 |
| Scaffold | nodes.py 상단에 Input Contracts 가이드 주석 자동 삽입 (graph 템플릿 4종) | UX |
| Memory | PostgreSQL 의존성 core 승격 (psycopg[binary], langgraph-checkpoint-postgres). [memory] extras 는 SQLite 전용으로 축소 | 의존성 변경 |
| Scaffold | _PostgresNodeStrategy 안전장치 — _json_default, connect(timeout=5.0), record_id fail-fast, pool 승급 가이드 | 안정성 |
.env.example | POSTGRES_MEMORY_DSN [필수] / POSTGRES_URL [선택] 구조 구분 | UX |
| Observability | NodeTracer.current_invoke_depth() — nested invoke 감지 공개 API | 내부 개선 |
Changed
섹션 제목: “Changed”| 영역 | 변경 | 분류 |
|---|---|---|
| Flow 템플릿 | exit 노드 → call_agent_auto(state=state) 1줄로 축약 | DX 개선 |
| Scaffold env | os.getenv → RuntimeEnv(source_file=__file__).get(...) 전환 (HTTP/Postgres 템플릿) | 일관성 |
a2a-sdk | 핀 >=0.3.25 → ==0.3.26 (1.0.0 breaking 회귀 차단) | 안정성 |
[memory] extras | SQLite 전용으로 의미 재정의 — doctor / cmd_ops 가이드 동기화 | 의존성 |
Breaking — Studio UX
섹션 제목: “Breaking — Studio UX”| 영역 | 변경 | 비고 |
|---|---|---|
| Prompt Template / Input Contracts 편집 UI | NodeSidePanel 에서 제거. Registry Prompt 바인딩은 AgentEditor UI 에서 유지. graph.json 의 input_contracts 필드는 round-trip 보존 | code-first 로 전환 |
| Quick Config (HTTP/Postgres 폼) | 프론트 4파일 + 백엔드 structured_config.py / markers.py / NodeDef.structured_config + 테스트 7개 삭제 | 단일 Code Editor 경로로 수렴 (≈ −1000 LOC) |
Fixed
섹션 제목: “Fixed”| 영역 | 버그 |
|---|---|
| Flow / agent-general 스트리밍 | _agent_is_stream_safe gate 가 신뢰 타입(LangGraphAgent / GraphInvocationAdapter / GuardrailRuntimeAdapter)을 차단해 message/stream 이 invoke 로 강등되던 버그. 해당 타입에 __llamon_bypass_runtime_adapter_check__ = True 마커 추가 |
call_llm 이중 토큰 emit | on_text_chunk 미지정 시 자동 writer 호출 제거. custom + messages 채널 중복 발행으로 artifact 2배 누적되던 버그 수정 |
| Stream 경로 invoke 중복 폴백 | _extract_stream_text 가 llamon-20b chunk 포맷(additional_kwargs.reasoning_content 등) 놓쳐 emitted_any=False 로 invoke 폴백이 가동, 동일 프롬프트가 stream+invoke 로 2회 생성되던 문제. 추출 범위 확장 |
| Stream trace name 덮어쓰기 | nested 호출에서 inner agent 의 .stream() 이 trace_invoke_span(is_root=True) 를 하드코딩해 부모 trace name 을 {node}.stream 으로 덮어쓰던 버그. is_root=(current_invoke_depth() == 0) 규칙으로 정렬 |
A2A 에러 처리 상세
섹션 제목: “A2A 에러 처리 상세”A2A 에러 처리 페이지에 단독으로 정리돼 있습니다. 주요 API:
from llamon_agent.core.errors import ( ErrorCode, # 분기용 고정 enum 8개 raise_application_error, # 도메인 특화 reason + extras 선언 LlamonApplicationError, # 매핑 우회 커스텀 예외 # Pydantic 모델 + 팩토리 (SDK 내부용) ProtocolErrorData, ApplicationErrorData, make_protocol_error_data, make_application_error_data, application_error_to_parts, python_exc_to_error_code,)클라이언트팀 영향: -32603 Internal Error 로 뭉뚱그려지던 내부 예외가 result.status.state === "failed" 로 내려옵니다. error 필드만 검사하던 파싱 로직은 Application-level 실패를 놓치므로 toError(resp) normalizer 적용 필요.
한눈에 보는 변경
섹션 제목: “한눈에 보는 변경”| 영역 | 변경 | 분류 |
|---|---|---|
| ReAct env | MAX_RETRY → REACT_MAX_ITERATIONS | Breaking |
| flow 스트리밍 | 마지막 노드만 토큰 출력 | 동작 변경 |
config.py 역할 | 3계층 모델 정립 (node-local / config.py / .env) | DX 개선 |
| Studio 승급·강등 | 공유 env 감지 → inline 배너 + diff 적용 | 신규 기능 |
write_config | AST-merge 전환 — Agent Editor 저장 시 사용자 상수 보존 | 동작 변경 (안전해짐) |
| Structured 어댑터 | SchemaValidatedRuntimeAdapter 신설 | 신규 API (권장) |
| MCP 호출 | @deterministic_tool 신설 | 신규 API |
| Observability | TraceBackend 추상화, HTTP ingestion 기본 | 내부 개선 |
| Langfuse | observation type 매핑 강화 | UI 표시 변경 |
| HTTP 노드 | HttpApiBackend 배치 큐 전환 | 처리량 개선 |
| Memory | MEMORY_WINDOW_SIZE 설정 추가 | 신규 옵션 |
| Guardrail | 병렬 평가, max_token 제한 | 성능 개선 |
| CLI | --no-wheelhouse, restore-online 보정 | 옵션 정리 |
| Kafka | 핫-리로드 안정화, stale timeout | 안정성 |
마이그레이션 가이드
섹션 제목: “마이그레이션 가이드”M1. .env 키 변경 (Breaking)
섹션 제목: “M1. .env 키 변경 (Breaking)”# 모든 에이전트/플로우 디렉토리에서 한 번에 치환sed -i '' 's/^MAX_RETRY=/REACT_MAX_ITERATIONS=/' .env .env.example| 항목 | v0.1.x | v0.2 |
|---|---|---|
.env 키 | MAX_RETRY | REACT_MAX_ITERATIONS |
| 미변경 시 | 동작 | 서버 시작 실패 |
Python API ExtensionConfig(max_retry=...) | 유지 | 유지 |
agent-card.json의 maxRetry 필드 | 표시 | 표시 |
배경: 레포의 docs/runtime/react-recursion.md.
M2. flow 스트리밍 — 마지막 노드만 토큰 출력
섹션 제목: “M2. flow 스트리밍 — 마지막 노드만 토큰 출력”한 줄 요약
섹션 제목: “한 줄 요약”message/stream 응답에 토큰을 흘릴 수 있는 노드는 END에 연결된 마지막 노드 하나뿐입니다. 그 외 중간 노드는 결과를 state에 담아 다음 노드로 넘기기만 합니다.
그림으로
섹션 제목: “그림으로”[client] --message/stream--> [agent_a] --output--> [agent_b] --output--> [final] --토큰 스트림--> [client] (sync) (sync) (END에 연결, 스트리밍 가능)누가 영향받나
섹션 제목: “누가 영향받나”- v0.1.x에서 중간 노드가
get_stream_writer()로 토큰을 직접 푸시하던 flow → 이 토큰들이 사용자 화면에 더 이상 안 나옵니다 - 마지막 노드 1개만 LLM 응답을 보내는 flow → 영향 없음
어떻게 확인하나
섹션 제목: “어떻게 확인하나”graph.py에서 END에 연결된 노드를 찾으세요.
builder.edge("agent_b", "final")builder.edge("final", END) # ← "final"이 exit 노드. 여기서만 스트림 가능마이그레이션
섹션 제목: “마이그레이션”마지막 노드에서 스트림 요청 여부를 확인하고 토큰을 출력합니다.
from llamon_agent import AIMessage, get_stream_writer
async def final_node(state, *, agent_url: str) -> dict: is_streaming = state.get("metadata", {}).get("is_stream_request") if is_streaming: writer = get_stream_writer() full_text = "" async for chunk in call_agent_stream(agent_url, build_agent_context(state)): writer(chunk) full_text += chunk return {"messages": [AIMessage(content=full_text)], "output": full_text} # 비스트림 호출은 평소대로 output = await call_agent(agent_url, build_agent_context(state)) return {"messages": [AIMessage(content=output)], "output": output}전체 패턴: 플로우 공통 패턴 → 스트리밍 규칙.
M3. config.py 3계층 모델 + Studio 승급·강등 (선택, 마이그레이션 불필요)
섹션 제목: “M3. config.py 3계층 모델 + Studio 승급·강등 (선택, 마이그레이션 불필요)”v0.2 부터 환경 설정값의 위치를 수명주기 기준으로 구분해 둡니다.
| 계층 | 위치 | 담는 것 |
|---|---|---|
| 노드 수명 | nodes.py 상단 RuntimeEnv(source_file=__file__) | 해당 노드 하나만 쓰는 env 값 |
| 프로젝트 수명 | app/config.py | 여러 노드가 공유하는 이름·기본값 + 배선 정보 |
| 배포 수명 | .env | 배포 환경별 값 + secrets (단일 진입점) |
원칙:
config.py는 공유되는 이름·기본값만. secrets·환경 종속 값은 항상.env→RuntimeEnv경유로 읽으세요.
Studio 자동 제안
섹션 제목: “Studio 자동 제안”Studio 가 노드 코드를 AST 로 분석해 다음을 inline 배너로 제안합니다:
- 승급: 2+ 개 노드에서 같은 env 키가 동일 default·타입으로 쓰이면
config.py로 이동 제안 - 강등:
config.py상수가 1개 노드에서만 참조되면 node-local 로 되돌리기 제안 - 충돌: 같은 키가 다른 default 로 여러 곳에 있으면 값 통일 안내
- 보안 휴리스틱:
SECRET,KEY,TOKEN,PASSWORD,DSN,URL,PASS키워드를 포함한 키는 자동 승급 후보에서 제외 —.env전용 대상 - 와이어링 보호:
graph.py/main.py에서만 참조되는 상수는 demote 하지 않음 (apply 시 import 가 깨지는 것을 방지)
[미리보기] → 실제 unified diff 확인 → [적용] 으로 파일 일괄 수정. 중간 실패 시 FileRestoreTransaction 이 자동 롤백합니다.
write_config AST-merge — 기존 상수 보존
섹션 제목: “write_config AST-merge — 기존 상수 보존”이전 버전에서는 Studio Agent Editor 저장이 config.py 를 통째로 재작성해 AGENT_A_ID 같은 Registry Agent ID 상수, 사용자 수동 상수, 주석 블록이 소실될 수 있었습니다. v0.2 부터는 AST-merge 로 build_extension() 함수만 교체하고 나머지 모든 내용 (상수, 주석, 커스텀 import, 승급한 공유 env 상수) 이 보존됩니다.
마이그레이션
섹션 제목: “마이그레이션”- 기존 프로젝트는 수정 불필요 — 기존 config.py 는 그대로 동작합니다.
- 새로 생성되는
config.py상단에 3계층 원칙 주석이 자동 삽입됩니다. - config.py 에 secret 이 박혀 있다면
.env로 옮기는 것을 권장합니다 (Studio 가 경고 배너로 안내).
M4. Structured 어댑터 → SchemaValidatedRuntimeAdapter (선택, 권장)
섹션 제목: “M4. Structured 어댑터 → SchemaValidatedRuntimeAdapter (선택, 권장)”extract_payload / build_summary를 직접 다루던 코드를 Pydantic 스키마 1개로 줄일 수 있습니다.
Before — StructuredOutputAgent (수작업)
섹션 제목: “Before — StructuredOutputAgent (수작업)”from llamon_agent import StructuredOutputAgent
class MyAgent(StructuredOutputAgent): def extract_payload(self, text, data): payload = super().extract_payload(text, data) intent = str(payload.get("intent", "")).strip() if intent not in ("simple_query", "document_verification"): intent = "unclassified" confidence = float(payload.get("confidence", 0.0)) confidence = max(0.0, min(1.0, confidence)) return {"intentType": intent, "confidence": confidence, "originalQuery": payload.get("originalQuery", "")}
def build_summary(self, payload): return f"질의를 {payload['intentType']}로 분류했습니다 (신뢰도 {payload['confidence']})"After — SchemaValidatedRuntimeAdapter[PayloadT]
섹션 제목: “After — SchemaValidatedRuntimeAdapter[PayloadT]”from pydantic import BaseModel, Fieldfrom typing import Literalfrom llamon_agent import SchemaValidatedRuntimeAdapter
class IntentPayload(BaseModel): intentType: Literal["simple_query", "document_verification", "unclassified"] confidence: float = Field(ge=0, le=1, default=0.0) originalQuery: str = ""
class MyAgent(SchemaValidatedRuntimeAdapter[IntentPayload]): payload_schema = IntentPayload
def apply_business_rules(self, payload, *, query, a2a_files, **_): if a2a_files and payload.intentType != "document_verification": payload.intentType = "document_verification" payload.confidence = max(payload.confidence, 0.6) return payload
def format_summary(self, payload): return f"질의를 {payload.intentType}로 분류했습니다 (신뢰도 {payload.confidence})"| 항목 | StructuredOutputAgent | SchemaValidatedRuntimeAdapter |
|---|---|---|
| JSON 파싱 | 수작업 | 자동 |
| 타입 캐스팅 / 범위 검증 | 수작업 | 자동 (Pydantic) |
| 검증 실패 처리 | 수작업 | on_validation_error() 훅 (기본: 안전 안내 응답) |
| 비즈니스 룰 위치 | extract_payload 안 | apply_business_rules() (선택, 기본 no-op) |
| summary | build_summary 필수 | format_summary (기본: payload의 summary/output_text/message 자동 탐색) |
| 코드 양 |
위의 Before 코드는 일반적인 패턴을 단순화한 illustrative 예시입니다. 실제 프로젝트에서는 추가 보정·로깅 로직이 더 들어가지만, 마이그레이션 작업의 큰 그림(수작업 → 스키마 위임)은 동일합니다.
전체 가이드 + on_validation_error 예시: Registry 기반 에이전트 → SchemaValidated.
M5. MCP 호출 — @deterministic_tool (선택)
섹션 제목: “M5. MCP 호출 — @deterministic_tool (선택)”ReAct 루프(LLM 자율 판단) 전에 코드로 확정 호출해야 할 때 사용합니다 (규제·민감 도메인 등).
from llamon_agent import deterministic_tool, DeterministicContext
@deterministic_tool(mcp_id="bank-service")def route_bank(ctx: DeterministicContext) -> dict | None: if "예금" in ctx.query: return {"tool_name": "get_deposit_rate", "params": {"bank_code": "004"}} return None # 매치 안 됨 → ReAct 폴백자세한 사용법: 확정 MCP 호출 (deterministic_tool).
M6. CLI 폐쇄망 옵션 정리 (선택)
섹션 제목: “M6. CLI 폐쇄망 옵션 정리 (선택)”| 명령 | 변경 |
|---|---|
prepare-offline | --no-wheelhouse 추가 (wheelhouse 단계 스킵) |
restore-online | 온라인 Dockerfile 복원 안정화 |
package | dist/ 자체는 아카이브에서 제외 |
폐쇄망 흐름은 로컬 실행/점검 + 폐쇄망 준비 참조.
신규 공개 API (요약)
섹션 제목: “신규 공개 API (요약)”| API | 임포트 | 가이드 |
|---|---|---|
SchemaValidatedRuntimeAdapter[PayloadT] | from llamon_agent import SchemaValidatedRuntimeAdapter | 에이전트 → SchemaValidated |
deterministic_tool, DeterministicContext | from llamon_agent import deterministic_tool, DeterministicContext | 확정 MCP 호출 |
호환성 요약
섹션 제목: “호환성 요약”- Breaking:
.env의MAX_RETRY→REACT_MAX_ITERATIONS(즉시 수정) - 유지: Python API
ExtensionConfig(max_retry=...)(인자명 그대로) - 유지:
agent-card.json의maxRetry필드 - 유지:
StructuredOutputAgent베이스 (SchemaValidatedRuntimeAdapter로 옮길지는 선택) - 유지: 기존 flow scaffold (스트리밍 동작 향상)
- 주의: Langfuse trace UI에서 일부 항목 분류가 더 정확해져 기존 대시보드 필터 재확인 권장