hindsight-api 0.2.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- hindsight_api/admin/__init__.py +1 -0
- hindsight_api/admin/cli.py +311 -0
- hindsight_api/alembic/versions/f1a2b3c4d5e6_add_memory_links_composite_index.py +44 -0
- hindsight_api/alembic/versions/g2a3b4c5d6e7_add_tags_column.py +48 -0
- hindsight_api/alembic/versions/h3c4d5e6f7g8_mental_models_v4.py +112 -0
- hindsight_api/alembic/versions/i4d5e6f7g8h9_delete_opinions.py +41 -0
- hindsight_api/alembic/versions/j5e6f7g8h9i0_mental_model_versions.py +95 -0
- hindsight_api/alembic/versions/k6f7g8h9i0j1_add_directive_subtype.py +58 -0
- hindsight_api/alembic/versions/l7g8h9i0j1k2_add_worker_columns.py +109 -0
- hindsight_api/alembic/versions/m8h9i0j1k2l3_mental_model_id_to_text.py +41 -0
- hindsight_api/alembic/versions/n9i0j1k2l3m4_learnings_and_pinned_reflections.py +134 -0
- hindsight_api/alembic/versions/o0j1k2l3m4n5_migrate_mental_models_data.py +113 -0
- hindsight_api/alembic/versions/p1k2l3m4n5o6_new_knowledge_architecture.py +194 -0
- hindsight_api/alembic/versions/q2l3m4n5o6p7_fix_mental_model_fact_type.py +50 -0
- hindsight_api/alembic/versions/r3m4n5o6p7q8_add_reflect_response_to_reflections.py +47 -0
- hindsight_api/alembic/versions/s4n5o6p7q8r9_add_consolidated_at_to_memory_units.py +53 -0
- hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py +134 -0
- hindsight_api/alembic/versions/u6p7q8r9s0t1_mental_models_text_id.py +41 -0
- hindsight_api/alembic/versions/v7q8r9s0t1u2_add_max_tokens_to_mental_models.py +50 -0
- hindsight_api/api/http.py +1406 -118
- hindsight_api/api/mcp.py +11 -196
- hindsight_api/config.py +359 -27
- hindsight_api/engine/consolidation/__init__.py +5 -0
- hindsight_api/engine/consolidation/consolidator.py +859 -0
- hindsight_api/engine/consolidation/prompts.py +69 -0
- hindsight_api/engine/cross_encoder.py +706 -88
- hindsight_api/engine/db_budget.py +284 -0
- hindsight_api/engine/db_utils.py +11 -0
- hindsight_api/engine/directives/__init__.py +5 -0
- hindsight_api/engine/directives/models.py +37 -0
- hindsight_api/engine/embeddings.py +553 -29
- hindsight_api/engine/entity_resolver.py +8 -5
- hindsight_api/engine/interface.py +40 -17
- hindsight_api/engine/llm_wrapper.py +744 -68
- hindsight_api/engine/memory_engine.py +2505 -1017
- hindsight_api/engine/mental_models/__init__.py +14 -0
- hindsight_api/engine/mental_models/models.py +53 -0
- hindsight_api/engine/query_analyzer.py +4 -3
- hindsight_api/engine/reflect/__init__.py +18 -0
- hindsight_api/engine/reflect/agent.py +933 -0
- hindsight_api/engine/reflect/models.py +109 -0
- hindsight_api/engine/reflect/observations.py +186 -0
- hindsight_api/engine/reflect/prompts.py +483 -0
- hindsight_api/engine/reflect/tools.py +437 -0
- hindsight_api/engine/reflect/tools_schema.py +250 -0
- hindsight_api/engine/response_models.py +168 -4
- hindsight_api/engine/retain/bank_utils.py +79 -201
- hindsight_api/engine/retain/fact_extraction.py +424 -195
- hindsight_api/engine/retain/fact_storage.py +35 -12
- hindsight_api/engine/retain/link_utils.py +29 -24
- hindsight_api/engine/retain/orchestrator.py +24 -43
- hindsight_api/engine/retain/types.py +11 -2
- hindsight_api/engine/search/graph_retrieval.py +43 -14
- hindsight_api/engine/search/link_expansion_retrieval.py +391 -0
- hindsight_api/engine/search/mpfp_retrieval.py +362 -117
- hindsight_api/engine/search/reranking.py +2 -2
- hindsight_api/engine/search/retrieval.py +848 -201
- hindsight_api/engine/search/tags.py +172 -0
- hindsight_api/engine/search/think_utils.py +42 -141
- hindsight_api/engine/search/trace.py +12 -1
- hindsight_api/engine/search/tracer.py +26 -6
- hindsight_api/engine/search/types.py +21 -3
- hindsight_api/engine/task_backend.py +113 -106
- hindsight_api/engine/utils.py +1 -152
- hindsight_api/extensions/__init__.py +10 -1
- hindsight_api/extensions/builtin/tenant.py +5 -1
- hindsight_api/extensions/context.py +10 -1
- hindsight_api/extensions/operation_validator.py +81 -4
- hindsight_api/extensions/tenant.py +26 -0
- hindsight_api/main.py +69 -6
- hindsight_api/mcp_local.py +12 -53
- hindsight_api/mcp_tools.py +494 -0
- hindsight_api/metrics.py +433 -48
- hindsight_api/migrations.py +141 -1
- hindsight_api/models.py +3 -3
- hindsight_api/pg0.py +53 -0
- hindsight_api/server.py +39 -2
- hindsight_api/worker/__init__.py +11 -0
- hindsight_api/worker/main.py +296 -0
- hindsight_api/worker/poller.py +486 -0
- {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/METADATA +16 -6
- hindsight_api-0.4.0.dist-info/RECORD +112 -0
- {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/entry_points.txt +2 -0
- hindsight_api/engine/retain/observation_regeneration.py +0 -254
- hindsight_api/engine/search/observation_utils.py +0 -125
- hindsight_api/engine/search/scoring.py +0 -159
- hindsight_api-0.2.1.dist-info/RECORD +0 -75
- {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System prompts for the reflect agent.
|
|
3
|
+
|
|
4
|
+
The reflect agent uses hierarchical retrieval:
|
|
5
|
+
1. search_mental_models - User-curated summaries (highest quality)
|
|
6
|
+
2. search_observations - Consolidated knowledge with freshness awareness
|
|
7
|
+
3. recall - Raw facts as ground truth fallback
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _extract_directive_rules(directives: list[dict[str, Any]]) -> list[str]:
|
|
15
|
+
"""
|
|
16
|
+
Extract directive rules as a list of strings.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
directives: List of directives with name and content
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of directive rule strings
|
|
23
|
+
"""
|
|
24
|
+
rules = []
|
|
25
|
+
for directive in directives:
|
|
26
|
+
directive_name = directive.get("name", "")
|
|
27
|
+
# New format: directives have direct content field
|
|
28
|
+
content = directive.get("content", "")
|
|
29
|
+
if content:
|
|
30
|
+
if directive_name:
|
|
31
|
+
rules.append(f"**{directive_name}**: {content}")
|
|
32
|
+
else:
|
|
33
|
+
rules.append(content)
|
|
34
|
+
else:
|
|
35
|
+
# Legacy format: check for observations
|
|
36
|
+
observations = directive.get("observations", [])
|
|
37
|
+
if observations:
|
|
38
|
+
for obs in observations:
|
|
39
|
+
# Support both Pydantic Observation objects and dicts
|
|
40
|
+
if hasattr(obs, "title"):
|
|
41
|
+
title = obs.title
|
|
42
|
+
obs_content = obs.content
|
|
43
|
+
else:
|
|
44
|
+
title = obs.get("title", "")
|
|
45
|
+
obs_content = obs.get("content", "")
|
|
46
|
+
if title and obs_content:
|
|
47
|
+
rules.append(f"**{title}**: {obs_content}")
|
|
48
|
+
elif obs_content:
|
|
49
|
+
rules.append(obs_content)
|
|
50
|
+
elif directive_name:
|
|
51
|
+
# Fallback to description
|
|
52
|
+
desc = directive.get("description", "")
|
|
53
|
+
if desc:
|
|
54
|
+
rules.append(f"**{directive_name}**: {desc}")
|
|
55
|
+
return rules
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_directives_section(directives: list[dict[str, Any]]) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Build the directives section for the system prompt.
|
|
61
|
+
|
|
62
|
+
Directives are hard rules that MUST be followed in all responses.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
directives: List of directive mental models with observations
|
|
66
|
+
"""
|
|
67
|
+
if not directives:
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
rules = _extract_directive_rules(directives)
|
|
71
|
+
if not rules:
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
parts = [
|
|
75
|
+
"## DIRECTIVES (MANDATORY)",
|
|
76
|
+
"These are hard rules you MUST follow in ALL responses:",
|
|
77
|
+
"",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for rule in rules:
|
|
81
|
+
parts.append(f"- {rule}")
|
|
82
|
+
|
|
83
|
+
parts.extend(
|
|
84
|
+
[
|
|
85
|
+
"",
|
|
86
|
+
"NEVER violate these directives, even if other context suggests otherwise.",
|
|
87
|
+
"IMPORTANT: Do NOT explain or justify how you handled directives in your answer. Just follow them silently.",
|
|
88
|
+
"",
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
return "\n".join(parts)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_directives_reminder(directives: list[dict[str, Any]]) -> str:
|
|
95
|
+
"""
|
|
96
|
+
Build a reminder section for directives to place at the end of the prompt.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
directives: List of directive mental models with observations
|
|
100
|
+
"""
|
|
101
|
+
if not directives:
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
rules = _extract_directive_rules(directives)
|
|
105
|
+
if not rules:
|
|
106
|
+
return ""
|
|
107
|
+
|
|
108
|
+
parts = [
|
|
109
|
+
"",
|
|
110
|
+
"## REMINDER: MANDATORY DIRECTIVES",
|
|
111
|
+
"Before responding, ensure your answer complies with ALL of these directives:",
|
|
112
|
+
"",
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
for i, rule in enumerate(rules, 1):
|
|
116
|
+
parts.append(f"{i}. {rule}")
|
|
117
|
+
|
|
118
|
+
parts.append("")
|
|
119
|
+
parts.append("Your response will be REJECTED if it violates any directive above.")
|
|
120
|
+
parts.append("Do NOT include any commentary about how you handled directives - just follow them.")
|
|
121
|
+
return "\n".join(parts)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_system_prompt_for_tools(
|
|
125
|
+
bank_profile: dict[str, Any],
|
|
126
|
+
context: str | None = None,
|
|
127
|
+
directives: list[dict[str, Any]] | None = None,
|
|
128
|
+
has_mental_models: bool = False,
|
|
129
|
+
budget: str | None = None,
|
|
130
|
+
) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Build the system prompt for tool-calling reflect agent.
|
|
133
|
+
|
|
134
|
+
The agent uses hierarchical retrieval:
|
|
135
|
+
1. search_mental_models - User-curated summaries (try first, if available)
|
|
136
|
+
2. search_observations - Consolidated knowledge with freshness
|
|
137
|
+
3. recall - Raw facts as ground truth
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
bank_profile: Bank profile with name and mission
|
|
141
|
+
context: Optional additional context
|
|
142
|
+
directives: Optional list of directive mental models to inject as hard rules
|
|
143
|
+
has_mental_models: Whether the bank has any mental models (skip if not)
|
|
144
|
+
budget: Search depth budget - "low", "mid", or "high". Controls exploration thoroughness.
|
|
145
|
+
"""
|
|
146
|
+
name = bank_profile.get("name", "Assistant")
|
|
147
|
+
mission = bank_profile.get("mission", "")
|
|
148
|
+
|
|
149
|
+
parts = []
|
|
150
|
+
|
|
151
|
+
# Inject directives at the VERY START for maximum prominence
|
|
152
|
+
if directives:
|
|
153
|
+
parts.append(build_directives_section(directives))
|
|
154
|
+
|
|
155
|
+
parts.extend(
|
|
156
|
+
[
|
|
157
|
+
"You are a reflection agent that answers questions by reasoning over retrieved memories.",
|
|
158
|
+
"",
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
parts.extend(
|
|
163
|
+
[
|
|
164
|
+
"## CRITICAL RULES",
|
|
165
|
+
"- You must NEVER fabricate information that has no basis in retrieved data",
|
|
166
|
+
"- You SHOULD synthesize, infer, and reason from the retrieved memories",
|
|
167
|
+
"- You MUST search before saying you don't have information",
|
|
168
|
+
"",
|
|
169
|
+
"## How to Reason",
|
|
170
|
+
"- If memories mention someone did an activity, you can infer they likely enjoyed it",
|
|
171
|
+
"- Synthesize a coherent narrative from related memories",
|
|
172
|
+
"- Be a thoughtful interpreter, not just a literal repeater",
|
|
173
|
+
"- When the exact answer isn't stated, use what IS stated to give the best answer",
|
|
174
|
+
"",
|
|
175
|
+
"## HIERARCHICAL RETRIEVAL STRATEGY",
|
|
176
|
+
"",
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Build retrieval levels based on what's available
|
|
181
|
+
if has_mental_models:
|
|
182
|
+
parts.extend(
|
|
183
|
+
[
|
|
184
|
+
"You have access to THREE levels of knowledge. Use them in this order:",
|
|
185
|
+
"",
|
|
186
|
+
"### 1. MENTAL MODELS (search_mental_models) - Try First",
|
|
187
|
+
"- User-curated summaries about specific topics",
|
|
188
|
+
"- HIGHEST quality - manually created and maintained",
|
|
189
|
+
"- If a relevant mental model exists and is FRESH, it may fully answer the question",
|
|
190
|
+
"- Check `is_stale` field - if stale, also verify with lower levels",
|
|
191
|
+
"",
|
|
192
|
+
"### 2. OBSERVATIONS (search_observations) - Second Priority",
|
|
193
|
+
"- Auto-consolidated knowledge from memories",
|
|
194
|
+
"- Check `is_stale` field - if stale, ALSO use recall() to verify",
|
|
195
|
+
"- Good for understanding patterns and summaries",
|
|
196
|
+
"",
|
|
197
|
+
"### 3. RAW FACTS (recall) - Ground Truth",
|
|
198
|
+
"- Individual memories (world facts and experiences)",
|
|
199
|
+
"- Use when: no mental models/observations exist, they're stale, or you need specific details",
|
|
200
|
+
"- This is the source of truth that other levels are built from",
|
|
201
|
+
"",
|
|
202
|
+
]
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
parts.extend(
|
|
206
|
+
[
|
|
207
|
+
"You have access to TWO levels of knowledge. Use them in this order:",
|
|
208
|
+
"",
|
|
209
|
+
"### 1. OBSERVATIONS (search_observations) - Try First",
|
|
210
|
+
"- Auto-consolidated knowledge from memories",
|
|
211
|
+
"- Check `is_stale` field - if stale, ALSO use recall() to verify",
|
|
212
|
+
"- Good for understanding patterns and summaries",
|
|
213
|
+
"",
|
|
214
|
+
"### 2. RAW FACTS (recall) - Ground Truth",
|
|
215
|
+
"- Individual memories (world facts and experiences)",
|
|
216
|
+
"- Use when: no observations exist, they're stale, or you need specific details",
|
|
217
|
+
"- This is the source of truth that observations are built from",
|
|
218
|
+
"",
|
|
219
|
+
]
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
parts.extend(
|
|
223
|
+
[
|
|
224
|
+
"## Query Strategy",
|
|
225
|
+
"recall() uses semantic search. NEVER just echo the user's question - decompose it into targeted searches:",
|
|
226
|
+
"",
|
|
227
|
+
"BAD: User asks 'recurring lesson themes between students' → recall('recurring lesson themes between students')",
|
|
228
|
+
"GOOD: Break it down into component searches:",
|
|
229
|
+
" 1. recall('lessons') - find all lesson-related memories",
|
|
230
|
+
" 2. recall('teaching sessions') - alternative phrasing",
|
|
231
|
+
" 3. recall('student progress') - find student-related memories",
|
|
232
|
+
"",
|
|
233
|
+
"Think: What ENTITIES and CONCEPTS does this question involve? Search for each separately.",
|
|
234
|
+
"",
|
|
235
|
+
]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Add budget guidance
|
|
239
|
+
if budget:
|
|
240
|
+
budget_lower = budget.lower()
|
|
241
|
+
if budget_lower == "low":
|
|
242
|
+
parts.extend(
|
|
243
|
+
[
|
|
244
|
+
"## RESEARCH DEPTH: SHALLOW (Quick Response)",
|
|
245
|
+
"- Prioritize speed over completeness",
|
|
246
|
+
"- If mental models or observations provide a reasonable answer, stop there",
|
|
247
|
+
"- Only dig deeper if the initial results are clearly insufficient",
|
|
248
|
+
"- Prefer a quick overview rather than exhaustive details",
|
|
249
|
+
"- Answer promptly with available information",
|
|
250
|
+
"",
|
|
251
|
+
]
|
|
252
|
+
)
|
|
253
|
+
elif budget_lower == "mid":
|
|
254
|
+
parts.extend(
|
|
255
|
+
[
|
|
256
|
+
"## RESEARCH DEPTH: MODERATE (Balanced)",
|
|
257
|
+
"- Balance thoroughness with efficiency",
|
|
258
|
+
"- Check multiple sources when the question warrants it",
|
|
259
|
+
"- Verify stale data if it's central to the answer",
|
|
260
|
+
"- Don't over-explore, but ensure reasonable coverage",
|
|
261
|
+
"",
|
|
262
|
+
]
|
|
263
|
+
)
|
|
264
|
+
elif budget_lower == "high":
|
|
265
|
+
parts.extend(
|
|
266
|
+
[
|
|
267
|
+
"## RESEARCH DEPTH: DEEP (Thorough Exploration)",
|
|
268
|
+
"- Explore comprehensively before answering",
|
|
269
|
+
"- Search across all available knowledge levels",
|
|
270
|
+
"- Use multiple query variations to ensure coverage",
|
|
271
|
+
"- Verify information across different retrieval levels",
|
|
272
|
+
"- Use expand() to get full context on important memories",
|
|
273
|
+
"- Take time to synthesize a complete, well-researched answer",
|
|
274
|
+
"",
|
|
275
|
+
]
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
parts.append("## Workflow")
|
|
279
|
+
|
|
280
|
+
if has_mental_models:
|
|
281
|
+
parts.extend(
|
|
282
|
+
[
|
|
283
|
+
"1. First, try search_mental_models() - check if a curated summary exists",
|
|
284
|
+
"2. If no mental model or it's stale, try search_observations() for consolidated knowledge",
|
|
285
|
+
"3. If observations are stale OR you need specific details, use recall() for raw facts",
|
|
286
|
+
"4. Use expand() if you need more context on specific memories",
|
|
287
|
+
"5. When ready, call done() with your answer and supporting IDs",
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
parts.extend(
|
|
292
|
+
[
|
|
293
|
+
"1. First, try search_observations() - check for consolidated knowledge",
|
|
294
|
+
"2. If observations are stale OR you need specific details, use recall() for raw facts",
|
|
295
|
+
"3. Use expand() if you need more context on specific memories",
|
|
296
|
+
"4. When ready, call done() with your answer and supporting IDs",
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
parts.extend(
|
|
301
|
+
[
|
|
302
|
+
"",
|
|
303
|
+
"## Output Format: Plain Text Answer",
|
|
304
|
+
"Call done() with a plain text 'answer' field.",
|
|
305
|
+
"- Do NOT use markdown formatting",
|
|
306
|
+
"- NEVER include memory IDs, UUIDs, or 'Memory references' in the answer text",
|
|
307
|
+
"- Put IDs ONLY in the memory_ids/mental_model_ids/observation_ids arrays, not in the answer",
|
|
308
|
+
]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
parts.append("")
|
|
312
|
+
parts.append(f"## Memory Bank: {name}")
|
|
313
|
+
|
|
314
|
+
if mission:
|
|
315
|
+
parts.append(f"Mission: {mission}")
|
|
316
|
+
|
|
317
|
+
# Disposition traits
|
|
318
|
+
disposition = bank_profile.get("disposition", {})
|
|
319
|
+
if disposition:
|
|
320
|
+
traits = []
|
|
321
|
+
if "skepticism" in disposition:
|
|
322
|
+
traits.append(f"skepticism={disposition['skepticism']}")
|
|
323
|
+
if "literalism" in disposition:
|
|
324
|
+
traits.append(f"literalism={disposition['literalism']}")
|
|
325
|
+
if "empathy" in disposition:
|
|
326
|
+
traits.append(f"empathy={disposition['empathy']}")
|
|
327
|
+
if traits:
|
|
328
|
+
parts.append(f"Disposition: {', '.join(traits)}")
|
|
329
|
+
|
|
330
|
+
if context:
|
|
331
|
+
parts.append(f"\n## Additional Context\n{context}")
|
|
332
|
+
|
|
333
|
+
# Add directive reminder at the END for recency effect
|
|
334
|
+
if directives:
|
|
335
|
+
parts.append(build_directives_reminder(directives))
|
|
336
|
+
|
|
337
|
+
return "\n".join(parts)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def build_agent_prompt(
|
|
341
|
+
query: str,
|
|
342
|
+
context_history: list[dict],
|
|
343
|
+
bank_profile: dict,
|
|
344
|
+
additional_context: str | None = None,
|
|
345
|
+
) -> str:
|
|
346
|
+
"""Build the user prompt for the reflect agent."""
|
|
347
|
+
parts = []
|
|
348
|
+
|
|
349
|
+
# Bank identity
|
|
350
|
+
name = bank_profile.get("name", "Assistant")
|
|
351
|
+
mission = bank_profile.get("mission", "")
|
|
352
|
+
|
|
353
|
+
parts.append(f"## Memory Bank Context\nName: {name}")
|
|
354
|
+
if mission:
|
|
355
|
+
parts.append(f"Mission: {mission}")
|
|
356
|
+
|
|
357
|
+
# Disposition traits if present
|
|
358
|
+
disposition = bank_profile.get("disposition", {})
|
|
359
|
+
if disposition:
|
|
360
|
+
traits = []
|
|
361
|
+
if "skepticism" in disposition:
|
|
362
|
+
traits.append(f"skepticism={disposition['skepticism']}")
|
|
363
|
+
if "literalism" in disposition:
|
|
364
|
+
traits.append(f"literalism={disposition['literalism']}")
|
|
365
|
+
if "empathy" in disposition:
|
|
366
|
+
traits.append(f"empathy={disposition['empathy']}")
|
|
367
|
+
if traits:
|
|
368
|
+
parts.append(f"Disposition: {', '.join(traits)}")
|
|
369
|
+
|
|
370
|
+
# Additional context from caller
|
|
371
|
+
if additional_context:
|
|
372
|
+
parts.append(f"\n## Additional Context\n{additional_context}")
|
|
373
|
+
|
|
374
|
+
# Tool call history
|
|
375
|
+
if context_history:
|
|
376
|
+
parts.append("\n## Tool Results (synthesize and reason from this data)")
|
|
377
|
+
for i, entry in enumerate(context_history, 1):
|
|
378
|
+
tool = entry["tool"]
|
|
379
|
+
output = entry["output"]
|
|
380
|
+
# Format as proper JSON for LLM readability
|
|
381
|
+
try:
|
|
382
|
+
output_str = json.dumps(output, indent=2, default=str)
|
|
383
|
+
except (TypeError, ValueError):
|
|
384
|
+
output_str = str(output)
|
|
385
|
+
parts.append(f"\n### Call {i}: {tool}\n```json\n{output_str}\n```")
|
|
386
|
+
|
|
387
|
+
# The question
|
|
388
|
+
parts.append(f"\n## Question\n{query}")
|
|
389
|
+
|
|
390
|
+
# Instructions
|
|
391
|
+
if context_history:
|
|
392
|
+
parts.append(
|
|
393
|
+
"\n## Instructions\n"
|
|
394
|
+
"Based on the tool results above, either call more tools or provide your final answer. "
|
|
395
|
+
"Synthesize and reason from the data - make reasonable inferences when helpful. "
|
|
396
|
+
"If you have related information, use it to give the best possible answer."
|
|
397
|
+
)
|
|
398
|
+
else:
|
|
399
|
+
parts.append(
|
|
400
|
+
"\n## Instructions\n"
|
|
401
|
+
"Start by searching for relevant information using the hierarchical retrieval strategy:\n"
|
|
402
|
+
"1. Try search_mental_models() first for curated summaries\n"
|
|
403
|
+
"2. Try search_observations() for consolidated knowledge\n"
|
|
404
|
+
"3. Use recall() for specific details or to verify stale data"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return "\n".join(parts)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def build_final_prompt(
|
|
411
|
+
query: str,
|
|
412
|
+
context_history: list[dict],
|
|
413
|
+
bank_profile: dict,
|
|
414
|
+
additional_context: str | None = None,
|
|
415
|
+
) -> str:
|
|
416
|
+
"""Build the final prompt when forcing a text response (no tools)."""
|
|
417
|
+
parts = []
|
|
418
|
+
|
|
419
|
+
# Bank identity
|
|
420
|
+
name = bank_profile.get("name", "Assistant")
|
|
421
|
+
mission = bank_profile.get("mission", "")
|
|
422
|
+
|
|
423
|
+
parts.append(f"## Memory Bank Context\nName: {name}")
|
|
424
|
+
if mission:
|
|
425
|
+
parts.append(f"Mission: {mission}")
|
|
426
|
+
|
|
427
|
+
# Disposition traits if present
|
|
428
|
+
disposition = bank_profile.get("disposition", {})
|
|
429
|
+
if disposition:
|
|
430
|
+
traits = []
|
|
431
|
+
if "skepticism" in disposition:
|
|
432
|
+
traits.append(f"skepticism={disposition['skepticism']}")
|
|
433
|
+
if "literalism" in disposition:
|
|
434
|
+
traits.append(f"literalism={disposition['literalism']}")
|
|
435
|
+
if "empathy" in disposition:
|
|
436
|
+
traits.append(f"empathy={disposition['empathy']}")
|
|
437
|
+
if traits:
|
|
438
|
+
parts.append(f"Disposition: {', '.join(traits)}")
|
|
439
|
+
|
|
440
|
+
# Additional context from caller
|
|
441
|
+
if additional_context:
|
|
442
|
+
parts.append(f"\n## Additional Context\n{additional_context}")
|
|
443
|
+
|
|
444
|
+
# Tool call history
|
|
445
|
+
if context_history:
|
|
446
|
+
parts.append("\n## Retrieved Data (synthesize and reason from this data)")
|
|
447
|
+
for entry in context_history:
|
|
448
|
+
tool = entry["tool"]
|
|
449
|
+
output = entry["output"]
|
|
450
|
+
# Format as proper JSON for LLM readability
|
|
451
|
+
try:
|
|
452
|
+
output_str = json.dumps(output, indent=2, default=str)
|
|
453
|
+
except (TypeError, ValueError):
|
|
454
|
+
output_str = str(output)
|
|
455
|
+
parts.append(f"\n### From {tool}:\n```json\n{output_str}\n```")
|
|
456
|
+
else:
|
|
457
|
+
parts.append("\n## Retrieved Data\nNo data was retrieved.")
|
|
458
|
+
|
|
459
|
+
# The question
|
|
460
|
+
parts.append(f"\n## Question\n{query}")
|
|
461
|
+
|
|
462
|
+
# Final instructions
|
|
463
|
+
parts.append(
|
|
464
|
+
"\n## Instructions\n"
|
|
465
|
+
"Provide a thoughtful answer by synthesizing and reasoning from the retrieved data above. "
|
|
466
|
+
"You can make reasonable inferences from the memories, but don't completely fabricate information."
|
|
467
|
+
"If the exact answer isn't stated, use what IS stated to give the best possible answer. "
|
|
468
|
+
"Only say 'I don't have information' if the retrieved data is truly unrelated to the question."
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return "\n".join(parts)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
FINAL_SYSTEM_PROMPT = """You are a thoughtful assistant that synthesizes answers from retrieved memories.
|
|
475
|
+
|
|
476
|
+
Your approach:
|
|
477
|
+
- Reason over the retrieved memories to answer the question
|
|
478
|
+
- Make reasonable inferences when the exact answer isn't explicitly stated
|
|
479
|
+
- Connect related memories to form a complete picture
|
|
480
|
+
- Be helpful - if you have related information, use it to give the best possible answer
|
|
481
|
+
|
|
482
|
+
Only say "I don't have information" if the retrieved data is truly unrelated to the question.
|
|
483
|
+
Do NOT fabricate information that has no basis in the retrieved data."""
|