solana-agent 31.1.1__py3-none-any.whl → 31.1.2__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.
- solana_agent/services/query.py +56 -37
- {solana_agent-31.1.1.dist-info → solana_agent-31.1.2.dist-info}/METADATA +1 -1
- {solana_agent-31.1.1.dist-info → solana_agent-31.1.2.dist-info}/RECORD +6 -6
- {solana_agent-31.1.1.dist-info → solana_agent-31.1.2.dist-info}/LICENSE +0 -0
- {solana_agent-31.1.1.dist-info → solana_agent-31.1.2.dist-info}/WHEEL +0 -0
- {solana_agent-31.1.1.dist-info → solana_agent-31.1.2.dist-info}/entry_points.txt +0 -0
solana_agent/services/query.py
CHANGED
@@ -200,7 +200,9 @@ class QueryService(QueryServiceInterface):
|
|
200
200
|
|
201
201
|
# 7) Captured data context + incremental save using previous assistant message
|
202
202
|
capture_context = ""
|
203
|
-
|
203
|
+
# Two completion flags:
|
204
|
+
required_complete = False
|
205
|
+
form_complete = False # required + optional
|
204
206
|
|
205
207
|
# Helpers
|
206
208
|
def _non_empty(v: Any) -> bool:
|
@@ -215,7 +217,6 @@ class QueryService(QueryServiceInterface):
|
|
215
217
|
|
216
218
|
def _parse_numbers_list(s: str) -> List[str]:
|
217
219
|
nums = re.findall(r"\b(\d+)\b", s)
|
218
|
-
# dedupe keep order
|
219
220
|
seen, out = set(), []
|
220
221
|
for n in nums:
|
221
222
|
if n not in seen:
|
@@ -225,9 +226,7 @@ class QueryService(QueryServiceInterface):
|
|
225
226
|
|
226
227
|
def _extract_numbered_options(text: str) -> Dict[str, str]:
|
227
228
|
"""Parse previous assistant message for lines like:
|
228
|
-
'1) Foo', '
|
229
|
-
Returns mapping '1' -> 'Foo', etc.
|
230
|
-
"""
|
229
|
+
'1) Foo', '1. Foo', '- 1) Foo', '* 1. Foo' -> {'1': 'Foo'}"""
|
231
230
|
options: Dict[str, str] = {}
|
232
231
|
if not text:
|
233
232
|
return options
|
@@ -235,13 +234,9 @@ class QueryService(QueryServiceInterface):
|
|
235
234
|
line = raw.strip()
|
236
235
|
if not line:
|
237
236
|
continue
|
238
|
-
# Common Markdown patterns: "1. Label", "1) Label", "- 1) Label", "* 1. Label"
|
239
237
|
m = re.match(r"^(?:[-*]\s*)?(\d+)[\.)]?\s+(.*)$", line)
|
240
238
|
if m:
|
241
|
-
idx, label = m.group(1), m.group(2).strip()
|
242
|
-
# Strip trailing markdown soft-break spaces
|
243
|
-
label = label.rstrip()
|
244
|
-
# Ignore labels that are too short or look like continuations
|
239
|
+
idx, label = m.group(1), m.group(2).strip().rstrip()
|
245
240
|
if len(label) >= 1:
|
246
241
|
options[idx] = label
|
247
242
|
return options
|
@@ -252,7 +247,6 @@ class QueryService(QueryServiceInterface):
|
|
252
247
|
if not prev_text or not isinstance(schema, dict):
|
253
248
|
return None
|
254
249
|
t = prev_text.lower()
|
255
|
-
# Heuristic synonyms for your onboarding schema
|
256
250
|
patterns = [
|
257
251
|
("ideas", ["which ideas attract you", "ideas"]),
|
258
252
|
("description", ["please describe yourself", "describe yourself"]),
|
@@ -269,7 +263,6 @@ class QueryService(QueryServiceInterface):
|
|
269
263
|
for field, keys in patterns:
|
270
264
|
if field in candidates and any(key in t for key in keys):
|
271
265
|
return field
|
272
|
-
# Fallback: property name appears directly
|
273
266
|
for field in candidates:
|
274
267
|
if field in t:
|
275
268
|
return field
|
@@ -322,33 +315,41 @@ class QueryService(QueryServiceInterface):
|
|
322
315
|
required_fields = list(
|
323
316
|
(active_capture_schema or {}).get("required", []) or []
|
324
317
|
)
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
318
|
+
all_fields = list(props.keys())
|
319
|
+
optional_fields = [
|
320
|
+
f for f in all_fields if f not in set(required_fields)
|
321
|
+
]
|
322
|
+
|
329
323
|
active_data_existing = (
|
330
324
|
latest_by_name.get(active_capture_name, {}) or {}
|
331
325
|
).get("data", {}) or {}
|
332
326
|
|
333
|
-
def
|
327
|
+
def _missing(fields: List[str]) -> List[str]:
|
334
328
|
return [
|
335
329
|
f
|
336
|
-
for f in
|
330
|
+
for f in fields
|
337
331
|
if not _non_empty(active_data_existing.get(f))
|
338
332
|
]
|
339
333
|
|
334
|
+
missing_required = _missing(required_fields)
|
335
|
+
missing_optional = _missing(optional_fields)
|
336
|
+
|
337
|
+
target_field: Optional[str] = _detect_field_from_prev_question(
|
338
|
+
prev_assistant, active_capture_schema
|
339
|
+
)
|
340
340
|
if not target_field:
|
341
|
-
missing
|
342
|
-
if len(
|
343
|
-
target_field =
|
341
|
+
# If exactly one required missing, target it; else if none required missing and exactly one optional missing, target it.
|
342
|
+
if len(missing_required) == 1:
|
343
|
+
target_field = missing_required[0]
|
344
|
+
elif len(missing_required) == 0 and len(missing_optional) == 1:
|
345
|
+
target_field = missing_optional[0]
|
344
346
|
|
345
|
-
if target_field:
|
347
|
+
if target_field and target_field in props:
|
346
348
|
f_schema = props.get(target_field, {}) or {}
|
347
349
|
f_type = f_schema.get("type")
|
348
350
|
number_to_label = _extract_numbered_options(prev_assistant)
|
349
351
|
|
350
352
|
if number_to_label:
|
351
|
-
# Map any numbers in user's reply to their labels
|
352
353
|
nums = _parse_numbers_list(user_text)
|
353
354
|
labels = [
|
354
355
|
number_to_label[n] for n in nums if n in number_to_label
|
@@ -359,7 +360,6 @@ class QueryService(QueryServiceInterface):
|
|
359
360
|
else:
|
360
361
|
incremental[target_field] = labels[0]
|
361
362
|
|
362
|
-
# If we didn't map via options, fallback to type-based parse
|
363
363
|
if target_field not in incremental:
|
364
364
|
if f_type == "number":
|
365
365
|
m = re.search(r"\b([0-9]+(?:\.[0-9]+)?)\b", user_text)
|
@@ -369,7 +369,6 @@ class QueryService(QueryServiceInterface):
|
|
369
369
|
except Exception:
|
370
370
|
pass
|
371
371
|
elif f_type == "array":
|
372
|
-
# Accept CSV-style input as array of strings
|
373
372
|
parts = [
|
374
373
|
p.strip()
|
375
374
|
for p in re.split(r"[,\n;]+", user_text)
|
@@ -377,11 +376,10 @@ class QueryService(QueryServiceInterface):
|
|
377
376
|
]
|
378
377
|
if parts:
|
379
378
|
incremental[target_field] = parts
|
380
|
-
else:
|
379
|
+
else:
|
381
380
|
if user_text.strip():
|
382
381
|
incremental[target_field] = user_text.strip()
|
383
382
|
|
384
|
-
# Filter out empty junk and save
|
385
383
|
if incremental:
|
386
384
|
cleaned = {
|
387
385
|
k: v for k, v in incremental.items() if _non_empty(v)
|
@@ -397,6 +395,7 @@ class QueryService(QueryServiceInterface):
|
|
397
395
|
)
|
398
396
|
except Exception as se:
|
399
397
|
logger.error(f"Error saving incremental capture: {se}")
|
398
|
+
|
400
399
|
except Exception as e:
|
401
400
|
logger.debug(f"Incremental extraction skipped: {e}")
|
402
401
|
|
@@ -411,19 +410,33 @@ class QueryService(QueryServiceInterface):
|
|
411
410
|
|
412
411
|
lines: List[str] = []
|
413
412
|
if active_capture_name and isinstance(active_capture_schema, dict):
|
414
|
-
|
413
|
+
props = (active_capture_schema or {}).get("properties", {})
|
415
414
|
required_fields = list(
|
416
415
|
(active_capture_schema or {}).get("required", []) or []
|
417
416
|
)
|
418
|
-
|
419
|
-
|
417
|
+
all_fields = list(props.keys())
|
418
|
+
optional_fields = [
|
419
|
+
f for f in all_fields if f not in set(required_fields)
|
420
420
|
]
|
421
|
-
|
421
|
+
|
422
|
+
active_data = _get_active_data(active_capture_name)
|
423
|
+
|
424
|
+
def _missing_from(data: Dict[str, Any], fields: List[str]) -> List[str]:
|
425
|
+
return [f for f in fields if not _non_empty(data.get(f))]
|
426
|
+
|
427
|
+
missing_required = _missing_from(active_data, required_fields)
|
428
|
+
missing_optional = _missing_from(active_data, optional_fields)
|
429
|
+
|
430
|
+
required_complete = (
|
431
|
+
len(missing_required) == 0 and len(required_fields) > 0
|
432
|
+
)
|
433
|
+
form_complete = required_complete and len(missing_optional) == 0
|
422
434
|
|
423
435
|
lines.append(
|
424
436
|
"CAPTURED FORM STATE (Authoritative; do not re-ask filled values):"
|
425
437
|
)
|
426
438
|
lines.append(f"- form_name: {active_capture_name}")
|
439
|
+
|
427
440
|
if active_data:
|
428
441
|
pairs = [
|
429
442
|
f"{k}: {v}" for k, v in active_data.items() if _non_empty(v)
|
@@ -433,8 +446,12 @@ class QueryService(QueryServiceInterface):
|
|
433
446
|
)
|
434
447
|
else:
|
435
448
|
lines.append("- filled_fields: (none)")
|
449
|
+
|
450
|
+
lines.append(
|
451
|
+
f"- missing_required_fields: {', '.join(missing_required) if missing_required else '(none)'}"
|
452
|
+
)
|
436
453
|
lines.append(
|
437
|
-
f"-
|
454
|
+
f"- missing_optional_fields: {', '.join(missing_optional) if missing_optional else '(none)'}"
|
438
455
|
)
|
439
456
|
lines.append("")
|
440
457
|
|
@@ -455,7 +472,7 @@ class QueryService(QueryServiceInterface):
|
|
455
472
|
if lines:
|
456
473
|
capture_context = "\n".join(lines) + "\n\n"
|
457
474
|
|
458
|
-
# Merge contexts
|
475
|
+
# Merge contexts + flow rules
|
459
476
|
combined_context = ""
|
460
477
|
if capture_context:
|
461
478
|
combined_context += capture_context
|
@@ -470,9 +487,11 @@ class QueryService(QueryServiceInterface):
|
|
470
487
|
"- Prefer KB/tools for facts.\n"
|
471
488
|
"- History is for tone and continuity.\n\n"
|
472
489
|
"FORM FLOW RULES:\n"
|
473
|
-
"- Ask exactly one
|
490
|
+
"- Ask exactly one field per turn.\n"
|
491
|
+
"- If any required fields are missing, ask the next missing required field.\n"
|
492
|
+
"- If all required fields are filled but optional fields are missing, ask the next missing optional field.\n"
|
474
493
|
"- Do NOT re-ask or verify values present in Captured User Data (auto-saved, authoritative).\n"
|
475
|
-
"-
|
494
|
+
"- Do NOT provide summaries until no required or optional fields are missing.\n\n"
|
476
495
|
)
|
477
496
|
|
478
497
|
# 8) Generate response
|
@@ -510,7 +529,7 @@ class QueryService(QueryServiceInterface):
|
|
510
529
|
except Exception:
|
511
530
|
pass
|
512
531
|
|
513
|
-
#
|
532
|
+
# Only run final structured output when no required or optional fields are missing
|
514
533
|
if capture_schema and capture_name and form_complete:
|
515
534
|
try:
|
516
535
|
DynamicModel = self._build_model_from_json_schema(
|
@@ -739,5 +758,5 @@ class QueryService(QueryServiceInterface):
|
|
739
758
|
else:
|
740
759
|
fields[field_name] = (typ, default)
|
741
760
|
|
742
|
-
Model = create_model(name, **fields)
|
761
|
+
Model = create_model(name, **fields)
|
743
762
|
return Model
|
@@ -34,10 +34,10 @@ solana_agent/repositories/memory.py,sha256=F46vZ-Uhj7PX2uFGCRKYsZ8JLmKteMN1d30qG
|
|
34
34
|
solana_agent/services/__init__.py,sha256=iko0c2MlF8b_SA_nuBGFllr2E3g_JowOrOzGcnU9tkA,162
|
35
35
|
solana_agent/services/agent.py,sha256=dotuINMtW3TQDLq2eNM5r1cAUwhzxbHBotw8p5CLsYU,20983
|
36
36
|
solana_agent/services/knowledge_base.py,sha256=ZvOPrSmcNDgUzz4bJIQ4LeRl9vMZiK9hOfs71IpB7Bk,32735
|
37
|
-
solana_agent/services/query.py,sha256=
|
37
|
+
solana_agent/services/query.py,sha256=bmam8rvpWFEGg1uVABOqd1X2GRqRNwBu7JCCzZ93iIE,32328
|
38
38
|
solana_agent/services/routing.py,sha256=hsHe8HSGO_xFc0A17WIOGTidLTfLSfApQw3l2HHqkLo,7614
|
39
|
-
solana_agent-31.1.
|
40
|
-
solana_agent-31.1.
|
41
|
-
solana_agent-31.1.
|
42
|
-
solana_agent-31.1.
|
43
|
-
solana_agent-31.1.
|
39
|
+
solana_agent-31.1.2.dist-info/LICENSE,sha256=BnSRc-NSFuyF2s496l_4EyrwAP6YimvxWcjPiJ0J7g4,1057
|
40
|
+
solana_agent-31.1.2.dist-info/METADATA,sha256=ic6JDKmY4bhO78QgX7sjtVV-BVuuxg2KR4sI58hSWP4,30013
|
41
|
+
solana_agent-31.1.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
42
|
+
solana_agent-31.1.2.dist-info/entry_points.txt,sha256=-AuT_mfqk8dlZ0pHuAjx1ouAWpTRjpqvEUa6YV3lmc0,53
|
43
|
+
solana_agent-31.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|