Update app.py
Browse files
app.py
CHANGED
|
@@ -631,6 +631,7 @@ def generate_article_interface(game_state):
|
|
| 631 |
}
|
| 632 |
return None
|
| 633 |
|
|
|
|
| 634 |
# --- 기사 평가 및 데스크 피드백 (OpenAI 사용) ---
|
| 635 |
def evaluate_article_and_get_feedback_openai(article, game_state, assignment_data):
|
| 636 |
status = game_state['status']
|
|
@@ -641,10 +642,10 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
|
|
| 641 |
article_tone_text = get_text_func(f"article_tone_{article['tone']}")
|
| 642 |
|
| 643 |
prompt = f"""
|
| 644 |
-
당신은 어린이 신문사의
|
| 645 |
-
이 기자는 초등학생 독자들도
|
| 646 |
|
| 647 |
-
[오늘의 할 일]
|
| 648 |
{current_assignment_text}
|
| 649 |
|
| 650 |
[기자가 적은 내용 (취재 노트)]
|
|
@@ -656,69 +657,83 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
|
|
| 656 |
- 선택한 분위기: {article_tone_text}
|
| 657 |
|
| 658 |
[평가 항목 및 피드백 가이드]
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
*
|
| 666 |
-
*
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
*
|
| 671 |
-
*
|
| 672 |
-
* **피드백
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
반드시 아래 JSON 형식으로 응답해주세요:
|
| 679 |
{{
|
| 680 |
"score_fact": 점수(숫자),
|
| 681 |
-
"comment_fact": "
|
| 682 |
"score_logic": 점수(숫자),
|
| 683 |
-
"comment_logic": "
|
| 684 |
"score_tone": 점수(숫자),
|
| 685 |
-
"comment_tone": "기사
|
| 686 |
"total_score": 총점(숫자),
|
| 687 |
-
"overall_feedback": "
|
| 688 |
}}
|
| 689 |
"""
|
| 690 |
try:
|
| 691 |
-
with st.spinner("AI 편집장님이 기사를
|
| 692 |
response = client.chat.completions.create(
|
| 693 |
-
model="gpt-
|
| 694 |
messages=[{"role": "user", "content": prompt}],
|
| 695 |
response_format={"type": "json_object"},
|
| 696 |
-
temperature=0.
|
| 697 |
-
max_tokens=
|
| 698 |
)
|
| 699 |
ai_evaluation_str = response.choices[0].message.content
|
| 700 |
ai_evaluation = json.loads(ai_evaluation_str)
|
| 701 |
|
| 702 |
total_score = ai_evaluation.get("total_score", 0)
|
| 703 |
-
overall_feedback = ai_evaluation.get("overall_feedback", "AI
|
| 704 |
|
| 705 |
except Exception as e:
|
| 706 |
st.error(get_text_func("error_openai_api").format(error=str(e)))
|
| 707 |
-
total_score = random.randint(
|
| 708 |
-
overall_feedback = "AI
|
| 709 |
|
| 710 |
-
article['ai_score'] = total_score
|
| 711 |
|
| 712 |
-
trust_change = (total_score -
|
| 713 |
status['public_trust'] = max(0, min(100, status['public_trust'] + trust_change))
|
| 714 |
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
if
|
| 721 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
|
| 723 |
status['article_score_total'] += total_score
|
| 724 |
status['article_count'] += 1
|
|
@@ -728,12 +743,10 @@ def evaluate_article_and_get_feedback_openai(article, game_state, assignment_dat
|
|
| 728 |
game_state['event_log'].append(get_text_func("log_desk_feedback").format(feedback=overall_feedback))
|
| 729 |
if trust_change != 0: game_state['event_log'].append(get_text_func("log_trust_change").format(change=trust_change))
|
| 730 |
|
| 731 |
-
game_state['submitted_articles'].append(article)
|
| 732 |
game_state['reporter_notebook'] = []
|
| 733 |
|
| 734 |
-
|
| 735 |
-
# return ai_evaluation
|
| 736 |
-
return overall_feedback
|
| 737 |
|
| 738 |
# --- UI 표시 함수들 ---
|
| 739 |
def display_reporter_dashboard(game_state):
|
|
|
|
| 631 |
}
|
| 632 |
return None
|
| 633 |
|
| 634 |
+
# --- 기사 평가 및 데스크 피드백 (OpenAI 사용) ---
|
| 635 |
# --- 기사 평가 및 데스크 피드백 (OpenAI 사용) ---
|
| 636 |
def evaluate_article_and_get_feedback_openai(article, game_state, assignment_data):
|
| 637 |
status = game_state['status']
|
|
|
|
| 642 |
article_tone_text = get_text_func(f"article_tone_{article['tone']}")
|
| 643 |
|
| 644 |
prompt = f"""
|
| 645 |
+
당신은 어린이 신문사의 엄격하지만 공정한 편집장입니다. 당신의 목표는 새내기 기자가 최고의 기사를 쓸 수 있도록 객관적으로 평가하고, 명확한 개선 방향을 제시하는 것입니다.
|
| 646 |
+
이 기자는 초등학생 독자들도 이해할 수 있는 글을 쓰려고 노력하고 있지만, 아직 미숙한 점이 많을 수 있습니다. 칭찬은 정말 뛰어난 부분에 한정하고, 대부분의 피드백은 구체적인 문제점 지적과 개선 방안 제시에 집중해주세요.
|
| 647 |
|
| 648 |
+
[오늘의 할 일 (취재 지시)]
|
| 649 |
{current_assignment_text}
|
| 650 |
|
| 651 |
[기자가 적은 내용 (취재 노트)]
|
|
|
|
| 657 |
- 선택한 분위기: {article_tone_text}
|
| 658 |
|
| 659 |
[평가 항목 및 피드백 가이드]
|
| 660 |
+
각 항목별로 점수를 부여하고, **매우 구체적이고 비��적인 관점에서 개선점을 명확히 지적**해주세요.
|
| 661 |
+
피드백은 기자가 무엇을 잘못했고, 어떻게 고쳐야 하는지 명확히 알 수 있도록 작성합니다.
|
| 662 |
+
|
| 663 |
+
1. **내용의 정확성, 중요도 및 깊이 (40점 만점):**
|
| 664 |
+
* 취재 노트의 핵심 정보와 취재 지시의 요구사항이 기사에 정확하고 충분히 반영되었는가?
|
| 665 |
+
* 단순 사실 나열을 넘어, 사건의 의미나 배경에 대한 고민이 담겨 있는가? (초등학생 눈높이에서)
|
| 666 |
+
* 불필요하거나 중요도가 낮은 내용이 포함되지는 않았는가? 혹은 중요한 내용이 누락되지는 않았는가?
|
| 667 |
+
* **피드백 예시:** "취재 노트에 있는 OOO 사실은 중요하지만 기사에 충분히 설명되지 않았습니다. 이 부분을 XXX 방식으로 보강해야 합니다." 또는 "YYY 내용은 현재 취재 지시와 관련성이 낮아 보입니다. 대신 ZZZ 관점을 추가하는 것을 고려해보세요."
|
| 668 |
+
|
| 669 |
+
2. **논리적 흐름, 명확성 및 어휘 수준 (30점 만점):**
|
| 670 |
+
* 기사의 논리가 명확하고, 문장 간 연결이 자연스러운가?
|
| 671 |
+
* 초등학생 독자가 이해하기 어려운 단어나 복잡한 문장 구조는 없는가? 있다면 구체적으로 지적하고, 쉬운 대안을 제시해야 합니다.
|
| 672 |
+
* 주장이나 설명에 대한 근거가 명확한가?
|
| 673 |
+
* **피드백 예시:** "기사 전반적으로 논리적 비약이 보입니다. 특히 OOO 부분에서 XXX로 넘어가는 연결이 부자연스럽습니다. 이 부분을 YYY와 같이 수정하여 독자의 이해를 도와야 합니다." 또는 "'ABC'라는 단어는 초등학생에게 어렵습니다. '가나다' 또는 '라마바'와 같이 쉬운 표현으로 변경하세요."
|
| 674 |
+
|
| 675 |
+
3. **기사 분위기의 적절성, 객관성 및 잠재적 영향 (30점 만점):**
|
| 676 |
+
* 선택한 기사 분위기가 사건의 본질과 기사 내용에 부합하는가? 감정에 치우치지 않고 객관성을 유지했는가?
|
| 677 |
+
* 기사가 독자에게 미칠 영향을 고려했는가? (특히 비판적 기사의 경우, 과도한 표현으로 인한 기자 안전 문제나 여론 왜곡 가능성을 지적할 수 있어야 합니다.)
|
| 678 |
+
* **피드백 예시:** "선택한 '{article_tone_text}' 분위기는 사건의 심각성을 전달하려는 의도는 알겠으나, 일부 표현(예: 'XXX')이 과도하여 객관성을 해칠 수 있습니다. YYY와 같이 좀 더 절제된 표현을 사용하는 것이 좋겠습니다." 또는 "이 기사는 독자들에게 ZZZ와 같은 영향을 줄 수 있습니다. 이 점을 인지하고, 균형 잡힌 시각을 제공하기 위해 노력해야 합니다."
|
| 679 |
+
|
| 680 |
+
[총평 및 최종 조언]
|
| 681 |
+
각 항목별 점수와 피드백을 바탕으로 총점(100점 만점)을 계산하고, "overall_feedback"에는 기사의 전반적인 문제점과 가장 시급히 개선해야 할 부분을 명확히 요약하여 전달합니다.
|
| 682 |
+
**칭찬은 정말 객관적으로 뛰어난 성과가 있을 때만 간략하게 언급하고, 대부분은 기자가 성장하기 위해 필요한 냉철한 조언을 담아야 합니다.**
|
| 683 |
+
예시: "이번 기사는 OOO점에서 가능성을 보였으나, 전반적으로 XXX와 YYY 문제가 두드러집니다. 특히 ZZZ 부분을 개선하는 데 집중해야 다음 기사에서 발전된 모습을 보일 수 있을 것입니다."
|
| 684 |
|
| 685 |
반드시 아래 JSON 형식으로 응답해주세요:
|
| 686 |
{{
|
| 687 |
"score_fact": 점수(숫자),
|
| 688 |
+
"comment_fact": "내용의 정확성, 중요도, 깊이에 대한 구체적이고 비판적인 피드백 (문자열)",
|
| 689 |
"score_logic": 점수(숫자),
|
| 690 |
+
"comment_logic": "논리적 흐름, 명확성, 어휘 수준에 대한 구체적이고 비판적인 피드백 (문자열)",
|
| 691 |
"score_tone": 점수(숫자),
|
| 692 |
+
"comment_tone": "기사 분위기의 적절성, 객관성, 영향에 대한 구체적이고 비판적인 피드백 (문자열)",
|
| 693 |
"total_score": 총점(숫자),
|
| 694 |
+
"overall_feedback": "기사의 전반적인 문제점과 개선 방향에 대한 냉철한 조언 (문자열, 칭찬은 최소화)"
|
| 695 |
}}
|
| 696 |
"""
|
| 697 |
try:
|
| 698 |
+
with st.spinner("AI 편집장님이 기사를 꼼꼼히 검토하고 있습니다..."): # 스피너 메시지 변경
|
| 699 |
response = client.chat.completions.create(
|
| 700 |
+
model="gpt-4.1-mini", # 더 정교한 평가를 위해 gpt-4o 권장
|
| 701 |
messages=[{"role": "user", "content": prompt}],
|
| 702 |
response_format={"type": "json_object"},
|
| 703 |
+
temperature=0.1, # 매우 객관적이고 일관된 평가를 위해 낮춤
|
| 704 |
+
max_tokens=900 # 피드백이 상세해질 수 있으므로 넉넉하게
|
| 705 |
)
|
| 706 |
ai_evaluation_str = response.choices[0].message.content
|
| 707 |
ai_evaluation = json.loads(ai_evaluation_str)
|
| 708 |
|
| 709 |
total_score = ai_evaluation.get("total_score", 0)
|
| 710 |
+
overall_feedback = ai_evaluation.get("overall_feedback", "AI 편집장 연결 오류로 자동 평가되었습니다. 기사 내용의 객관성과 논리성을 다시 한번 점검해보시기 바랍니다.")
|
| 711 |
|
| 712 |
except Exception as e:
|
| 713 |
st.error(get_text_func("error_openai_api").format(error=str(e)))
|
| 714 |
+
total_score = random.randint(30, 60) # 점수 범위를 낮춤
|
| 715 |
+
overall_feedback = "AI 편집장 시스템 오류로 자동 평가되었습니다. 기사의 핵심 내용 전달과 논리적 흐름을 중점적으로 검토하고 개선할 부분을 찾아보세요."
|
| 716 |
|
| 717 |
+
article['ai_score'] = total_score
|
| 718 |
|
| 719 |
+
trust_change = (total_score - 60) // 6 # 신뢰도 변화 기준을 더 엄격하게
|
| 720 |
status['public_trust'] = max(0, min(100, status['public_trust'] + trust_change))
|
| 721 |
|
| 722 |
+
# 비판적 기사에 대한 패널티는 유지하되, 점수가 낮아도 발생할 수 있도록 조정
|
| 723 |
+
if article['tone'] == "critical":
|
| 724 |
+
freedom_penalty_chance = 0.2 + ( (50 - status['press_freedom']) / 200 ) # 언론자유 낮을수록 확률 증가
|
| 725 |
+
safety_penalty_chance = 0.2 + ( (40 - status['reporter_safety']) / 200 ) # 기자안전 낮을수록 확률 증가
|
| 726 |
+
|
| 727 |
+
if random.random() < freedom_penalty_chance:
|
| 728 |
+
freedom_change = -random.randint(3, 8)
|
| 729 |
+
status['press_freedom'] = max(0, min(100, status['press_freedom'] + freedom_change))
|
| 730 |
+
if freedom_change != 0: game_state['event_log'].append(get_text_func("log_freedom_change_article").format(change=freedom_change))
|
| 731 |
+
|
| 732 |
+
if random.random() < safety_penalty_chance:
|
| 733 |
+
safety_change = -random.randint(5, 12)
|
| 734 |
+
status['reporter_safety'] = max(0, min(100, status['reporter_safety'] + safety_change))
|
| 735 |
+
if safety_change != 0: game_state['event_log'].append(get_text_func("log_safety_change_article").format(change=safety_change))
|
| 736 |
+
|
| 737 |
|
| 738 |
status['article_score_total'] += total_score
|
| 739 |
status['article_count'] += 1
|
|
|
|
| 743 |
game_state['event_log'].append(get_text_func("log_desk_feedback").format(feedback=overall_feedback))
|
| 744 |
if trust_change != 0: game_state['event_log'].append(get_text_func("log_trust_change").format(change=trust_change))
|
| 745 |
|
| 746 |
+
game_state['submitted_articles'].append(article)
|
| 747 |
game_state['reporter_notebook'] = []
|
| 748 |
|
| 749 |
+
return overall_feedback
|
|
|
|
|
|
|
| 750 |
|
| 751 |
# --- UI 표시 함수들 ---
|
| 752 |
def display_reporter_dashboard(game_state):
|