Spaces:
Sleeping
Sleeping
Commit ·
725b08e
1
Parent(s): c540d93
fix: preserve bulk boundary \n + substring-count dedupe + 30-char prefix
Browse files- bulk join "" → "\n" to keep paragraph boundaries across bulks
- apply_paragraph_dedupe: substring-count input to tolerate LLM paragraph restructuring
- prefix_len default 80 → 30 to catch corrected+original echo pairs
- temperature 0 → 0.0001 to loosen greedy decoding bias
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pipelines.py +21 -11
- postprocess.py +14 -10
pipelines.py
CHANGED
|
@@ -416,7 +416,11 @@ def call_llm(
|
|
| 416 |
system_prompt: str,
|
| 417 |
user_content: str,
|
| 418 |
model: str = "solar-pro2",
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
reasoning_effort: str | None = None,
|
| 421 |
max_tokens: int | None = None,
|
| 422 |
response_format: dict | None = None,
|
|
@@ -804,12 +808,14 @@ def apply_rule(
|
|
| 804 |
)
|
| 805 |
elif rule == "paragraph_dedupe":
|
| 806 |
config = step.get("config", {})
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
|
|
|
|
|
|
| 813 |
|
| 814 |
return text
|
| 815 |
|
|
@@ -1068,10 +1074,14 @@ def run_pipeline(
|
|
| 1068 |
processed = process_bulks_parallel(
|
| 1069 |
bulks, original_bulks, step, model, prompts, client
|
| 1070 |
)
|
| 1071 |
-
#
|
| 1072 |
-
#
|
| 1073 |
-
#
|
| 1074 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1075 |
elif step["type"] == "specialist":
|
| 1076 |
text = run_specialist(step, text, original_text, prompts, model, client)
|
| 1077 |
# If a step produced an unusably short output (< 10% of input), the
|
|
|
|
| 416 |
system_prompt: str,
|
| 417 |
user_content: str,
|
| 418 |
model: str = "solar-pro2",
|
| 419 |
+
# Upstage 서빙 스택이 temperature=0 에서 greedy 디코딩 경로로 들어가는데,
|
| 420 |
+
# 이 경로가 특정 입력(truncated article 등)에서 학습 데이터의 "재시작" 패턴을
|
| 421 |
+
# 재현성 높게 재현하는 바이어스가 관찰됨. 0.0001 로 sampling 경로로 살짝
|
| 422 |
+
# 밀어 넣어 bias 를 흔들되, argmax 확률 비중은 거의 그대로 유지.
|
| 423 |
+
temperature: float = 0.0001,
|
| 424 |
reasoning_effort: str | None = None,
|
| 425 |
max_tokens: int | None = None,
|
| 426 |
response_format: dict | None = None,
|
|
|
|
| 808 |
)
|
| 809 |
elif rule == "paragraph_dedupe":
|
| 810 |
config = step.get("config", {})
|
| 811 |
+
# prefix_len 기본값은 apply_paragraph_dedupe 의 기본값(30)을 따른다.
|
| 812 |
+
# 명시적으로 config 에 지정된 경우만 override.
|
| 813 |
+
kwargs: dict = {}
|
| 814 |
+
if "min_len" in config:
|
| 815 |
+
kwargs["min_len"] = config["min_len"]
|
| 816 |
+
if "prefix_len" in config:
|
| 817 |
+
kwargs["prefix_len"] = config["prefix_len"]
|
| 818 |
+
return apply_paragraph_dedupe(text, original_text, **kwargs)
|
| 819 |
|
| 820 |
return text
|
| 821 |
|
|
|
|
| 1074 |
processed = process_bulks_parallel(
|
| 1075 |
bulks, original_bulks, step, model, prompts, client
|
| 1076 |
)
|
| 1077 |
+
# Bulk 경계는 원본에서 \n 로 분리돼 있었으므로 합칠 때도 \n 로
|
| 1078 |
+
# 연결한다. "".join 으로 붙이면 인접 bulk 의 문단이 한 줄로
|
| 1079 |
+
# 뭉개지고, 이후 split_into_bulks 재실행 시 문단 수가 줄어
|
| 1080 |
+
# step1 의 <원문>↔<교열_모델_수정결과> 정렬이 어긋나며 구조
|
| 1081 |
+
# 보존 규칙까지 깨진다 (참조: run.py:326 "\n".join).
|
| 1082 |
+
text = "\n".join(
|
| 1083 |
+
str(p) if not isinstance(p, str) else p for p in processed
|
| 1084 |
+
)
|
| 1085 |
elif step["type"] == "specialist":
|
| 1086 |
text = run_specialist(step, text, original_text, prompts, model, client)
|
| 1087 |
# If a step produced an unusably short output (< 10% of input), the
|
postprocess.py
CHANGED
|
@@ -380,7 +380,7 @@ def apply_paragraph_dedupe(
|
|
| 380 |
text: str,
|
| 381 |
original: str,
|
| 382 |
min_len: int = 40,
|
| 383 |
-
prefix_len: int =
|
| 384 |
) -> str:
|
| 385 |
"""LLM이 뱉은 중복 문단을 제거한다.
|
| 386 |
|
|
@@ -411,13 +411,12 @@ def apply_paragraph_dedupe(
|
|
| 411 |
if not text:
|
| 412 |
return text
|
| 413 |
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
in_prefix[norm[:prefix_len]] += 1
|
| 421 |
|
| 422 |
out_paras = _split_output_paragraphs(text)
|
| 423 |
out_exact_seen: Counter[str] = Counter()
|
|
@@ -435,12 +434,17 @@ def apply_paragraph_dedupe(
|
|
| 435 |
out_exact_seen[norm] += 1
|
| 436 |
out_prefix_seen[prefix] += 1
|
| 437 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
exact_dup = (
|
| 439 |
-
out_exact_seen[norm] >
|
| 440 |
and out_exact_seen[norm] >= 2
|
| 441 |
)
|
| 442 |
near_dup = (
|
| 443 |
-
out_prefix_seen[prefix] >
|
| 444 |
and out_prefix_seen[prefix] >= 2
|
| 445 |
)
|
| 446 |
|
|
|
|
| 380 |
text: str,
|
| 381 |
original: str,
|
| 382 |
min_len: int = 40,
|
| 383 |
+
prefix_len: int = 30,
|
| 384 |
) -> str:
|
| 385 |
"""LLM이 뱉은 중복 문단을 제거한다.
|
| 386 |
|
|
|
|
| 411 |
if not text:
|
| 412 |
return text
|
| 413 |
|
| 414 |
+
# Input 은 "normalized 전체 문자열" 로 두고 substring count 를 쓴다.
|
| 415 |
+
# 이전 구현은 input 을 문단 Counter 로 셌는데, LLM 이 문단 경계를 재구조화
|
| 416 |
+
# (예: 줄바꿈 없이 중복 문장이 들어있던 한 문단을 여러 문단으로 쪼갬) 하면
|
| 417 |
+
# output 문단이 input 문단과 exact match 가 안 되어 input_count=0 으로 잡혀,
|
| 418 |
+
# 정당한 중복 (저자/원본이 의도한 반복) 까지 drop 되는 버그가 있었다.
|
| 419 |
+
in_norm = _normalize_paragraph(original or "")
|
|
|
|
| 420 |
|
| 421 |
out_paras = _split_output_paragraphs(text)
|
| 422 |
out_exact_seen: Counter[str] = Counter()
|
|
|
|
| 434 |
out_exact_seen[norm] += 1
|
| 435 |
out_prefix_seen[prefix] += 1
|
| 436 |
|
| 437 |
+
# input 문자열 전체에서 해당 문단(또는 prefix)이 몇 번 substring 으로
|
| 438 |
+
# 등장하는지 집계. output_count 가 input_count 를 초과할 때만 drop.
|
| 439 |
+
in_exact_count = in_norm.count(norm) if in_norm else 0
|
| 440 |
+
in_prefix_count = in_norm.count(prefix) if in_norm else 0
|
| 441 |
+
|
| 442 |
exact_dup = (
|
| 443 |
+
out_exact_seen[norm] > in_exact_count
|
| 444 |
and out_exact_seen[norm] >= 2
|
| 445 |
)
|
| 446 |
near_dup = (
|
| 447 |
+
out_prefix_seen[prefix] > in_prefix_count
|
| 448 |
and out_prefix_seen[prefix] >= 2
|
| 449 |
)
|
| 450 |
|