Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,17 @@ LOG_SQL=false
# 数据库表结构一致性校验(启动时校验 DDL 与数据库是否一致)
DB_SCHEMA_CHECK_ENABLED=true
DB_SCHEMA_CHECK_FAIL_FAST=true

# AI 失败分析(AIFA):main_module→仓库 YAML 绝对路径,空则 repo_hint 为空(模板见 config/module_repo_mapping.yaml.example)
AI_MODULE_REPO_MAPPING_PATH=

# AI 失败分析(AIFA)代理配置
# AIFA_BASE_URL: AIFA 服务地址(不含 /v1/analyze)
AIFA_BASE_URL=http://127.0.0.1:8080
# AIFA_INTERNAL_TOKEN: dt-report 调用 AIFA 的内部 token(需与 AIFA 侧一致)
AIFA_INTERNAL_TOKEN=
# A5 限流:单 history_id 1 分钟最多 10 次(可通过环境变量覆盖)
AI_ANALYZE_RATE_LIMIT_WINDOW_SECONDS=60
AI_ANALYZE_RATE_LIMIT_MAX_REQUESTS=10
# dt-report -> AIFA 请求超时(秒)
AI_ANALYZE_TIMEOUT_SECONDS=180
30 changes: 30 additions & 0 deletions ai-failure-analyzer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 内部 Service Token(dt-report 或脚本调用 AIFA 时使用;禁止提交真实值)
AIFA_INTERNAL_TOKEN=

# LLM(OpenAI 兼容协议);Mock 模式下可不填
AIFA_LLM_BASE_URL=
AIFA_LLM_API_KEY=
AIFA_LLM_MODEL=gpt-4o-mini

# 设为 1 或 true 时不调用外网 LLM,返回固定 fixture(CI / 本地无密钥)
AIFA_LLM_MOCK=

# 监听端口(uvicorn 启动时可用,见 README)
AIFA_PORT=8080

# CodeHub(B5)
AIFA_CODEHUB_BASE_URL=
AIFA_CODEHUB_TOKEN=
AIFA_CODEHUB_CONNECT_TIMEOUT_SECONDS=3
AIFA_CODEHUB_READ_TIMEOUT_SECONDS=15
AIFA_CODEHUB_LIST_LIMIT=30
AIFA_CODEHUB_DIFF_MAX_LINES=500
AIFA_CODEHUB_DIFF_TOP_N=5
AIFA_CODEHUB_FALLBACK_WINDOW_DAYS=7

# C4:观测与成本
AIFA_MAX_TOKENS_PER_REQUEST=80000
AIFA_PRICE_PER_1K_INPUT=0
AIFA_PRICE_PER_1K_OUTPUT=0
AIFA_TRACE_LOG_PATH=trace.log
AIFA_MAX_CONCURRENT_ANALYSES=8
7 changes: 7 additions & 0 deletions ai-failure-analyzer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.venv/
__pycache__/
*.py[cod]
.pytest_cache/
*.egg-info/
dist/
build/
62 changes: 62 additions & 0 deletions ai-failure-analyzer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# AIFA:与 dt-report 使用相同基础镜像(ubuntu:20.04)、同一 docker/sources.list、同一套 apt 安装 Python 的方式
#(系统 python3 为 focal 自带版本,当前为 3.8.x;应用代码兼容 3.8+,见 pyproject.toml)。
#
# 构建必须在仓库根目录执行:
# docker build -f ai-failure-analyzer/Dockerfile -t aifa:a1 .
#
# 可选 build-arg(与根目录 Dockerfile 对齐):HTTP_PROXY、HTTPS_PROXY、NO_PROXY、PIP_INDEX_URL、PIP_TRUSTED_HOST

FROM ubuntu:20.04

ARG HTTP_PROXY
ARG HTTPS_PROXY
ARG NO_PROXY
ARG PIP_INDEX_URL
ARG PIP_TRUSTED_HOST

ENV DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

COPY docker/sources.list /etc/apt/sources.list

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*

RUN python3 -m venv /opt/venv

ENV PATH="/opt/venv/bin:$PATH"

WORKDIR /app

COPY ai-failure-analyzer/requirements.txt .
RUN PIP_TRUST=() \
&& if [ -n "${PIP_TRUSTED_HOST}" ]; then PIP_TRUST=(--trusted-host "${PIP_TRUSTED_HOST}"); fi \
&& if [ -n "${PIP_INDEX_URL}" ]; then \
echo "[docker build] 使用内网 PyPI: ${PIP_INDEX_URL}"; \
pip install --no-cache-dir --upgrade --default-timeout=180 --retries=5 "${PIP_TRUST[@]}" -i "${PIP_INDEX_URL}" "pip<25"; \
pip install --no-cache-dir --default-timeout=180 --retries=5 "${PIP_TRUST[@]}" -i "${PIP_INDEX_URL}" -r requirements.txt; \
else \
echo "[docker build] 使用默认 PyPI(需容器可访问外网或代理)"; \
pip install --no-cache-dir --upgrade --default-timeout=180 --retries=5 "pip<25"; \
pip install --no-cache-dir --default-timeout=180 --retries=5 -r requirements.txt; \
fi

COPY ai-failure-analyzer/pyproject.toml .
COPY ai-failure-analyzer/ai_failure_analyzer ./ai_failure_analyzer

RUN pip install --no-cache-dir .

EXPOSE 8080

CMD ["uvicorn", "ai_failure_analyzer.main:app", "--host", "0.0.0.0", "--port", "8080"]
116 changes: 116 additions & 0 deletions ai-failure-analyzer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# ai-failure-analyzer(AIFA)Phase A1

独立服务:失败用例 AI 辅助归因(当前已支持 **Plan / Act / Synthesize 三阶段主循环**,并具备 B1 报告/截图工具调用能力)。

规格说明:
- [docs/superpowers/specs/aifa-phase-a1-service-spec.md](../docs/superpowers/specs/aifa-phase-a1-service-spec.md)
- [docs/superpowers/specs/aifa-phase-b1-report-screenshot-tools-spec.md](../docs/superpowers/specs/aifa-phase-b1-report-screenshot-tools-spec.md)
- [docs/superpowers/specs/aifa-phase-b2-agent-three-stage-spec.md](../docs/superpowers/specs/aifa-phase-b2-agent-three-stage-spec.md)

## 环境要求

- **Python 3.8+**(与 `pyproject.toml` 中 `requires-python` 一致;便于与 dt-report 容器同为 3.8 或本地 3.10/3.11)。
- 技术选型文档仍推荐独立服务使用 **3.11**;若环境与镜像暂为 **3.8**,代码层面已兼容,行为与接口不变。

## 安装

```bash
cd ai-failure-analyzer
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade 'setuptools>=68' pip wheel
pip install -e ".[dev]"
```

若本机 `pip install -e` 报 PEP 660 相关错误,可改用**非 editable**:

```bash
pip install -r requirements-dev.txt
PYTHONPATH=. pytest -q
```

或仅生产依赖:

```bash
pip install -r requirements.txt
pip install .
```

## 环境变量

复制 `.env.example` 为 `.env` 并按需填写。键名说明见 `.env.example`。

- **必须(生产)**:`AIFA_INTERNAL_TOKEN`
- **真实 LLM**:`AIFA_LLM_BASE_URL`、`AIFA_LLM_API_KEY`、`AIFA_LLM_MODEL`(Mock 模式下可不填)
- **CI / 无密钥**:`AIFA_LLM_MOCK=1`
- **CodeHub(B5)**:`AIFA_CODEHUB_BASE_URL`、`AIFA_CODEHUB_TOKEN`(未配置时自动降级跳过 code_blame)

## 启动

```bash
cd ai-failure-analyzer
source .venv/bin/activate
export AIFA_INTERNAL_TOKEN="dev-only-change-me"
export AIFA_LLM_MOCK=1
uvicorn ai_failure_analyzer.main:app --host 0.0.0.0 --port 8080
```

## 健康检查

```bash
curl -sS http://127.0.0.1:8080/healthz | jq .
```

## 指标与 trace(C4)

```bash
curl -sS http://127.0.0.1:8080/metrics | jq .
```

- `/metrics` 返回进程内聚合指标(requests/tokens/cost/熔断次数等)
- 每次分析会追加一条 JSONL 到 `AIFA_TRACE_LOG_PATH`(默认 `trace.log`)
- 单请求 token 达到 `AIFA_MAX_TOKENS_PER_REQUEST` 时会触发熔断并返回 `partial`

## 分析(SSE)

请求体校验失败时返回 **422**(FastAPI 默认,字段为 `detail`)。请求体过大返回 **400**。

```bash
curl -sS -N \
-H "Authorization: Bearer dev-only-change-me" \
-H "Content-Type: application/json" \
-d '{"session_id":"550e8400-e29b-41d4-a716-446655440000","mode":"initial","case_context":{"case_name":"demo_case","batch":"b1","platform":"Android"}}' \
http://127.0.0.1:8080/v1/analyze
```

无 `Authorization` 或 token 错误时返回 **401**(JSON,非 SSE)。

## LLM 说明

使用 OpenAI 兼容 `AsyncOpenAI`,默认请求 `response_format={"type":"json_object"}`。若你的兼容网关不支持该参数,可能报错;此时可仅用 Mock 跑通 CI,或联系管理员调整网关。

## 测试

```bash
cd ai-failure-analyzer
pip install -e ".[dev]"
pytest -q
```

## Docker(可选)

镜像与 **dt-report 根目录 Dockerfile** 对齐:`FROM ubuntu:20.04`、`COPY docker/sources.list`,`apt` 安装 **`python3` / `python3-pip` / `python3-venv`**(focal 上为 **Python 3.8.x**),`python3 -m venv /opt/venv`,**不**使用 deadsnakes / Launchpad PPA。应用代码兼容 **3.8+**(见 `pyproject.toml`)。

**构建必须在仓库根目录执行**(否则无法 `COPY docker/sources.list`):

```bash
cd /path/to/QualityBoard # 仓库根目录
docker build -f ai-failure-analyzer/Dockerfile -t aifa:a1 .
docker run --rm -e AIFA_INTERNAL_TOKEN=test -e AIFA_LLM_MOCK=1 -p 8080:8080 aifa:a1
```

内网 PyPI 与 dt-report 相同方式传入即可,例如:

`--build-arg PIP_INDEX_URL=... --build-arg PIP_TRUSTED_HOST=...`

若需 **Python 3.10/3.11** 独立镜像,可自行使用 `FROM python:3.11-slim` 等单独维护一条构建线;本仓库默认与 dt-report 同一 Ubuntu + apt Python 路径。
1 change: 1 addition & 0 deletions ai-failure-analyzer/ai_failure_analyzer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""AIFA — ai-failure-analyzer 服务包。"""
1 change: 1 addition & 0 deletions ai-failure-analyzer/ai_failure_analyzer/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

33 changes: 33 additions & 0 deletions ai-failure-analyzer/ai_failure_analyzer/api/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""健康检查。"""

from fastapi import APIRouter, Depends

from ai_failure_analyzer.core.config import Settings, get_settings
from ai_failure_analyzer.services.observability import get_metrics_snapshot

router = APIRouter(tags=["health"])


@router.get("/healthz")
async def healthz(settings: Settings = Depends(get_settings)) -> dict:
if settings.aifa_llm_mock:
llm_status = "ok"
elif settings.aifa_llm_base_url and settings.aifa_llm_api_key:
llm_status = "ok"
else:
llm_status = "not_configured"

return {
"status": "ok",
"checks": {
"process": "ok",
"report_fetch": "skipped",
"codehub": "skipped",
"llm": llm_status,
},
}


@router.get("/metrics")
async def metrics() -> dict:
return get_metrics_snapshot()
1 change: 1 addition & 0 deletions ai-failure-analyzer/ai_failure_analyzer/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Loading