anna-agent 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anna_agent-0.2.1 → anna_agent-0.2.2}/PKG-INFO +1 -1
- {anna_agent-0.2.1 → anna_agent-0.2.2}/pyproject.toml +1 -1
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/__init__.py +1 -1
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/anna_agent_template.py +8 -5
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/cli.py +78 -15
- anna_agent-0.2.2/src/anna_agent/complaint_elicitor.py +71 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/init_content.py +3 -9
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/load_config.py +36 -19
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/diagnostics.py +4 -2
- anna_agent-0.2.2/src/anna_agent/fill_scales.py +175 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/querier.py +8 -52
- anna_agent-0.2.2/src/anna_agent/short_term_memory.py +185 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/PKG-INFO +1 -1
- anna_agent-0.2.1/src/anna_agent/complaint_elicitor.py +0 -88
- anna_agent-0.2.1/src/anna_agent/fill_scales.py +0 -385
- anna_agent-0.2.1/src/anna_agent/short_term_memory.py +0 -286
- {anna_agent-0.2.1 → anna_agent-0.2.2}/LICENSE +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/README.md +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/setup.cfg +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/__main__.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/assets.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/backbone.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/case_data.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/common/registry.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/common/tool_calls.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/complaint_chain.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/__init__.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/defaults.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/environment_reader.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/initialize.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/models/anna_engine_config.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/counselor.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/datasets/cbt-triggering-events.csv +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/emotion_modulator.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/emotion_pertuber.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/event_trigger.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/figure/readme.md +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/figure/whiteboard_exported_image_en.png +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/initialize.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/__init__.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/chunking.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/embeddings.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/models.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/store.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/model_services.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/ms_patient.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/ms_patient_with_prompt.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/runtime.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/__init__.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/bdi.json +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/ghq-28.json +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/sass.json +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/complaint.sh +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/counselor.sh +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/emotion.sh +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/service.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/style_analyzer.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/workspace.py +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/SOURCES.txt +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/dependency_links.txt +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/entry_points.txt +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/requires.txt +0 -0
- {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anna-agent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Dynamic Evolution Agent System with Multi-Session Memory for Realistic Seeker Simulation
|
|
5
5
|
Project-URL: Documentation, https://sci-m-wang.github.io/AnnaAgent/
|
|
6
6
|
Project-URL: Repository, https://github.com/sci-m-wang/AnnaAgent
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
## 主诉请求和情绪通过reminder来控制
|
|
2
2
|
|
|
3
3
|
prompt_template = """
|
|
4
|
-
|
|
5
|
-
结合历史内容的内容用一致性的语气回复。配合我进行演出,
|
|
6
|
-
请不要回答你是语言模型,永远记住你正在扮演 心理咨询患者
|
|
7
|
-
注意保持你的性格特点包括 {situation}
|
|
4
|
+
# Role: 心理咨询患者
|
|
8
5
|
|
|
9
6
|
## Profile
|
|
10
7
|
- 性别: {gender}
|
|
11
8
|
- 年龄: {age}
|
|
12
9
|
- 职业: {occupation}
|
|
13
10
|
- 婚姻状况: {marriage}
|
|
14
|
-
|
|
11
|
+
|
|
12
|
+
## Situation
|
|
13
|
+
- 你是一个有心理障碍的患者,正在与心理咨询师进行对话。
|
|
14
|
+
{situation}
|
|
15
|
+
|
|
15
16
|
## Status
|
|
16
17
|
{status}
|
|
17
18
|
|
|
@@ -24,4 +25,6 @@ prompt_template = """
|
|
|
24
25
|
## Constraints
|
|
25
26
|
- 使用中文回复
|
|
26
27
|
- 一次不能提及过多的症状信息,每轮最多讨论一个症状。
|
|
28
|
+
- 你应该用含糊和口语化的方式表达你的症状,并将其与你的生活经历联系起来。
|
|
29
|
+
- 不要使用专业术语。
|
|
27
30
|
"""
|
|
@@ -196,17 +196,77 @@ def _print_deploy_env_status(status: dict[str, Any]) -> None:
|
|
|
196
196
|
console.print(table)
|
|
197
197
|
|
|
198
198
|
|
|
199
|
-
def
|
|
199
|
+
def _iter_exception_chain(err: BaseException):
|
|
200
200
|
current: BaseException | None = err
|
|
201
201
|
while current is not None:
|
|
202
|
-
|
|
203
|
-
return True
|
|
202
|
+
yield current
|
|
204
203
|
current = current.__cause__ or current.__context__
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _looks_like_connection_error(err: BaseException) -> bool:
|
|
207
|
+
for current in _iter_exception_chain(err):
|
|
208
|
+
if current.__class__.__name__ in {
|
|
209
|
+
"APIConnectionError",
|
|
210
|
+
"APITimeoutError",
|
|
211
|
+
"ConnectError",
|
|
212
|
+
"ConnectTimeout",
|
|
213
|
+
"ReadTimeout",
|
|
214
|
+
"TimeoutException",
|
|
215
|
+
}:
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _looks_like_auth_error(err: BaseException) -> bool:
|
|
221
|
+
for current in _iter_exception_chain(err):
|
|
222
|
+
class_name = current.__class__.__name__
|
|
223
|
+
status_code = getattr(current, "status_code", None)
|
|
224
|
+
text = str(current).lower()
|
|
225
|
+
if class_name in {"AuthenticationError", "PermissionDeniedError"}:
|
|
226
|
+
return True
|
|
227
|
+
if status_code in {401, 403}:
|
|
228
|
+
return True
|
|
229
|
+
if "invalid api key" in text or "invalid_key" in text:
|
|
230
|
+
return True
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _looks_like_model_service_error(err: BaseException) -> bool:
|
|
235
|
+
if _looks_like_connection_error(err) or _looks_like_auth_error(err):
|
|
236
|
+
return True
|
|
237
|
+
for current in _iter_exception_chain(err):
|
|
238
|
+
if current.__class__.__name__ in {"APIStatusError", "RateLimitError"}:
|
|
239
|
+
return True
|
|
205
240
|
return False
|
|
206
241
|
|
|
207
242
|
|
|
243
|
+
def _configured_secret_values() -> list[str]:
|
|
244
|
+
try:
|
|
245
|
+
cfg = registry.get("anna_engine_config")
|
|
246
|
+
except Exception:
|
|
247
|
+
return []
|
|
248
|
+
return [
|
|
249
|
+
cfg.api_key,
|
|
250
|
+
cfg.complaint_api_key,
|
|
251
|
+
cfg.counselor_api_key,
|
|
252
|
+
cfg.emotion_api_key,
|
|
253
|
+
cfg.embedding_api_key,
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
208
257
|
def _print_model_connection_help(workspace: Path, err: BaseException) -> None:
|
|
209
|
-
|
|
258
|
+
detail = escape(_redact_text(str(err), _configured_secret_values()))
|
|
259
|
+
if _looks_like_auth_error(err):
|
|
260
|
+
console.print(f"[red]Model service authentication failed:[/red] {detail}")
|
|
261
|
+
console.print(
|
|
262
|
+
"Check [bold]ANNA_ENGINE_API_KEY[/bold] or [bold]MIMO_API_KEY[/bold] "
|
|
263
|
+
"in the workspace .env and ensure the key matches the configured "
|
|
264
|
+
"base_url/model."
|
|
265
|
+
)
|
|
266
|
+
elif _looks_like_connection_error(err):
|
|
267
|
+
console.print(f"[red]Model service connection failed:[/red] {detail}")
|
|
268
|
+
else:
|
|
269
|
+
console.print(f"[red]Model service request failed:[/red] {detail}")
|
|
210
270
|
console.print(
|
|
211
271
|
"Run [bold]anna doctor --workspace {workspace}[/bold] and "
|
|
212
272
|
"[bold]anna models status --workspace {workspace}[/bold]. If you use "
|
|
@@ -538,10 +598,7 @@ def config_wizard(
|
|
|
538
598
|
console.print(
|
|
539
599
|
Panel(
|
|
540
600
|
"[bold]AnnaAgent backbone model[/bold]\n"
|
|
541
|
-
"This model drives AnnaAgent's internal seeker-simulation modules.
|
|
542
|
-
"It can temporarily stand in when no external counselor is wired, "
|
|
543
|
-
"but formal runs should use a dedicated counselor process or model "
|
|
544
|
-
"for counselor turns.",
|
|
601
|
+
"This model drives AnnaAgent's internal seeker-simulation modules.",
|
|
545
602
|
border_style="cyan",
|
|
546
603
|
)
|
|
547
604
|
)
|
|
@@ -626,10 +683,16 @@ def test_model(
|
|
|
626
683
|
_configure(workspace)
|
|
627
684
|
cfg = registry.get("anna_engine_config")
|
|
628
685
|
client = backbone.get_openai_client()
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
686
|
+
try:
|
|
687
|
+
response = client.chat.completions.create(
|
|
688
|
+
model=cfg.model_name,
|
|
689
|
+
messages=[{"role": "user", "content": "Reply with OK."}],
|
|
690
|
+
)
|
|
691
|
+
except Exception as err:
|
|
692
|
+
if _looks_like_model_service_error(err):
|
|
693
|
+
_print_model_connection_help(workspace, err)
|
|
694
|
+
raise typer.Exit(code=1) from None
|
|
695
|
+
raise
|
|
633
696
|
console.print(
|
|
634
697
|
f"[green]Model OK[/green] response={response.choices[0].message.content}"
|
|
635
698
|
)
|
|
@@ -838,7 +901,7 @@ def initialize_full(
|
|
|
838
901
|
try:
|
|
839
902
|
state = build_full_state(case_file, progress_callback=progress)
|
|
840
903
|
except Exception as err:
|
|
841
|
-
if
|
|
904
|
+
if _looks_like_model_service_error(err):
|
|
842
905
|
_print_model_connection_help(workspace, err)
|
|
843
906
|
raise typer.Exit(code=1) from None
|
|
844
907
|
raise
|
|
@@ -1187,7 +1250,7 @@ def chat(
|
|
|
1187
1250
|
progress_callback=progress,
|
|
1188
1251
|
)
|
|
1189
1252
|
except Exception as err:
|
|
1190
|
-
if
|
|
1253
|
+
if _looks_like_model_service_error(err):
|
|
1191
1254
|
_print_model_connection_help(workspace, err)
|
|
1192
1255
|
raise typer.Exit(code=1) from None
|
|
1193
1256
|
raise
|
|
@@ -1206,7 +1269,7 @@ def chat(
|
|
|
1206
1269
|
compact_status = None
|
|
1207
1270
|
except Exception as err:
|
|
1208
1271
|
compact_status = None
|
|
1209
|
-
if
|
|
1272
|
+
if _looks_like_model_service_error(err):
|
|
1210
1273
|
_print_model_connection_help(workspace, err)
|
|
1211
1274
|
raise typer.Exit(code=1) from None
|
|
1212
1275
|
raise
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .backbone import get_openai_client
|
|
4
|
+
from .common.registry import registry
|
|
5
|
+
from .common.tool_calls import extract_tool_call_arguments
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
tools = [
|
|
10
|
+
{
|
|
11
|
+
"type": "function",
|
|
12
|
+
"function": {
|
|
13
|
+
"name": "is_recognized",
|
|
14
|
+
"description": (
|
|
15
|
+
"根据对话内容和主诉认知变化链,判断患者目前是否很好地认知到了"
|
|
16
|
+
"当前阶段的主诉问题。"
|
|
17
|
+
),
|
|
18
|
+
"parameters": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"is_recognized": {"type": "boolean"},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
"required": ["is_recognized"],
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def transform_chain(chain):
|
|
31
|
+
transformed_chain = {}
|
|
32
|
+
for node in chain:
|
|
33
|
+
transformed_chain[node["stage"]] = node["content"]
|
|
34
|
+
pass
|
|
35
|
+
return transformed_chain
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def switch_complaint(chain, index, conversation):
|
|
39
|
+
client = get_openai_client()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
transformed_chain = transform_chain(chain)
|
|
43
|
+
logger.debug("transformed chain: %s", transformed_chain)
|
|
44
|
+
|
|
45
|
+
# 提取对话记录
|
|
46
|
+
dialogue_history = "\n".join(
|
|
47
|
+
[f"{conv['role']}: {conv['content']}" for conv in conversation]
|
|
48
|
+
)
|
|
49
|
+
response = client.chat.completions.create(
|
|
50
|
+
model=registry.get("anna_engine_config").model_name,
|
|
51
|
+
messages=[
|
|
52
|
+
{
|
|
53
|
+
"role": "user",
|
|
54
|
+
"content": (
|
|
55
|
+
"### 任务\n"
|
|
56
|
+
"根据患者情况及咨访对话历史记录,判断患者当前阶段的主诉问题是否已经得到解决。"
|
|
57
|
+
f"\n### 咨访对话历史记录\n{dialogue_history}"
|
|
58
|
+
f"\n### 主诉认知变化链\n{transformed_chain}"
|
|
59
|
+
f"\n### 当前阶段\n{transformed_chain[index]}"
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
tools=tools,
|
|
64
|
+
tool_choice={"type": "function", "function": {"name": "is_recognized"}},
|
|
65
|
+
)
|
|
66
|
+
args = extract_tool_call_arguments(response)
|
|
67
|
+
if args and args.get("is_recognized"):
|
|
68
|
+
return index + 1
|
|
69
|
+
except Exception as err:
|
|
70
|
+
logger.debug("switch_complaint error: %s", err)
|
|
71
|
+
return index
|
|
@@ -15,10 +15,6 @@ servers:
|
|
|
15
15
|
model_name: {anna_engine_defaults.complaint_model_name}
|
|
16
16
|
api_key: {anna_engine_defaults.complaint_api_key}
|
|
17
17
|
base_url: {anna_engine_defaults.complaint_base_url}
|
|
18
|
-
counselor:
|
|
19
|
-
model_name: {anna_engine_defaults.counselor_model_name}
|
|
20
|
-
api_key: {anna_engine_defaults.counselor_api_key}
|
|
21
|
-
base_url: {anna_engine_defaults.counselor_base_url}
|
|
22
18
|
emotion:
|
|
23
19
|
use_sft_model: {str(anna_engine_defaults.emotion_use_sft_model).lower()}
|
|
24
20
|
model_name: {anna_engine_defaults.emotion_model_name}
|
|
@@ -108,9 +104,7 @@ INIT_DOTENV = """\
|
|
|
108
104
|
# Keep real secrets in this file and do not commit it.
|
|
109
105
|
|
|
110
106
|
# AnnaAgent backbone OpenAI-compatible model service.
|
|
111
|
-
# This drives internal seeker-simulation modules.
|
|
112
|
-
# when no external counselor/counselor model is wired, but formal runs should
|
|
113
|
-
# use a dedicated counselor process or model for counselor turns.
|
|
107
|
+
# This drives internal seeker-simulation modules.
|
|
114
108
|
# ANNA_ENGINE_API_KEY=your-backbone-model-api-key
|
|
115
109
|
# ANNA_ENGINE_BASE_URL=https://your-backbone-endpoint/v1
|
|
116
110
|
# ANNA_ENGINE_MODEL_NAME=your-backbone-model
|
|
@@ -137,8 +131,8 @@ INIT_DOTENV = """\
|
|
|
137
131
|
# ANNA_ENGINE_COMPLAINT_MODEL_NAME=your-complaint-sft-model
|
|
138
132
|
# ANNA_ENGINE_COMPLAINT_USE_SFT_MODEL=true
|
|
139
133
|
|
|
140
|
-
# Optional
|
|
141
|
-
#
|
|
134
|
+
# Optional override for legacy internal-module routing. Leave unset to use the
|
|
135
|
+
# AnnaAgent backbone model_service above.
|
|
142
136
|
# ANNA_ENGINE_COUNSELOR_API_KEY=your-backbone-fallback-key
|
|
143
137
|
# ANNA_ENGINE_COUNSELOR_BASE_URL=https://your-backbone-fallback-endpoint/v1
|
|
144
138
|
# ANNA_ENGINE_COUNSELOR_MODEL_NAME=your-backbone-fallback-model
|
|
@@ -12,6 +12,18 @@ from .models.anna_engine_config import AnnaEngineConfig
|
|
|
12
12
|
|
|
13
13
|
_default_config_files = ["settings.yaml", "settings.yml", "settings.json"]
|
|
14
14
|
|
|
15
|
+
_default_aliases = {
|
|
16
|
+
"model_name": {anna_engine_defaults.model_name, "counselor"},
|
|
17
|
+
"api_key": {anna_engine_defaults.api_key, "counselor"},
|
|
18
|
+
"base_url": {anna_engine_defaults.base_url},
|
|
19
|
+
"counselor_model_name": {
|
|
20
|
+
anna_engine_defaults.counselor_model_name,
|
|
21
|
+
"counselor",
|
|
22
|
+
},
|
|
23
|
+
"counselor_api_key": {anna_engine_defaults.counselor_api_key, "counselor"},
|
|
24
|
+
"counselor_base_url": {anna_engine_defaults.counselor_base_url},
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
|
|
16
28
|
def _search_for_config_in_root_dir(root: str | Path) -> Path | None:
|
|
17
29
|
root = Path(root)
|
|
@@ -146,6 +158,10 @@ def _first_env(*keys: str) -> str | None:
|
|
|
146
158
|
return None
|
|
147
159
|
|
|
148
160
|
|
|
161
|
+
def _is_default_alias(field: str, value: Any) -> bool:
|
|
162
|
+
return value is None or value in _default_aliases.get(field, set())
|
|
163
|
+
|
|
164
|
+
|
|
149
165
|
def _apply_embedding_env_aliases(values: dict[str, Any]) -> None:
|
|
150
166
|
api_key = _first_env("ANNA_ENGINE_EMBEDDING_API_KEY")
|
|
151
167
|
base_url = _first_env("ANNA_ENGINE_EMBEDDING_BASE_URL")
|
|
@@ -194,14 +210,11 @@ def _apply_model_env_aliases(values: dict[str, Any]) -> None:
|
|
|
194
210
|
api_key = _first_env("ANNA_ENGINE_API_KEY", "MIMO_API_KEY")
|
|
195
211
|
base_url = _first_env("ANNA_ENGINE_BASE_URL", "MIMO_BASE_URL")
|
|
196
212
|
model_name = _first_env("ANNA_ENGINE_MODEL_NAME", "MIMO_MODEL")
|
|
197
|
-
if api_key and values.get("api_key")
|
|
213
|
+
if api_key and _is_default_alias("api_key", values.get("api_key")):
|
|
198
214
|
values["api_key"] = api_key
|
|
199
|
-
if base_url and values.get("base_url")
|
|
215
|
+
if base_url and _is_default_alias("base_url", values.get("base_url")):
|
|
200
216
|
values["base_url"] = base_url
|
|
201
|
-
if model_name and values.get("model_name")
|
|
202
|
-
None,
|
|
203
|
-
anna_engine_defaults.model_name,
|
|
204
|
-
}:
|
|
217
|
+
if model_name and _is_default_alias("model_name", values.get("model_name")):
|
|
205
218
|
values["model_name"] = model_name
|
|
206
219
|
|
|
207
220
|
env_mappings = {
|
|
@@ -212,27 +225,31 @@ def _apply_model_env_aliases(values: dict[str, Any]) -> None:
|
|
|
212
225
|
"emotion_api_key": "ANNA_ENGINE_EMOTION_API_KEY",
|
|
213
226
|
}
|
|
214
227
|
defaults = {
|
|
215
|
-
"complaint_api_key": anna_engine_defaults.complaint_api_key,
|
|
216
|
-
"counselor_api_key":
|
|
217
|
-
"counselor_base_url":
|
|
218
|
-
"counselor_model_name":
|
|
219
|
-
"emotion_api_key": anna_engine_defaults.emotion_api_key,
|
|
228
|
+
"complaint_api_key": {None, anna_engine_defaults.complaint_api_key},
|
|
229
|
+
"counselor_api_key": None,
|
|
230
|
+
"counselor_base_url": None,
|
|
231
|
+
"counselor_model_name": None,
|
|
232
|
+
"emotion_api_key": {None, anna_engine_defaults.emotion_api_key},
|
|
220
233
|
}
|
|
221
234
|
for field, env_key in env_mappings.items():
|
|
222
235
|
env_value = _first_env(env_key)
|
|
223
|
-
if env_value
|
|
236
|
+
if not env_value:
|
|
237
|
+
continue
|
|
238
|
+
if field.startswith("counselor_"):
|
|
239
|
+
if _is_default_alias(field, values.get(field)):
|
|
240
|
+
values[field] = env_value
|
|
241
|
+
elif values.get(field) in defaults[field]:
|
|
224
242
|
values[field] = env_value
|
|
225
243
|
|
|
226
244
|
|
|
227
245
|
def _inherit_default_counselor_from_base(values: dict[str, Any]) -> None:
|
|
228
|
-
counselor_defaults = {
|
|
229
|
-
"counselor_api_key": anna_engine_defaults.counselor_api_key,
|
|
230
|
-
"counselor_base_url": anna_engine_defaults.counselor_base_url,
|
|
231
|
-
"counselor_model_name": anna_engine_defaults.counselor_model_name,
|
|
232
|
-
}
|
|
233
246
|
if not all(
|
|
234
|
-
values.get(field)
|
|
235
|
-
for field
|
|
247
|
+
_is_default_alias(field, values.get(field))
|
|
248
|
+
for field in [
|
|
249
|
+
"counselor_api_key",
|
|
250
|
+
"counselor_base_url",
|
|
251
|
+
"counselor_model_name",
|
|
252
|
+
]
|
|
236
253
|
):
|
|
237
254
|
return
|
|
238
255
|
values["counselor_api_key"] = values.get("api_key") or anna_engine_defaults.api_key
|
|
@@ -47,9 +47,11 @@ def run_doctor(workspace: Path) -> list[DiagnosticCheck]:
|
|
|
47
47
|
if cfg:
|
|
48
48
|
checks.append(
|
|
49
49
|
DiagnosticCheck(
|
|
50
|
-
"model service",
|
|
50
|
+
"model service config",
|
|
51
51
|
"ok" if cfg.api_key and cfg.base_url else "warn",
|
|
52
|
-
f"model={cfg.model_name},
|
|
52
|
+
f"model={cfg.model_name}, "
|
|
53
|
+
f"base_url configured={bool(cfg.base_url)}; "
|
|
54
|
+
"run `anna test model` for live connectivity",
|
|
53
55
|
)
|
|
54
56
|
)
|
|
55
57
|
checks.append(
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .backbone import get_counselor_client
|
|
4
|
+
from .common.registry import registry
|
|
5
|
+
from .common.tool_calls import extract_tool_call_arguments
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _extract_answers(response, expected_length, default="B"):
|
|
11
|
+
args = extract_tool_call_arguments(response)
|
|
12
|
+
answers = args.get("answers") if args else None
|
|
13
|
+
if isinstance(answers, list) and len(answers) == expected_length:
|
|
14
|
+
return answers
|
|
15
|
+
return [default] * expected_length
|
|
16
|
+
|
|
17
|
+
tools = [
|
|
18
|
+
{
|
|
19
|
+
"type": "function",
|
|
20
|
+
"function": {
|
|
21
|
+
"name": "fill_bdi",
|
|
22
|
+
"description": "填写BDI量表",
|
|
23
|
+
"parameters": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"answers": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {"type": "string", "enum": ["A", "B", "C", "D"]},
|
|
29
|
+
"minItems": 21,
|
|
30
|
+
"maxItems": 21,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"required": ["answers"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "function",
|
|
39
|
+
"function": {
|
|
40
|
+
"name": "fill_ghq",
|
|
41
|
+
"description": "填写GHQ-28量表",
|
|
42
|
+
"parameters": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"answers": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {"type": "string", "enum": ["A", "B", "C", "D"]},
|
|
48
|
+
"minItems": 28,
|
|
49
|
+
"maxItems": 28,
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"required": ["answers"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "function",
|
|
58
|
+
"function": {
|
|
59
|
+
"name": "fill_sass",
|
|
60
|
+
"description": "填写SASS量表",
|
|
61
|
+
"parameters": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"answers": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {"type": "string", "enum": ["A", "B", "C", "D"]},
|
|
67
|
+
"minItems": 21,
|
|
68
|
+
"maxItems": 21,
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"required": ["answers"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# 根据profile和report填写之前的量表
|
|
79
|
+
def fill_scales_previous(profile, report):
|
|
80
|
+
"""
|
|
81
|
+
结构化信息转换成非结构化文本数据,免去模型对语义解析的理解错误
|
|
82
|
+
"""
|
|
83
|
+
client = get_counselor_client()
|
|
84
|
+
prompt = (
|
|
85
|
+
"### 任务\n根据个人描述和报告,填写量表。"
|
|
86
|
+
f"\n### 个人描述\n{profile}"
|
|
87
|
+
f"\n### 报告\n{report}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# 填写BDI量表
|
|
91
|
+
response = client.chat.completions.create(
|
|
92
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
93
|
+
messages=[{"role": "user", "content": prompt}],
|
|
94
|
+
temperature=0.1,
|
|
95
|
+
tools=tools,
|
|
96
|
+
tool_choice={"type": "function", "function": {"name": "fill_bdi"}},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
logger.debug("BDI response: %s", response)
|
|
100
|
+
bdi = _extract_answers(response, 21)
|
|
101
|
+
|
|
102
|
+
# 填写GHQ-28量表
|
|
103
|
+
response = client.chat.completions.create(
|
|
104
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
105
|
+
messages=[{"role": "user", "content": prompt}],
|
|
106
|
+
temperature=0.1,
|
|
107
|
+
tools=tools,
|
|
108
|
+
tool_choice={"type": "function", "function": {"name": "fill_ghq"}},
|
|
109
|
+
)
|
|
110
|
+
# print(response.choices[0].message.tool_calls[0].function.arguments)
|
|
111
|
+
ghq = _extract_answers(response, 28)
|
|
112
|
+
|
|
113
|
+
# 填写SASS量表
|
|
114
|
+
response = client.chat.completions.create(
|
|
115
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
116
|
+
messages=[{"role": "user", "content": prompt}],
|
|
117
|
+
temperature=0.1,
|
|
118
|
+
tools=tools,
|
|
119
|
+
tool_choice={"type": "function", "function": {"name": "fill_sass"}},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Step 3:提取量表答案
|
|
123
|
+
sass = _extract_answers(response, 21)
|
|
124
|
+
|
|
125
|
+
return bdi, ghq, sass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# 根据prompt填写量表
|
|
129
|
+
def fill_scales(prompt):
|
|
130
|
+
client = get_counselor_client()
|
|
131
|
+
logger.debug("scale prompt: %s", prompt)
|
|
132
|
+
task_prompt = "### 任务\n请根据你的情况填写量表。"
|
|
133
|
+
|
|
134
|
+
# 填写BDI量表
|
|
135
|
+
response = client.chat.completions.create(
|
|
136
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
137
|
+
messages=[
|
|
138
|
+
{"role": "system", "content": prompt},
|
|
139
|
+
{"role": "user", "content": task_prompt},
|
|
140
|
+
],
|
|
141
|
+
tools=tools,
|
|
142
|
+
temperature=0.1,
|
|
143
|
+
tool_choice={"type": "function", "function": {"name": "fill_bdi"}},
|
|
144
|
+
)
|
|
145
|
+
logger.debug("BDI response: %s", response)
|
|
146
|
+
bdi = _extract_answers(response, 21)
|
|
147
|
+
# 填写GHQ-28量表
|
|
148
|
+
response = client.chat.completions.create(
|
|
149
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
150
|
+
messages=[
|
|
151
|
+
{"role": "system", "content": prompt},
|
|
152
|
+
{"role": "user", "content": task_prompt},
|
|
153
|
+
],
|
|
154
|
+
temperature=0.1,
|
|
155
|
+
tools=tools,
|
|
156
|
+
tool_choice={"type": "function", "function": {"name": "fill_ghq"}},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
logger.debug("GHQ response: %s", response)
|
|
160
|
+
ghq = _extract_answers(response, 28)
|
|
161
|
+
# 填写SASS量表
|
|
162
|
+
response = client.chat.completions.create(
|
|
163
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
164
|
+
messages=[
|
|
165
|
+
{"role": "system", "content": prompt},
|
|
166
|
+
{"role": "user", "content": task_prompt},
|
|
167
|
+
],
|
|
168
|
+
temperature=0.1,
|
|
169
|
+
tools=tools,
|
|
170
|
+
tool_choice={"type": "function", "function": {"name": "fill_sass"}},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
logger.debug("SASS response: %s", response)
|
|
174
|
+
sass = _extract_answers(response, 21)
|
|
175
|
+
return bdi, ghq, sass
|
|
@@ -44,12 +44,8 @@ def is_need(utterance):
|
|
|
44
44
|
{
|
|
45
45
|
"role": "user",
|
|
46
46
|
"content": (
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"判断标准包括但不限于:是否提到了过去的咨询记录、曾讨论过的话题、上次谈话的内容,或者使用了‘你上次说过’、‘我们之前谈到’等引用性表达。\n\n"
|
|
50
|
-
"【咨询师发言】\n"
|
|
51
|
-
f"{utterance}\n\n"
|
|
52
|
-
"请返回判断结果(是 / 否)以及判断依据。"
|
|
47
|
+
"下面这句话是心理咨询师说的话,请判断它是否提及了之前疗程的内容。"
|
|
48
|
+
f"\n### 话语\n{utterance}"
|
|
53
49
|
),
|
|
54
50
|
}
|
|
55
51
|
]
|
|
@@ -78,56 +74,16 @@ def query(utterance, conversations, scales):
|
|
|
78
74
|
return formatted_hits
|
|
79
75
|
|
|
80
76
|
client = get_counselor_client()
|
|
81
|
-
# 原始输入结构
|
|
82
|
-
raw_prompt = {
|
|
83
|
-
"task": "根据对话内容,从知识库中搜索相关的信息并总结",
|
|
84
|
-
"utterance": utterance,
|
|
85
|
-
"conversations": conversations,
|
|
86
|
-
"scales": scales
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# 用强模型进行提示词结构优化
|
|
90
|
-
rewrite_prompt = [
|
|
91
|
-
{
|
|
92
|
-
"role": "system",
|
|
93
|
-
"content": (
|
|
94
|
-
"你是一个提示词结构优化专家,擅长将任务信息重写成适合小模型理解、结构清晰的自然语言提示词。"
|
|
95
|
-
"请根据以下内容,重写一段清晰、结构化的指令,以引导小模型从知识库中提取与用户发言密切相关的内容,"
|
|
96
|
-
"并组织成 JSON 字段 `knowledge`。"
|
|
97
|
-
"输出只需为优化后的提示词内容本身,不需要解释、注释或任何其他输出。"
|
|
98
|
-
)
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
"role": "user",
|
|
102
|
-
"content": f"""任务描述:{raw_prompt['task']}
|
|
103
|
-
|
|
104
|
-
【对话内容】
|
|
105
|
-
{raw_prompt['utterance']}
|
|
106
|
-
|
|
107
|
-
【知识库:历史会话】
|
|
108
|
-
{raw_prompt['conversations']}
|
|
109
|
-
|
|
110
|
-
【知识库:量表结果】
|
|
111
|
-
{raw_prompt['scales']}
|
|
112
|
-
"""
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
# 用强模型(如 GPT-4o)生成优化提示词
|
|
117
|
-
rewritten_prompt_response = client.chat.completions.create(
|
|
118
|
-
model=registry.get("anna_engine_config").counselor_model_name, # 推荐用强模型
|
|
119
|
-
messages=rewrite_prompt
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
# 获取结构化优化后的提示词内容
|
|
123
|
-
optimized_prompt = rewritten_prompt_response.choices[0].message.content
|
|
124
|
-
# 用小模型调用工具
|
|
125
77
|
response = client.chat.completions.create(
|
|
126
|
-
model=registry.get("anna_engine_config").counselor_model_name,
|
|
78
|
+
model=registry.get("anna_engine_config").counselor_model_name,
|
|
127
79
|
messages=[
|
|
128
80
|
{
|
|
129
81
|
"role": "user",
|
|
130
|
-
"content":
|
|
82
|
+
"content": (
|
|
83
|
+
"### 任务\n根据对话内容,从知识库中搜索相关的信息并总结。"
|
|
84
|
+
f"\n### 对话内容\n{utterance}"
|
|
85
|
+
f"\n### 知识库\n{conversations}\n{scales}"
|
|
86
|
+
),
|
|
131
87
|
}
|
|
132
88
|
],
|
|
133
89
|
tools=tools,
|