Recipes

CrewAI 연구 크루를 위한 AgentOps 관측성

README 링크에서 발견 · 2 days ago

Software & DevOps

AgentOps 트레이싱 및 비용 추적 기능을 갖춘 CrewAI 에이전트 크루를 계측했습니다.

What they did

그들은 LlamaIndex 검색 파이프라인을 통해 문서를 가져온 뒤 종합된 브리프를 작성하여 연구 질문에 답하는 작은 CrewAI crew를 운영하고 있었습니다. 문제는 가시성이었습니다. 답변이 틀리거나 느릴 때, 검색기가 잘못된 청크를 반환한 것인지, 에이전트가 잘못된 도구를 선택한 것인지, 아니면 모델이 그냥 환각을 일으킨 것인지 알 수 없었습니다. 그들은 AgentOps를 관측성 계층으로 추가했습니다. agentops 패키지를 설치하고, 대시보드에서 API 키를 받아, 엔트리포인트 상단에서 세션을 초기화하여 모든 실행이 세션 id를 갖도록 했습니다. 그런 다음 AgentOps가 CrewAI 에이전트와 태스크를 자동 계측하도록 하여, 에이전트 단계, 도구 호출, LLM 완료의 트레이스 트리를 생성했습니다. 그들은 각 세션에 메타데이터(crew 이름, 질문 id, 검색기 인덱스 버전)를 태그하여 나중에 실행을 필터링할 수 있도록 했습니다. 또한 LlamaIndex 쿼리 엔진 호출을 래핑하여 검색 지연 시간과 반환된 노드 수가 에이전트 span과 함께 표시되도록 했습니다. 마지막으로 세션당 점수를 기록하는 작은 평가 패스를 추가하여, 출력을 눈으로 확인하는 대신 대시보드에서 프롬프트 변형을 비교할 수 있도록 했습니다.

How it went

그들은 결국 동작하는 trace view를 갖게 되었다. 각 research run은 crew의 task 시퀀스, 어떤 도구가 실행되었는지, LLM 호출별 token 수와 예상 비용, 그리고 retrieval 단계를 보여준다. 디버깅은 print 문을 넣고 다시 실행하는 방식에서 세션 replay를 여는 방식으로 바뀌었다. evaluation tagging 덕분에 두 prompt 버전을 나란히 비교할 수 있었다. 완성도 수준은 prototype-to-internal-tool 정도다. 그들의 머신과 CI smoke test에서는 안정적으로 동작하지만, multi-tenant production 트래픽에 대비해 hardened된 상태는 아니다.

Worth knowing

자동 계측은 일반적인 CrewAI 경로를 커버하지만, 커스텀 도구 래퍼에는 수동 span이 필요할 수 있습니다. 세션 메타데이터와 평가 점수는 여러분이 기억해서 추가하는 태그만큼만 유용합니다. API 키는 소스 컨트롤 밖에 보관해야 하며, 무료 티어에는 이벤트 제한이 있어서 말이 많은 crew는 금방 한도에 도달할 수 있습니다. LlamaIndex 검색 span은 자동으로 나타나는 대신 명시적인 래퍼가 필요했습니다.

Wiring

환경 변수를 먼저 로드한 다음, CrewAI crew를 임포트하거나 구성하기 전에 agentops.init(api_key=...)를 호출해야 자동 계측이 올바른 클래스에 패치를 적용합니다. 그다음 LlamaIndex 인덱스와 쿼리 엔진을 빌드하고, 쿼리 호출을 수동 span으로 감쌉니다. CrewAI 에이전트와 태스크를 구성한 뒤, 성공 또는 실패 상태로 agentops.end_session을 호출하는 try/finally 안에서 kickoff합니다. 대시보드 필터가 작동하도록 세션 시작 시 메타데이터(crew 이름, run id, 인덱스 버전)를 첨부합니다. 설정은 .env에 있습니다: AGENTOPS_API_KEY, 모델 제공자 키, 인덱스 경로. CI에서는 단일 스모크 질문을 실행하고 세션 id가 반환되었는지 검증합니다.

Try it yourself

# AgentOps Observability for CrewAI Agent

Below is the minimal diff to add AgentOps tracing to your CrewAI + LlamaIndex agent. I'm assuming a typical layout (`main.py` entrypoint, `crew.py` for the crew, `retriever.py` for the LlamaIndex query engine). Adjust paths to match your repo.

## Diff

```diff
diff --git a/.env.example b/.env.example
index 1111111..2222222 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,6 @@
 OPENAI_API_KEY=sk-...
+AGENTOPS_API_KEY=your-agentops-key
 # optional
 LLAMA_INDEX_VERSION=v1
```

```diff
diff --git a/requirements.txt b/requirements.txt
index 3333333..4444444 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
 crewai
 llama-index
 python-dotenv
+agentops
```

```diff
diff --git a/main.py b/main.py
index 5555555..6666666 100644
--- a/main.py
+++ b/main.py
@@ -1,20 +1,45 @@
+import os
+import uuid
+
+import agentops
 from dotenv import load_dotenv
 
 from crew import build_crew
 from retriever import get_query_engine
 
 load_dotenv()
 
+
 def main() -> None:
-    crew = build_crew()
-    result = crew.kickoff()
-    print(result)
+    run_id = str(uuid.uuid4())
+    index_version = os.environ.get("LLAMA_INDEX_VERSION", "v1")
+
+    agentops.init(
+        api_key=os.environ["AGENTOPS_API_KEY"],
+        tags=["crewai", "llamaindex"],
+    )
+    try:
+        agentops.set_session_metadata(
+            crew_name="research_crew",
+            run_id=run_id,
+            retriever_index_version=index_version,
+        )
+
+        crew = build_crew()
+        result = crew.kickoff()
+        print(result)
+
+        # (5) per-session evaluation score from existing output check
+        score = 1.0 if output_check_passes(result) else 0.0
+        agentops.record_evaluation(
+            name="output_check",
+            score=score,
+            metadata={"run_id": run_id},
+        )
+    finally:
+        agentops.end_session("Success")
 
 
 if __name__ == "__main__":
     main()
```

```diff
diff --git a/retriever.py b/retriever.py
index 7777777..8888888 100644
--- a/retriever.py
+++ b/retriever.py
@@ -1,10 +1,22 @@
+import agentops
 from llama_index.core import VectorStoreIndex
 
 
 def get_query_engine(index: VectorStoreIndex):
     return index.as_query_engine()
 
 
 def query(engine, question: str):
-    return engine.query(question)
+    with agentops.start_span("llamaindex_query") as span:
+        response = engine.query(question)
+        nodes = getattr(response, "source_nodes", []) or []
+        span.set_attribute("node_count", len(nodes))
+        span.set_attribute("question_length", len(question))
+        return response
```

## Notes on each step

1. **Install + env**: `pip install agentops`, add `AGENTOPS_API_KEY` to `.env.example`, read via `os.environ["AGENTOPS_API_KEY"]` (fails fast if missing).
2. **Init once at entrypoint**: `agentops.init()` is called in `main()` before `build_crew()`. `end_session` is in a `finally` so it runs even on exceptions. If you have multiple entrypoints, move init into a shared `bootstrap.py` and call it from each.
3. **Session metadata**: `set_session_metadata` tags the session with `crew_name`, `run_id`, and `retriever_index_version`. These show up as filterable attributes in the dashboard.
4. **Retrieval span**: `agentops.start_span("llamaindex_query")` wraps the query engine call. `node_count` is derived from `response.source_nodes` (LlamaIndex's standard field). Latency is captured automatically by the span's start/end timestamps.
5. **Evaluation score**: `record_evaluation` records a per-session score from your existing `output_check_passes` helper. If your check returns a float already, pass it directly.

## Verifying traces in the dashboard

1. **Run the agent locally** with a valid key:
   ```bash
   export AGENTOPS_API_KEY=...
   python main.py
   ```
   You should see an AgentOps session URL printed in the console (e.g. `https://app.agentops.ai/sessions/<id>`).

2. **Open the session** in the AgentOps dashboard. Confirm:
   - **Session metadata** shows `crew_name`, `run_id`, `retriever_index_version`.
   - **Spans** include `llamaindex_query` with `node_count` and a latency value.
   - **Evaluations** tab shows `output_check` with the score.
   - **CrewAI events** (agent steps, tool calls) appear as child spans under the session.

3. **Sanity checks if traces are missing**:
   - `agentops.init()` must run before any CrewAI/LlamaIndex object is constructed — otherwise auto-instrumentation won't attach.
   - Confirm `agentops` version supports your CrewAI version (`pip show agentops crewai`); CrewAI instrumentation is version-sensitive.
   - Check the console for `AgentOps: Session started` / `Session ended` log lines.
   - If `end_session` is never called (e.g. process killed), the session may appear as "in progress" — the `finally` block prevents this for normal exits.

4. **CI verification** (optional): assert the session URL is printed and exit code is 0; the dashboard is the source of truth for span content.

Source

English

Tools in this stack