Metadata-Version: 2.4
Name: thesis-review-agent
Version: 0.3.2
Summary: AI-assisted HWPX thesis citation and reference validation
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: openai-agents>=0.22.3
Requires-Dist: langgraph>=1.2.11
Requires-Dist: python-hwpx>=6.4.0
Requires-Dist: python-dotenv>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=9.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=1.0.0; extra == "dev"

# Thesis Review Agent

**현재 패키지 버전: 0.3.2** (`pyproject.toml` 기준)

HWPX 논문에서 본문 인용, 참고문헌, 표·각주를 추출하고 AI 분류와 규칙 검증으로 검토 후보를 반환하는 Python 라이브러리입니다. 문장 분류 과정에서 오타도 함께 검수합니다. 검토가 끝나면 위반 범위에 HWPX 네이티브 형광펜을 적용한 문서 객체와 결과 모델을 반환합니다.

## 0.3.2 변경 사항

- 문장 분류 Agent와 오타 검수 후처리의 책임을 분리했습니다. Agent 요청 수와 문장별 분류·검수 방식은 그대로 유지합니다.
- 문장 분류 호출 조정, 오타 검출 결과 변환, 반복 오타 전파 로직을 `sentence_classify_node/usecases/`의 모듈로 나눴습니다. 모듈 구성과 실행 책임은 [오타 검수 문서](thesis_review_agent/graph/nodes/classification/sentence_classify_node/README.md#코드-책임-분리)를 참고하세요.

## 0.3.1 변경 사항

- 각주와 표·그림 주를 구분해 NOTE 규칙을 검사합니다. 입력 구조로 확인할 수 없는 페이지 배치나 그림 범례는 판정하지 않습니다.
- `consistency_validate_node`가 문서 안에서 우세한 국문 표기 방식과 다른 인용·참고문헌 후보를 찾아 `consistency_issues`로 반환합니다. 관찰값이 부족하거나 표기가 동률이면 위반을 만들지 않습니다.
- REF-001·002는 본문 인용과 참고문헌을 정규화한 저자·연도 키로 대조합니다. 인용에 공저자가 명시되면 저자 수와 순서도 비교합니다. 모호한 대응은 자동 위반으로 단정하지 않습니다.
- 표·캡션·셀과 각주를 항상 추출하고 일반 실행에서 분류·검증합니다. 최종 리뷰도 항상 생성합니다.
- CIT, REF/TYP, NOTE 위반 표시를 옵션으로 끌 수 있습니다. 기본값은 켜짐입니다.

## 설치

Python 3.12 이상이 필요합니다. 저장소를 내려받아 프로젝트 루트에서 설치합니다.

```bash
python -m pip install -e .
```

개발 의존성과 테스트 도구까지 설치하려면 다음을 사용합니다.

```bash
python -m pip install -e '.[dev]'
```

기본 모델은 `gpt-6-luna`입니다. 기본 OpenAI 클라이언트를 쓸 때는 `OPENAI_API_KEY`가 필요합니다. ChatGPT 웹 로그인이나 구독은 API 키를 대신하지 않습니다.

```bash
export OPENAI_API_KEY="your-api-key"
```

PowerShell에서는 다음과 같이 설정합니다.

```powershell
$env:OPENAI_API_KEY = "your-api-key"
```

## 빠른 시작

```python
import asyncio
from pathlib import Path

from thesis_review_agent import ThesisReviewAgent


async def main() -> None:
    agent = ThesisReviewAgent()
    result = await agent.run(Path("thesis.hwpx").read_bytes())

    print("최종 리뷰:", result.final_review)
    print("인용 위반:", len(result.quotation_issues))
    print("참고문헌 위반:", len(result.reference_issues))
    print("주석 위반:", len(result.note_issues))
    print("표기 일관성:", len(result.consistency_issues))
    print("오타 후보:", len(result.typo_issues))

    # 저장은 호출자가 선택적으로 수행합니다.
    if result.document is not None:
        result.document.save_to_path("thesis.reviewed.hwpx")


asyncio.run(main())
```

`run()`은 내부 그래프 상태가 아닌 공개 응답 모델 `ThesisReviewResult`를 반환합니다. 검토 문서를 Python에서 저장하려면 `result.document.save_to_path(...)` 또는 `to_bytes()`를 호출합니다. JSON/SSE 응답에는 HWPX 원본과 검토본 바이트가 Base64 문자열로 포함됩니다. JSON 크기를 줄이거나 원본을 재전송하지 않으려면 `source_hwpx_bytes`를 제외해 직렬화하세요.

```python
payload = result.model_dump(mode="json", exclude={"source_hwpx_bytes"})
```

## 옵션

`options`는 딕셔너리 또는 `ReviewOptions`로 전달합니다. 알 수 없는 키, 잘못된 값 형식, 1 미만의 동시 요청 수는 오류를 반환합니다.

| 옵션 | 기본값 | 동작 |
| --- | ---: | --- |
| `extract_only` | `False` | `True`이면 HWPX 구조를 추출한 직후 멈춥니다. AI 분류·검증은 실행하지 않고 결과를 `extracted_document`에 반환합니다. |
| `max_concurrent_requests` | `10` | 문장별 분류·오타 검수 요청의 최대 동시 실행 수입니다. |
| `highlight_quotation_issues` | `True` | CIT 위반 범위를 HWPX에서 표시합니다. |
| `highlight_reference_issues` | `True` | REF·TYP 위반 범위를 HWPX에서 표시합니다. |
| `highlight_note_issues` | `True` | NOTE 위반 범위를 HWPX에서 표시합니다. |

표·각주 추출과 최종 리뷰 생성은 항상 수행됩니다. 지원 옵션에 최종 리뷰를 끄는 키는 없습니다. 표시 옵션을 끄더라도 검증 결과는 반환되며, 해당 유형의 HWPX 표시와 `highlight_id` 연결만 생략됩니다. 오타 표시는 위 세 옵션과 별개로 적용됩니다.

```python
from thesis_review_agent import ReviewOptions, ThesisReviewAgent

options: ReviewOptions = {
    "max_concurrent_requests": 5,
    "highlight_quotation_issues": False,
}
result = await ThesisReviewAgent().run(hwpx_bytes, options=options)
```

### 추출 결과만 확인하기

AI 호출 없이 HWPX 추출 구조를 살펴보려면 `extract_only`를 설정합니다.

```python
result = await ThesisReviewAgent().run(
    hwpx_bytes,
    options={"extract_only": True},
)
assert result.extracted_document is not None
print(result.extracted_document.model_dump_json(indent=2))
```

이 모드에서는 분류·검증·최종 리뷰를 실행하지 않습니다. 일반 검토에서는 표 셀·캡션과 각주도 문장별 분류 및 오타 검수에 포함됩니다.

실제 파일을 사용하는 저장소 내 추출 예제는 다음 테스트 함수에서 볼 수 있습니다. 이 테스트들은 `extract_only`를 사용하므로 AI API를 호출하지 않으며, 기본 입력은 프로젝트 루트의 `test.hwpx`입니다.

```bash
python -m pytest tests/test_thesis_review_agent.py::test_extract_hwpx_tables_without_classification -m integration -s
python -m pytest tests/test_thesis_review_agent.py::test_extract_hwpx_footnotes_without_classification -m integration -s
```

다른 파일은 `THESIS_REVIEW_HWPX=/path/to/file.hwpx`로 지정합니다. 테스트는 추출된 표·캡션·셀 내용 또는 각주 본문·본문 앵커를 콘솔에 출력합니다.

## 모델 설정

`AgentModelConfig`로 모델, OpenAI 호환 API Base URL, 토큰을 모든 Agent에 공통 적용할 수 있습니다. 별도 설정이 없으면 OpenAI 기본 클라이언트와 `OPENAI_API_KEY`를 사용합니다.

```python
import os

from thesis_review_agent import AgentModelConfig, ThesisReviewAgent

agent = ThesisReviewAgent(
    model_config=AgentModelConfig(
        base_url=os.environ.get("AGENT_BASE_URL"),
        model=os.environ.get("AGENT_MODEL", "gpt-6-luna"),
        api_key=os.environ.get("AGENT_API_KEY") or os.environ.get("AGENT_TOKEN"),
    )
)
result = await agent.run(hwpx_bytes)
```

`AGENT_*` 환경 변수는 위 예제처럼 `model_config`에 직접 전달해야 합니다. `AGENT_API_KEY`가 없고 `AGENT_TOKEN`도 없으면 OpenAI SDK는 기본 OpenAI 클라이언트에서 `OPENAI_API_KEY`를 사용합니다. 테스트 코드는 추가로 `OPENAI_BASE_URL`과 `OPENAI_MODEL`을 fallback으로 읽습니다.

| 서버 | Base URL 예시 | 모델 값 | 키 |
| --- | --- | --- | --- |
| OpenAI 기본 클라이언트 | 지정하지 않음 | 기본값 `gpt-6-luna` 또는 설정한 모델 | `OPENAI_API_KEY` |
| OpenRouter | `https://openrouter.ai/api/v1` | 서버에 등록된 전체 모델 ID | OpenRouter API 키 |
| 로컬 OpenAI 호환 서버 | 예: `http://127.0.0.1:8000/v1` | 서버에 등록한 모델 ID | 서버 설정에 맞춘 키; 무인증 서버는 임의 문자열을 요구할 수 있음 |

커스텀 서버는 OpenAI 호환 Chat Completions API와 JSON Schema 구조화 출력을 지원해야 합니다. 모델·서버별 지원 차이는 해당 제공자 문서를 확인하세요. 문장·인용·참고문헌 내용이 모델 제공자에게 전송되고 비용이 발생할 수 있습니다.

## 스트리밍

`stream()`은 `progress`, `result`, `done` 이벤트를 순서대로 반환합니다. 각 이벤트는 `to_sse()`로 표준 SSE 형식으로 인코딩할 수 있습니다.

```python
async for event in agent.stream(hwpx_bytes):
    print(event.to_sse(), end="")
    if event.event == "result":
        result = event.data
```

`progress` 데이터에는 노드 이름과 사용자용 진행 메시지가, `result`에는 `ThesisReviewResult`가, `done`에는 정상 종료 정보가 담깁니다. `result` 이벤트의 `reviewed_hwpx_bytes`를 Base64 디코딩해 rhwp에 열면 표시가 적용된 파일을 확인할 수 있습니다. 원본 파일 대신 검토본 바이트를 사용하세요.

## 결과 모델

| 필드 | 내용 |
| --- | --- |
| `quotations` | 추출한 본문 인용과 분류 결과 |
| `references` | 참고문헌 항목, REF/TYP 분류와 서식 정보 |
| `reference_scope_matches` | 본문 인용과 참고문헌의 규칙 기반 대조 결과 |
| `quotation_issues` | CIT 위반 후보 |
| `reference_issues` | REF·TYP 위반 후보 |
| `note_issues` | 각주·표·그림 주의 NOTE 위반 후보 |
| `consistency_issues` | 문서 전체 표기 일관성(MIX) 위반 후보 |
| `typo_issues` | 오타·띄어쓰기 검수 결과와 수정 제안 |
| `final_review` | 반복되는 오류 경향과 재확인할 부분을 요약한 최종 의견 |
| `document` | HWPX 표시가 적용된 `HwpxDocument`. JSON에는 포함되지 않으며 호출자가 저장 |
| `reviewed_hwpx_bytes` | 표시가 적용된 HWPX 바이트. JSON에서는 Base64 문자열 |
| `extracted_document` | `extract_only=True`일 때의 미분류 추출 구조 |

위반 객체는 규칙 ID, 메시지, 원문 문맥, 문서 위치와 가능할 때 `document_ranges`를 포함합니다. 오타 객체는 `original_text`, `suggested_text`, `severity`, `review_note`, UTF-16 위치 범위를 제공합니다. `serious_typo`는 명백한 글자·철자 오류, `suspected_typo`는 띄어쓰기 또는 사람 확인이 필요한 어휘 후보입니다. 오타는 자동으로 고치지 않습니다.

정확한 위반 범위를 원문에서 유일하게 찾은 경우 HWPX 표시와 JSON 범위가 연결됩니다. ID는 `hl-0001`처럼 증가하며, HWPX의 범위 시작 `hp:fieldBegin/@name`과 JSON `highlight_id`/`document_ranges[].range_id`가 같은 ID를 가리킵니다. 범위 오프셋은 문단 내 UTF-16 코드 유닛 기준의 반개구간 `[start, end)`이며 JavaScript 문자열 인덱스와 호환됩니다. 원문에서 위치를 유일하게 확정할 수 없으면 표시 ID나 범위가 비어 있을 수 있습니다. 이 경우 프론트엔드는 임의의 전체 문장 검색으로 위치를 추정하지 않아야 합니다.

정상 인용·참고문헌은 유형별 색으로 표시할 수 있지만 ID를 발급하지 않습니다. 위반·오타 표시는 색을 적용하고 범위 ID를 연결합니다. 인용, 참고문헌 일반 규칙, 자료유형 규칙, 주석, 오타는 서로 다른 색상으로 구분합니다. 색상·HWPX 구조와 위치 매칭 세부사항은 [하이라이트 노드 문서](thesis_review_agent/graph/nodes/annotation/highlight_violations_node/README.md)를 참고하세요.

## 검증 흐름과 세부 문서

```text
HWPX 추출 및 변환
  → 문장 분류·오타 검수
  → 인용·참고문헌 추출 및 분류
  → 본문 인용과 참고문헌 대응 대조
  → CIT / REF / TYP / NOTE / MIX 검증
  → 위반 의심 AI 판정 및 최종 리뷰
  → HWPX 네이티브 형광펜 적용
```

REF-001·002 대응 대조는 규칙 기반이며 별도 AI 호출을 하지 않습니다. 분류가 필요한 자료 유형과 적용 조건은 AI가 판단하고, 개별 검증기는 선택된 규칙을 적용합니다. 노드별 정규식·실행 조건·예시는 다음 문서를 참고하세요.

- [그래프 실행 흐름](thesis_review_agent/graph/README.md)
- [본문 인용 검증](thesis_review_agent/graph/nodes/validation/quotation_validate_node/README.md)
- [참고문헌 일반 검증](thesis_review_agent/graph/nodes/validation/reference_validate_node/README.md)
- [참고문헌 자료유형 분류·검증](thesis_review_agent/graph/nodes/validation/reference_type_validation_node/README.md)
- [주석 검증](thesis_review_agent/graph/nodes/validation/note_validate_node/README.md)
- [표기 일관성 검증](thesis_review_agent/graph/nodes/validation/consistency_validate_node/README.md)
- [오타 검수](thesis_review_agent/graph/nodes/classification/sentence_classify_node/README.md)
- [최종 리뷰](thesis_review_agent/graph/nodes/reviewer/review_node/README.md)
- [형광펜·범위 ID](thesis_review_agent/graph/nodes/annotation/highlight_violations_node/README.md)

## 테스트

AI를 호출하지 않는 기본 테스트는 다음 명령으로 실행합니다.

```bash
python -m pytest
```

실제 AI 통합 테스트는 API 키와 HWPX 입력이 있을 때만 실행하세요. 저장소의 `tests/test_thesis_review_agent.py`는 프로젝트 루트 `.env`에서 설정을 읽고, 기본 입력으로 `test.hwpx`를 사용합니다. 다른 입력 파일은 `THESIS_REVIEW_HWPX`로 지정합니다.

```dotenv
# OpenAI 기본 클라이언트
OPENAI_API_KEY=your-openai-api-key

# 또는 OpenRouter/로컬 OpenAI 호환 서버 설정
AGENT_BASE_URL=https://openrouter.ai/api/v1
AGENT_MODEL=provider/model-id
AGENT_API_KEY=your-provider-api-key
```

`AGENT_TOKEN`은 테스트 코드에서 `AGENT_API_KEY`의 별칭으로 읽습니다. 실제 통합 테스트를 명시적으로 켜려면 다음을 실행합니다.

```bash
RUN_AI_INTEGRATION_TESTS=1 python -m pytest -m integration -s
```

## 제한 사항

- 입력 형식은 HWPX입니다. DOCX와 PDF 입력은 지원하지 않습니다.
- 자동 추출·분류·검증은 오류를 놓치거나 잘못된 후보를 낼 수 있습니다. 결과를 원문과 적용할 학술 작성 지침에 대조하세요.
- 입력 구조에서 위치를 확정할 수 없는 항목은 HWPX에서 표시되지 않을 수 있지만, 검증 결과에는 남습니다.
- 페이지 배치, 그림 범례 등 추출 모델이 제공하지 않는 정보는 검증하지 않습니다.
- 생성된 HWPX는 자동 저장되지 않습니다. `result.document` 또는 `result.reviewed_hwpx_bytes`를 호출자가 저장·전달합니다.
