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.
Files changed (63) hide show
  1. {anna_agent-0.2.1 → anna_agent-0.2.2}/PKG-INFO +1 -1
  2. {anna_agent-0.2.1 → anna_agent-0.2.2}/pyproject.toml +1 -1
  3. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/__init__.py +1 -1
  4. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/anna_agent_template.py +8 -5
  5. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/cli.py +78 -15
  6. anna_agent-0.2.2/src/anna_agent/complaint_elicitor.py +71 -0
  7. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/init_content.py +3 -9
  8. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/load_config.py +36 -19
  9. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/diagnostics.py +4 -2
  10. anna_agent-0.2.2/src/anna_agent/fill_scales.py +175 -0
  11. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/querier.py +8 -52
  12. anna_agent-0.2.2/src/anna_agent/short_term_memory.py +185 -0
  13. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/PKG-INFO +1 -1
  14. anna_agent-0.2.1/src/anna_agent/complaint_elicitor.py +0 -88
  15. anna_agent-0.2.1/src/anna_agent/fill_scales.py +0 -385
  16. anna_agent-0.2.1/src/anna_agent/short_term_memory.py +0 -286
  17. {anna_agent-0.2.1 → anna_agent-0.2.2}/LICENSE +0 -0
  18. {anna_agent-0.2.1 → anna_agent-0.2.2}/README.md +0 -0
  19. {anna_agent-0.2.1 → anna_agent-0.2.2}/setup.cfg +0 -0
  20. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/__main__.py +0 -0
  21. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/assets.py +0 -0
  22. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/backbone.py +0 -0
  23. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/case_data.py +0 -0
  24. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/common/registry.py +0 -0
  25. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/common/tool_calls.py +0 -0
  26. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/complaint_chain.py +0 -0
  27. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/__init__.py +0 -0
  28. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/defaults.py +0 -0
  29. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/environment_reader.py +0 -0
  30. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/initialize.py +0 -0
  31. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/config/models/anna_engine_config.py +0 -0
  32. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/counselor.py +0 -0
  33. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/datasets/cbt-triggering-events.csv +0 -0
  34. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/emotion_modulator.py +0 -0
  35. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/emotion_pertuber.py +0 -0
  36. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/event_trigger.py +0 -0
  37. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/figure/readme.md +0 -0
  38. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/figure/whiteboard_exported_image_en.png +0 -0
  39. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/initialize.py +0 -0
  40. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/__init__.py +0 -0
  41. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/chunking.py +0 -0
  42. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/embeddings.py +0 -0
  43. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/models.py +0 -0
  44. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/memory/store.py +0 -0
  45. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/model_services.py +0 -0
  46. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/ms_patient.py +0 -0
  47. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/ms_patient_with_prompt.py +0 -0
  48. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/runtime.py +0 -0
  49. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/__init__.py +0 -0
  50. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/bdi.json +0 -0
  51. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/ghq-28.json +0 -0
  52. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/scales/sass.json +0 -0
  53. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/complaint.sh +0 -0
  54. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/counselor.sh +0 -0
  55. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/server/emotion.sh +0 -0
  56. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/service.py +0 -0
  57. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/style_analyzer.py +0 -0
  58. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent/workspace.py +0 -0
  59. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/SOURCES.txt +0 -0
  60. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/dependency_links.txt +0 -0
  61. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/entry_points.txt +0 -0
  62. {anna_agent-0.2.1 → anna_agent-0.2.2}/src/anna_agent.egg-info/requires.txt +0 -0
  63. {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.1
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "anna-agent"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Dynamic Evolution Agent System with Multi-Session Memory for Realistic Seeker Simulation"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = []
4
4
 
5
- __version__ = "0.2.1"
5
+ __version__ = "0.2.2"
@@ -1,17 +1,18 @@
1
1
  ## 主诉请求和情绪通过reminder来控制
2
2
 
3
3
  prompt_template = """
4
- 你正在扮演 心理障碍的患者,你正在cosplay 心理咨询患者。
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 _looks_like_connection_error(err: BaseException) -> bool:
199
+ def _iter_exception_chain(err: BaseException):
200
200
  current: BaseException | None = err
201
201
  while current is not None:
202
- if current.__class__.__name__ in {"APIConnectionError", "ConnectError"}:
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
- console.print(f"[red]Model service connection failed:[/red] {escape(str(err))}")
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
- response = client.chat.completions.create(
630
- model=cfg.model_name,
631
- messages=[{"role": "user", "content": "Reply with OK."}],
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 _looks_like_connection_error(err):
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 _looks_like_connection_error(err):
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 _looks_like_connection_error(err):
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. It can temporarily stand in
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 AnnaAgent backbone fallback endpoint for internal modules that still
141
- # use the historical counselor config key.
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") in {None, anna_engine_defaults.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") in {None, anna_engine_defaults.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") in {
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": anna_engine_defaults.counselor_api_key,
217
- "counselor_base_url": anna_engine_defaults.counselor_base_url,
218
- "counselor_model_name": anna_engine_defaults.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 and values.get(field) in {None, defaults[field]}:
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) in {None, default}
235
- for field, default in counselor_defaults.items()
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}, base_url configured={bool(cfg.base_url)}",
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
- "请判断下面这句咨询师的发言是否包含了对之前疗程或历史对话的提及。\n\n"
48
- "【任务说明】\n"
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": optimized_prompt
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,