앰비언트 에이전트의 정의와 위키피디아 실시간 스트림의 특성
구글의 에이전트 개발 키트(Agent Development Kit)는 앰비언트 에이전트를 요청-응답(Request-Response) 호출 방식이 아니라 스트림에 데이터가 도착하는 순간 깨어나는 인프라 구조로 정의한다. 이는 사용자가 메시지를 입력할 때까지 기다리는 일반적인 챗봇과 달리, 외부 이벤트에 의해 스스로 트리거되어 작동하는 상시 가동형 에이전트를 의미한다. 랭체인(LangChain) 역시 인간의 메시지가 아닌 이벤트에 의해 작동하는 구조를 앰비언트 에이전트의 핵심으로 설명한다.
위키피디아의 EventStreams 서비스는 전 세계에서 발생하는 편집 내역을 별도의 API 키 없이 일반 HTTP GET 요청을 통한 SSE(Server-Sent Events) 형태로 제공한다. 이 스트림은 활성도가 높은 날의 경우 초당 수건의 편집 데이터가 쏟아지는 고속 데이터 흐름을 가진다. 모든 데이터를 LLM에 그대로 전달할 경우 로컬 머신의 연산 자원이 낭비될 뿐만 아니라, 처리 지연으로 인해 실시간 스트림을 따라잡지 못하는 현상이 발생하여 상시 가동 에이전트의 목적을 상실하게 된다.
이러한 문제를 해결하기 위해 규칙 기반의 1단계 필터와 LLM 기반의 2단계 추론을 결합한 '2단계 퍼널(Two-stage funnel)' 구조를 도입한다. 각 파이프라인 단계는 독립적인 파일로 매핑되어 설계되었으며, 이를 통해 개발자는 각 단계를 개별적으로 테스트하고 추론할 수 있다. 이 구조의 핵심은 무의미한 트래픽을 먼저 걷어내고 정밀 분석이 필요한 신호만 모델에 전달하여 연산 효율을 극대화하는 것이다.
1단계 필터: 규칙 기반 전처리를 통한 연산 자원 최적화
`is_anonymous_user` 함수는 위키피디아 편집자의 사용자 이름이 IPv4 또는 IPv6 주소 형태인지 확인하여 익명 사용자의 편집 여부를 판별한다. 위키피디아 피드에는 명시적인 '익명 여부' 필드가 없으므로, IP 주소 패턴을 직접 검사하는 방식이 실제 프로덕션 환경에서 작동하는 유효한 탐지 방법이다. `parse_sse_line`과 `to_event` 함수는 네트워크 의존성이 없는 순수 함수로 구현되어, 실제 연결 전 샘플 페이로드를 통해 파싱 로직의 버그를 사전에 검증할 수 있도록 설계되었다.
`EditVelocityTracker` 클래스는 `deque`를 사용하여 사용자별 최근 편집 타임스탬프를 관리하며, 호출 시마다 윈도우 밖의 오래된 데이터를 제거한다. 이를 통해 '2분 내 5회 편집'과 같은 구체적인 임계치를 실시간으로 정확하게 계산하여 비정상적인 편집 속도를 보이는 사용자를 식별한다. `wikipedia_event_stream` 함수는 `while True` 루프와 재연결-슬립(reconnect-and-sleep) 로직을 통해 HTTP 오류 발생 시에도 연결을 유지함으로써 서비스의 연속성을 보장한다.
`max_tracked` 설정은 추적 대상 딕셔너리가 무한히 커지는 것을 방지하는 방출 가드 역할을 수행한다. 멈추지 않는 스트림 데이터 특성상 사용자 추적 제한이 없으면 메모리 점유율이 계속 상승하여 시스템이 중단될 수 있으며, 이는 데모 수준을 넘어 실제 운영 환경에서 필수적인 메모리 관리 설계다. `Stage1Filter.evaluate` 메서드는 대부분의 이벤트에 대해 None을 반환하여 LLM 전달을 차단하고, 임계치를 넘은 경우에만 `FilterSignal` 객체를 생성해 2단계로 넘긴다.
2단계 추론: Ollama 스키마 강제와 하이브리드 스트리밍 구현
Ollama는 `AgentVerdict.model_json_schema()`를 통해 생성 단계에서부터 응답 형식을 강제하여, 모델이 내뱉는 결과물이 반드시 정의된 JSON 스키마를 따르도록 보장한다. 이는 모델의 응답을 사후에 깎아내는 방어적 파싱(Defensive Parsing) 과정을 생략하게 하며, 런타임에서 유효하지 않은 JSON으로 인해 발생하는 오류를 원천적으로 차단한다. 결과적으로 호출부 코드는 데이터 형식을 의심하지 않고 즉시 타입 체크가 완료된 객체를 비즈니스 로직에 활용할 수 있다.
`evaluate_signal` 함수는 모델이 생성하는 raw chunk를 실시간으로 yield 하여 클라이언트가 AI의 추론 과정을 평문 문자열로 즉시 관찰할 수 있게 한다. 동시에 전체 스트림이 완료된 시점에는 최종적으로 검증된 `AgentVerdict` 객체를 반환하여, 사용자 경험(UX)을 위한 실시간성과 시스템 안정성을 위한 정형 데이터를 동시에 확보한다. 이러한 하이브리드 방식은 사용자가 추론 과정을 지켜보는 동안 백엔드에서는 타입 안전성이 보장된 객체로 후속 작업을 수행할 수 있게 한다.
이 단계에서 LLM은 1단계 필터를 통과한 신호만을 분석하여 해당 편집이 반달리즘(Vandalism)인지, 혹은 단순한 대량 수정인지 판단한다. 모델은 구조화된 심각도 점수(Severity Score)를 포함한 판정 결과를 생성하며, 이는 1단계에서 걸러진 '관심 있는' 이벤트에 대해서만 수행되므로 전체 연산 비용을 획기적으로 낮춘다. 결과적으로 시스템은 불필요한 토큰 생성을 억제하고 고부가가치 추론에만 자원을 집중한다.
인프라 안정성: 비동기 큐 분리와 생명주기 관리
시스템은 연결된 각 클라이언트에게 개별적인 `asyncio.Queue`를 할당하여 메시지를 독립적으로 배포하는 팬아웃(Fan-out) 구조를 채택했다. `publish` 함수는 `put_nowait`를 try/except 구문으로 감싸 처리함으로써, 특정 구독자의 처리 속도가 느려져도 다른 클라이언트나 메인 이벤트 루프가 블로킹되지 않는 Graceful Degradation을 구현했다. 만약 특정 브라우저 탭이 응답하지 않아 큐가 가득 차면 해당 클라이언트의 메시지만 조용히 드롭하여 전체 파이프라인의 병목을 방지한다.
`run_pipeline` 함수는 전체 서비스의 척추 역할을 하며, 특히 2단계 추론 호출부를 try/except 블록으로 감싸 Ollama의 일시적 장애나 잘못된 모델 응답이 백그라운드 태스크를 종료시키지 않도록 보호한다. 오류 발생 시 해당 이벤트만 로그를 남기고 다음 이벤트로 즉시 넘어가므로, 에이전트가 '눈먼 상태'로 방치되는 것을 막고 상시 가동 상태를 유지한다. 이는 단일 오류가 전체 시스템의 가용성을 해치지 않게 하는 핵심 방어 기제다.
FastAPI의 `lifespan` 컨텍스트 매니저는 앱 부팅 시 파이프라인을 백그라운드 태스크로 시작하고 종료 시 깨끗하게 취소하는 현대적인 생명주기 관리 패턴을 적용했다. 또한 `/events` 경로의 루프 내에서 `request.is_disconnected()`를 매번 확인하여, 브라우저 탭이 닫힌 클라이언트의 큐를 즉시 제거함으로써 메모리 누수를 방지한다. 이러한 세밀한 리소스 관리는 장기간 구동되어야 하는 앰비언트 에이전트의 필수 조건이다.
실행 가이드 및 운영 환경으로의 확장 경로
로컬 환경에서 이 시스템을 구동하기 위해서는 `pip install fastapi uvicorn ollama pydantic` 명령어로 필수 패키지를 설치해야 한다. 이후 `python main.py`를 실행하여 서버를 가동하고, `curl -N http://localhost:8000/events` 명령어나 웹 브라우저를 통해 실시간으로 필터링된 이벤트와 LLM의 판정 결과가 SSE 형태로 출력되는 것을 확인할 수 있다. 사용자는 1단계 필터가 감지한 대량 삭제 신호, 이어지는 모델의 추론 토큰, 그리고 최종 구조화된 판정 메시지가 순차적으로 도착하는 과정을 관찰하게 된다.
단일 머신 환경에서는 `asyncio.Queue` 기반의 인-프로세스 통신만으로 충분하지만, 다중 소스를 감시하거나 여러 소비자 프로세스가 협업하는 대규모 확장 시에는 Kafka와 같은 메시지 버스 도입이 필요하다. 메시지 버스는 데이터 생산자와 추론 단계 사이에서 완충 지대 역할을 수행하며, 서비스 재시작 시에도 처리 중이던 이벤트를 보존하는 영속성을 제공한다. 단일 프로세스 기반의 브로드캐스터는 메모리 한계와 단일 장애점(SPOF) 문제로 인해 운영 환경에서는 병목 현상이 발생하기 때문이다.
결론적으로 이벤트 기반의 앰비언트 에이전트를 구축할 때는 [1단계 필터(속도/익명성) $\rightarrow$ 2단계 추론(스키마 강제) $\rightarrow$ 비동기 큐 분배]로 이어지는 파이프라인 구조를 설계 기준으로 삼아야 한다. 설계자는 단순한 챗봇 구현을 넘어, 데이터의 유입 속도와 모델의 추론 비용 사이의 간극을 메우는 퍼널 구조와 클라이언트별 독립 큐를 통한 격리 전략을 통해 시스템의 가용성을 확보해야 한다.




