-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscoring.py
More file actions
199 lines (162 loc) · 5.88 KB
/
Copy pathscoring.py
File metadata and controls
199 lines (162 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from __future__ import annotations
import os
from models.llm.LLMVendor import invoke_structured_output
from dotenv import load_dotenv
import logging
from schemas import (
InterviewAnalysisResult,
QuestionScore,
DimensionScore,
)
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
load_dotenv()
LLM = os.environ.get("LLM", "gemini-2.5-flash")
# =========================================================
# Public API
# =========================================================
def score_interview_answers(
conversation: str,
questions: str
):
"""
Public API: Answer Quality Scoring
Required:
- conversation
- questions
- llm(prompt) -> str
Returns:
- InterviewAnalysisResult
"""
prompt = build_scoring_prompt(
conversation=conversation,
questions=questions
)
# 使用 LangChain 的強型別結構化輸出
try:
question_score = invoke_structured_output(prompt, QuestionScore)
print(f'LLM Structured Response: {question_score.model_dump_json()[:2000]}')
return question_score
except Exception as e:
logger.exception('Structured output failed, falling back to generic JSON parse')
# =========================================================
# Prompt (Schema-locked)
# =========================================================
def build_scoring_prompt(
conversation: str,
questions: str
) -> str:
"""
Build a schema-locked prompt to force LLM output that matches parse_llm_response().
"""
dim_list = ["relevance", "specificity", "reasoning", "operability", "risk_integrity"]
return f"""
你是一個企業招募用的「面試回答品質評分 AI」。你只負責輸出 Answer Quality 評分與證據引用。
硬規則(必須遵守):
1) 只能根據【完整對話紀錄】評分;不得引用履歷、JD、臆測或外部常識補完。
2) 每一個維度分數(0~4)都必須提供:
- evidence:1~3 句對話原文片段(可節錄,但不得捏造)
若缺少證據,該維度最高只能給 2 分。
3) 不得因為回答很長就加分;只看資訊密度與可驗證性。
4) 你必須針對題目產生一筆 question_scores(即使候選人完全沒答到,也要產出該題評分)。
5) 僅輸出 JSON,嚴禁輸出 markdown、```、或任何額外文字。
6) JSON 的 key 名稱、層級結構必須完全符合下方 Schema;不得自行改名或改成中文 key。
=====================
[面試題目]
=====================
{questions}
=====================
[完整對話紀錄(唯一可用事實來源)]
=====================
{conversation}
=====================
輸出 JSON Schema(必須完全相符,key 不可改名):
{{
"question_id": "Q1",
"max_points": 100,
"raw_dim_total": 0,
"raw_pct": 0.0,
"caps_applied": [],
"final_pct": 0.0,
"level": "",
"one_line_summary": "",
"per_dim": [
{{
"dim_name": "{dim_list[0]}",
"score_0_to_4": 0,
"rationale": "",
"evidence": ["..."],
"evidence_utterance_ids": ["utt1"],
"gaps": []
}},
{{
"dim_name": "{dim_list[1]}",
"score_0_to_4": 0,
"rationale": "",
"evidence": ["..."],
"evidence_utterance_ids": ["utt1"],
"gaps": []
}},
{{
"dim_name": "{dim_list[2]}",
"score_0_to_4": 0,
"rationale": "",
"evidence": ["..."],
"evidence_utterance_ids": ["utt1"],
"gaps": []
}},
{{
"dim_name": "{dim_list[3]}",
"score_0_to_4": 0,
"rationale": "",
"evidence": ["..."],
"evidence_utterance_ids": ["utt1"],
"gaps": []
}},
{{
"dim_name": "{dim_list[4]}",
"score_0_to_4": 0,
"rationale": "",
"evidence": ["..."],
"evidence_utterance_ids": ["utt1"],
"gaps": []
}}
]
}}
填寫規則(務必遵守):
- per_dim 必須包含且只包含 {dim_list} 五個維度,各 1 筆。
- raw_dim_total = 五個維度分數總和(0~20)。
- raw_pct = raw_dim_total / 20 * 100。
- caps_applied 先填空陣列 [](不要自行加 cap 規則;後續由程式 normalize)。
- final_pct 先等於 raw_pct(後續由程式 normalize 覆寫)。
- level 可留空字串 ""(後續由程式 normalize 覆寫)。
- one_line_summary:用 1 句話描述此題回答品質,需指出亮點或缺口,禁止空泛字眼(例如「還不錯」「普通」)。
- overall_pct、overall_comment 可先填 0.0 / ""(後續由程式 normalize 重新計算亦可)。
現在開始輸出 JSON(不得輸出其他任何文字)。
""".strip()
def parse_llm_response(qs: dict) -> QuestionScore:
# try to construct Pydantic QuestionScore from dict
try:
return QuestionScore.model_validate(qs)
except Exception:
logger.error('Failed to parse LLM response into QuestionScore')
raise
if __name__ == "__main__":
import dotenv
dotenv.load_dotenv()
question="""
請問在處理系統高併發效能瓶頸時,你通常如何識別問題點?
"""
conversation_history = """
請問在處理系統高併發效能瓶頸時,你通常如何識別問題點?"
我會先看 CPU 使用率,如果太高就加機器。也會看 Log 紀錄。
"""
response = score_interview_answers(
conversation=conversation_history,
questions=question)
print(f'Scoring Result: {response.final_pct} / {response.max_points}')
print(f'summsary: {response.one_line_summary}')
# loop response and write log
for dim in response.per_dim:
print(f' - {dim.dim_name}: {dim.score_0_to_4} 分')
print(f' 證據: {dim.evidence}')