mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-23 11:20:13 +00:00
fix(tracing): a temporary file of its own per writer; the docs name deployments
- record_last_run wrote every record through one `.tmp` name, so two crews finishing together in one project could replace each other's temporary file and one record was lost. Each write now goes through tempfile.mkstemp in the same directory, then os.replace; a failed write removes its own temporary file. Tests: two writers use two names and leave only last_run.json; a failed replace leaves nothing behind. - tracing.mdx (en, ar, ko, pt-BR): nothing is recorded inside a deployment, where the platform owns the trace. 87 passed (test_last_run + test_session_trace_export); ruff and mypy clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -154,7 +154,7 @@ result = flow.kickoff()
|
||||
للتتبعات المرتبطة بحسابك، افتح [علامة تبويب التتبعات في لوحة تحكم CrewAI AMP](https://app.crewai.com/crewai_plus/trace_batches) لعرض تفاعلات الوكلاء واستخدام الأدوات واستدعاءات LLM.
|
||||

|
||||
|
||||
بعد تشغيلٍ وصلت نطاقاته (spans) إلى CrewAI AMP، يكتب crewAI الملف `.crewai/last_run.json` في مجلد المشروع: معرّف التنفيذ، وما إذا كان التتبع مجهولاً أو تحت حسابك، ووقت البدء والانتهاء، وعنوان AMP الذي أُرسل إليه. يقرأه الأمر `crewai eval` لتقييم آخر تشغيل دون نسخ أي معرّف. يتضمن `.gitignore` في القالب المجلد `.crewai/`؛ ولا يُسجَّل إلا التشغيل الذي صُدِّر كاملاً، ولا يُكتب شيء عند `CREWAI_TESTING=true`.
|
||||
بعد تشغيلٍ وصلت نطاقاته (spans) إلى CrewAI AMP، يكتب crewAI الملف `.crewai/last_run.json` في مجلد المشروع: معرّف التنفيذ، وما إذا كان التتبع مجهولاً أو تحت حسابك، ووقت البدء والانتهاء، وعنوان AMP الذي أُرسل إليه. يقرأه الأمر `crewai eval` لتقييم آخر تشغيل دون نسخ أي معرّف. يتضمن `.gitignore` في القالب المجلد `.crewai/`؛ ولا يُسجَّل إلا التشغيل الذي صُدِّر كاملاً؛ ولا يُكتب شيء عند `CREWAI_TESTING=true`، ولا داخل نشرٍ (deployment) حيث تملك المنصة التتبع.
|
||||
|
||||
### البديل: إعداد متغير البيئة
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ Traces are uploaded only after a successful authenticated export or an explicitl
|
||||
For traces associated with your account, open the [Traces tab in the CrewAI AMP dashboard](https://app.crewai.com/crewai_plus/trace_batches) to view agent interactions, tool usage, and LLM calls.
|
||||

|
||||
|
||||
After a run whose spans reached CrewAI AMP, crewAI writes `.crewai/last_run.json` in the project directory: the run's execution id, whether it was traced anonymously or under your account, when it started and finished, and the AMP it was sent to. `crewai eval` reads it to evaluate the last run without you copying an id. The scaffold's `.gitignore` covers `.crewai/`; only a run exported whole is recorded, and nothing is written under `CREWAI_TESTING=true`.
|
||||
After a run whose spans reached CrewAI AMP, crewAI writes `.crewai/last_run.json` in the project directory: the run's execution id, whether it was traced anonymously or under your account, when it started and finished, and the AMP it was sent to. `crewai eval` reads it to evaluate the last run without you copying an id. The scaffold's `.gitignore` covers `.crewai/`; only a run exported whole is recorded; nothing is written under `CREWAI_TESTING=true`, and nothing inside a deployment, where the platform owns the trace.
|
||||
|
||||
### Alternative: Environment Variable Configuration
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ result = flow.kickoff()
|
||||
계정에 연결된 추적은 [CrewAI AMP 대시보드의 Traces 탭](https://app.crewai.com/crewai_plus/trace_batches)에서 agent 상호 작용, 도구 사용 및 LLM 호출을 확인하세요.
|
||||

|
||||
|
||||
스팬이 CrewAI AMP에 도달한 실행이 끝나면 crewAI는 프로젝트 디렉터리에 `.crewai/last_run.json`을 기록합니다. 실행 ID, 익명 또는 계정으로 추적되었는지, 시작·종료 시각, 전송된 AMP 주소가 담깁니다. `crewai eval`은 이 파일을 읽어 ID를 복사하지 않고도 마지막 실행을 평가합니다. 스캐폴드의 `.gitignore`는 `.crewai/`를 포함하며, 전체가 내보내진 실행만 기록되고 `CREWAI_TESTING=true`에서는 아무것도 기록되지 않습니다.
|
||||
스팬이 CrewAI AMP에 도달한 실행이 끝나면 crewAI는 프로젝트 디렉터리에 `.crewai/last_run.json`을 기록합니다. 실행 ID, 익명 또는 계정으로 추적되었는지, 시작·종료 시각, 전송된 AMP 주소가 담깁니다. `crewai eval`은 이 파일을 읽어 ID를 복사하지 않고도 마지막 실행을 평가합니다. 스캐폴드의 `.gitignore`는 `.crewai/`를 포함하며, 전체가 내보내진 실행만 기록되며, `CREWAI_TESTING=true`에서는 아무것도 기록되지 않고, 플랫폼이 추적을 소유하는 배포 안에서도 기록되지 않습니다.
|
||||
|
||||
### 대안: 환경 변수 구성
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ Os rastreamentos são enviados somente após uma exportação autenticada bem-su
|
||||
Para rastreamentos associados à sua conta, abra a [aba Traces no painel CrewAI AMP](https://app.crewai.com/crewai_plus/trace_batches) para visualizar interações dos agentes, uso de ferramentas e chamadas de LLM.
|
||||

|
||||
|
||||
Após uma execução cujos spans chegaram ao CrewAI AMP, o crewAI grava `.crewai/last_run.json` no diretório do projeto: o id da execução, se foi rastreada anonimamente ou na sua conta, quando começou e terminou, e o AMP para onde foi enviada. O `crewai eval` lê esse arquivo para avaliar a última execução sem você copiar um id. O `.gitignore` do scaffold já cobre `.crewai/`; só uma execução exportada por inteiro é registrada, e nada é gravado com `CREWAI_TESTING=true`.
|
||||
Após uma execução cujos spans chegaram ao CrewAI AMP, o crewAI grava `.crewai/last_run.json` no diretório do projeto: o id da execução, se foi rastreada anonimamente ou na sua conta, quando começou e terminou, e o AMP para onde foi enviada. O `crewai eval` lê esse arquivo para avaliar a última execução sem você copiar um id. O `.gitignore` do scaffold já cobre `.crewai/`; só uma execução exportada por inteiro é registrada; nada é gravado com `CREWAI_TESTING=true`, nem dentro de um deployment, onde a plataforma é dona do trace.
|
||||
|
||||
### Alternativa: Configuração de Variável de Ambiente
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@ before crewAI would start its own tracing, so this code never runs there.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -72,9 +74,19 @@ def record_last_run(
|
||||
try:
|
||||
path = last_run_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(path.name + ".tmp")
|
||||
temporary.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
|
||||
os.replace(temporary, path)
|
||||
# A temporary file of its own per writer: two crews finishing together in one
|
||||
# project must not write through the same name, or one record is lost.
|
||||
descriptor, temporary = tempfile.mkstemp(
|
||||
prefix=f".{path.name}.", suffix=".tmp", dir=path.parent
|
||||
)
|
||||
try:
|
||||
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
||||
handle.write(json.dumps(record, indent=2) + "\n")
|
||||
os.replace(temporary, path)
|
||||
except OSError:
|
||||
with contextlib.suppress(OSError):
|
||||
os.unlink(temporary)
|
||||
raise
|
||||
except OSError as error: # a vanished cwd fails last_run_path() too; the run is never failed by this
|
||||
logger.debug(
|
||||
"Could not record the last run in %s: %s",
|
||||
|
||||
@@ -25,7 +25,7 @@ def test_a_run_is_recorded_atomically_and_read_back(project):
|
||||
amp_base_url="https://app.crewai.com",
|
||||
)
|
||||
assert path == project / ".crewai" / "last_run.json"
|
||||
assert not path.with_name("last_run.json.tmp").exists()
|
||||
assert [child.name for child in path.parent.iterdir()] == ["last_run.json"] # no temporary file left behind
|
||||
written = json.loads(path.read_text(encoding="utf-8"))
|
||||
assert written["execution_id"] == "6f31fe1a-20bd-4bfe-a011-25d6b9341f62"
|
||||
assert written["tier"] == "ephemeral"
|
||||
@@ -63,3 +63,22 @@ def test_a_missing_or_broken_record_reads_as_none(project):
|
||||
def test_a_write_failure_never_raises(project, monkeypatch):
|
||||
(project / ".crewai").write_text("a file where the directory should be", encoding="utf-8")
|
||||
assert last_run.record_last_run(execution_id="x", tier=None, started_at_ns=None, finished_at_ns=None, amp_base_url=None) is None
|
||||
|
||||
|
||||
def test_each_writer_uses_a_temporary_file_of_its_own(project, monkeypatch):
|
||||
"""Two crews finishing together in one project: neither may replace the other's temporary file."""
|
||||
replaced: list[str] = []
|
||||
real_replace = last_run.os.replace
|
||||
monkeypatch.setattr(last_run.os, "replace", lambda src, dst: replaced.append(str(src)) or real_replace(src, dst))
|
||||
for execution_id in ("first", "second"):
|
||||
last_run.record_last_run(execution_id=execution_id, tier=None, started_at_ns=None, finished_at_ns=None, amp_base_url=None)
|
||||
assert len(replaced) == 2 and replaced[0] != replaced[1]
|
||||
names = [source.rsplit("/", 1)[-1] for source in replaced]
|
||||
assert all(name.startswith(".last_run.json.") and name.endswith(".tmp") for name in names)
|
||||
assert [child.name for child in (project / ".crewai").iterdir()] == ["last_run.json"]
|
||||
|
||||
|
||||
def test_a_failed_write_leaves_no_temporary_file(project, monkeypatch):
|
||||
monkeypatch.setattr(last_run.os, "replace", lambda src, dst: (_ for _ in ()).throw(OSError("disk full")))
|
||||
assert last_run.record_last_run(execution_id="x", tier=None, started_at_ns=None, finished_at_ns=None, amp_base_url=None) is None
|
||||
assert list((project / ".crewai").iterdir()) == []
|
||||
|
||||
Reference in New Issue
Block a user