-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterface.py
More file actions
226 lines (180 loc) · 8.1 KB
/
Copy pathinterface.py
File metadata and controls
226 lines (180 loc) · 8.1 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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# ============================================================
# interface.py —— 接口层
# ============================================================
# 这一层只干一件事:规定"数据长什么样"。
#
# 为什么要单独一个文件做这件事?
# 因为以后不管你把 logic.py 换成更复杂的判断逻辑,
# 或者把 execution.py 换成别的模型,
# 只要大家都遵守这里规定的"数据形状",其他文件就不用跟着改。
# 这就是 README 里说的"标准化 JSON Schema",现在先用最简单的
# Python 字典(dict)来实现这个想法,原理是一样的。
# ============================================================
def format_input(ideal_self: str, actual_behavior: str) -> dict:
"""
把用户的两段原始文字,包装成一个"标准输入包"。
参数说明:
ideal_self: 用户描述的"理想自我"
actual_behavior: 用户描述的"实际行为"
返回:
一个字典,形状固定为:
{
"ideal_self": "...",
"actual_behavior": "..."
}
以后无论谁调用这个函数,拿到的数据形状都是一样的,
这就是"标准化"的意思。
"""
return {
"ideal_self": ideal_self,
"actual_behavior": actual_behavior
}
def validate_input(data: dict) -> bool:
"""
检查一份输入数据是不是符合我们规定的形状。
这个函数现在很简单,就是检查两个必须的字段是否存在,
而且不是空的。以后如果输入变复杂了(比如加了"压力场景描述"),
只需要改这一个函数,其他文件都不用动。
返回:
True 表示数据合格,False 表示不合格
"""
required_keys = ["ideal_self", "actual_behavior"]
for key in required_keys:
# 检查这个字段存在,而且不是空字符串
if key not in data or not data[key].strip():
return False
return True
# ------------------------------------------------------------
# 下面两个函数是"压力场景模拟"功能新加的。
#
# 跟普通分析不一样:普通分析问的是"已经发生过的实际行为",
# 压力场景问的是"假设遇到某个处境,你觉得自己会怎么反应"——
# 是一种预演,不是回忆。这是不同的数据形状,
# 所以单独定义一套"标准输入包",而不是硬套用 format_input。
# ------------------------------------------------------------
def format_stress_input(ideal_self: str, scenario: str, imagined_reaction: str) -> dict:
"""
把压力场景模拟的三段原始文字,包装成标准输入包。
参数说明:
ideal_self: 用户描述的"理想自我"
scenario: 这次模拟的压力场景描述
imagined_reaction: 用户设想自己在这个场景里会怎么反应
返回:
一个字典,形状固定为:
{
"ideal_self": "...",
"scenario": "...",
"imagined_reaction": "..."
}
"""
return {
"ideal_self": ideal_self,
"scenario": scenario,
"imagined_reaction": imagined_reaction
}
def validate_stress_input(data: dict) -> bool:
"""
检查压力场景的输入数据是不是三个字段都填了、且不是空的。
"""
required_keys = ["ideal_self", "scenario", "imagined_reaction"]
for key in required_keys:
if key not in data or not data[key].strip():
return False
return True
def format_output(raw_text: str) -> dict:
"""
把模型返回的原始文字,包装成一个"标准输出包"。
现在的形状很简单,只有一个字段。以后如果想加"雷达图数据"
"裂隙点列表(结构化的,不只是一段文字)",就在这里扩展,
main.py 和 execution.py 都不需要跟着改。
"""
return {
"report_text": raw_text
}
# ------------------------------------------------------------
# 下面这个函数是雷达图功能新加的。
#
# 现在 logic.py 让模型返回的不再是一整段普通文字,而是一段
# "看起来像文字,其实是 JSON 格式"的内容——JSON 是一种很常见的
# 数据格式,长得像 Python 的字典(dict),可以用代码直接读取。
#
# 这个函数负责把模型返回的这段 JSON 文字,"解析"成 Python
# 能直接使用的字典。这也是接口层该干的事:模型的输出"长什么样"、
# 怎么被读出来,都归这一层管。
# ------------------------------------------------------------
import json # Python 自带的工具包,专门用来处理 JSON 格式
import re # Python 自带的工具包,专门用来"按规律找文字"(正则表达式)
def parse_model_output(raw_text: str) -> dict:
"""
把模型返回的内容,解析成一个标准字典:
{
"report_text": "...",
"scores": {"语言表达": 72, "行为反应": 65, ...}
}
现在模型返回的格式是:
<report> 一段自然语言文字 </report>
<scores> {"语言表达": 72, ...} </scores>
之所以不把"报告正文"也塞进一个大 JSON 里,是因为报告正文
是模型自由发挥写的自然语言,经常会带上英文引号 " " 之类的符号,
这些符号一旦出现在 JSON 字符串内部又没有被正确转义,
会直接把整个 JSON 撑破、导致解析失败。
分数部分只有数字,天然更"干净",所以单独用 JSON 解析,
报告正文只是"原样取出这段文字",不需要 JSON 解析,也就不会
被引号符号影响。
"""
# 第一步:用正则表达式,把 <report>...</report> 之间的内容抠出来
# re.DOTALL 的作用是让 "." 也能匹配换行符,不然多段落的报告会被截断
report_match = re.search(r"<report>(.*?)</report>", raw_text, re.DOTALL)
scores_match = re.search(r"<scores>(.*?)</scores>", raw_text, re.DOTALL)
if not report_match or not scores_match:
print("警告:没有找到 <report> 或 <scores> 标签,原始内容如下:")
print(raw_text)
return {
"report_text": raw_text,
"scores": None
}
report_text = report_match.group(1).strip()
scores_raw = scores_match.group(1).strip()
# 第二步:只对分数这一小段文字做 JSON 解析,出错概率低很多
try:
scores = json.loads(scores_raw)
except json.JSONDecodeError:
print("警告:分数部分不是标准 JSON,原始内容如下:")
print(scores_raw)
scores = None
return {
"report_text": report_text,
"scores": scores
}
# ------------------------------------------------------------
# 下面是"自由书写 → 反思性提问"这个新机制专用的函数。
#
# 输入形状跟之前都不一样——不再是"理想自我 vs 实际行为"
# 这种命题式的两段话,而是一段更自然的自由书写内容。
# ------------------------------------------------------------
def format_freeform_input(text: str) -> dict:
"""
把用户自由书写的一段话,包装成标准输入包。
参数:
text: 用户随手写下的、关于自己近况的内容
返回:
{"text": "..."}
"""
return {"text": text}
def validate_freeform_input(data: dict) -> bool:
"""检查自由书写的内容是否存在、且不是空的。"""
return "text" in data and bool(data["text"].strip())
def parse_reflection_output(raw_text: str) -> dict:
"""
从模型返回的内容里,提取出 <reflection> 标签包裹的部分。
这个机制的输出比之前简单——只有一段反思式的话,
没有分数,所以不需要 JSON 解析,只需要把标签内容取出来。
返回:
{"reflection_text": "..."}
"""
match = re.search(r"<reflection>(.*?)</reflection>", raw_text, re.DOTALL)
if not match:
print("警告:没有找到 <reflection> 标签,原始内容如下:")
print(raw_text)
return {"reflection_text": raw_text}
return {"reflection_text": match.group(1).strip()}