dasein-core 0.2.2__tar.gz → 0.2.3__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.
- {dasein_core-0.2.2/src/dasein_core.egg-info → dasein_core-0.2.3}/PKG-INFO +1 -1
- {dasein_core-0.2.2 → dasein_core-0.2.3}/pyproject.toml +1 -1
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/api.py +54 -3
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/capture.py +151 -4
- {dasein_core-0.2.2 → dasein_core-0.2.3/src/dasein_core.egg-info}/PKG-INFO +1 -1
- {dasein_core-0.2.2 → dasein_core-0.2.3}/LICENSE +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/MANIFEST.in +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/README.md +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/examples/dasein_examples.ipynb +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/setup.cfg +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/setup.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/__init__.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/advice_format.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/config.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/events.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/extractors.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/injection_strategies.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/injector.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/services/__init__.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/services/post_run_client.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/services/pre_run_client.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/services/service_adapter.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/services/service_config.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/trace_buffer.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein/types.py +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein_core.egg-info/SOURCES.txt +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein_core.egg-info/dependency_links.txt +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein_core.egg-info/requires.txt +0 -0
- {dasein_core-0.2.2 → dasein_core-0.2.3}/src/dasein_core.egg-info/top_level.txt +0 -0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "dasein-core"
|
7
|
-
version = "0.2.
|
7
|
+
version = "0.2.3"
|
8
8
|
description = "Universal memory for agentic AI. Attach a brain to any LangChain/LangGraph agent in a single line."
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.8"
|
@@ -2714,8 +2714,9 @@ Follow these rules when planning your actions."""
|
|
2714
2714
|
trace = self._extract_trace_from_langgraph_result(result, extracted_query)
|
2715
2715
|
|
2716
2716
|
if not trace:
|
2717
|
-
|
2718
|
-
|
2717
|
+
# Do not bail out – continue with empty trace so KPIs can still be recorded by the service
|
2718
|
+
print(f"[DASEIN] No trace available for synthesis - continuing with empty trace for KPIs")
|
2719
|
+
trace = []
|
2719
2720
|
|
2720
2721
|
print(f"[DASEIN] Sending trace with {len(trace)} steps to post-run service")
|
2721
2722
|
|
@@ -2773,6 +2774,56 @@ Follow these rules when planning your actions."""
|
|
2773
2774
|
mode_str = "BLOCKING" if wait_for_synthesis else "ASYNC"
|
2774
2775
|
print(f"[DASEIN] Calling post-run service for rule synthesis ({mode_str} mode)")
|
2775
2776
|
|
2777
|
+
# Compute agent fingerprint for post-run (mirror pre-run minimal fingerprint)
|
2778
|
+
def _minimal_agent_fingerprint(agent) -> str:
|
2779
|
+
try:
|
2780
|
+
agent_cls = getattr(agent, '__class__', None)
|
2781
|
+
agent_name = getattr(agent_cls, '__name__', '') if agent_cls else ''
|
2782
|
+
module = getattr(agent, '__module__', '') or ''
|
2783
|
+
framework = module.split('.')[0] if module else ''
|
2784
|
+
model_id = ''
|
2785
|
+
llm = getattr(agent, 'llm', None)
|
2786
|
+
if llm is not None:
|
2787
|
+
model_id = (
|
2788
|
+
getattr(llm, 'model', None)
|
2789
|
+
or getattr(llm, 'model_name', None)
|
2790
|
+
or getattr(llm, 'model_id', None)
|
2791
|
+
or getattr(llm, 'model_tag', None)
|
2792
|
+
or ''
|
2793
|
+
)
|
2794
|
+
tool_names = []
|
2795
|
+
tools_attr = getattr(agent, 'tools', None)
|
2796
|
+
if tools_attr:
|
2797
|
+
try:
|
2798
|
+
for t in tools_attr:
|
2799
|
+
name = getattr(t, 'name', None) or getattr(t, '__name__', None) or getattr(t.__class__, '__name__', '')
|
2800
|
+
if name:
|
2801
|
+
tool_names.append(str(name))
|
2802
|
+
except Exception:
|
2803
|
+
pass
|
2804
|
+
elif getattr(agent, 'toolkit', None):
|
2805
|
+
tk = getattr(agent, 'toolkit')
|
2806
|
+
tk_tools = getattr(tk, 'tools', None) or getattr(tk, 'get_tools', None)
|
2807
|
+
try:
|
2808
|
+
iterable = tk_tools() if callable(tk_tools) else tk_tools
|
2809
|
+
for t in (iterable or []):
|
2810
|
+
name = getattr(t, 'name', None) or getattr(t, '__name__', None) or getattr(t.__class__, '__name__', '')
|
2811
|
+
if name:
|
2812
|
+
tool_names.append(str(name))
|
2813
|
+
except Exception:
|
2814
|
+
pass
|
2815
|
+
norm = lambda s: str(s).strip().lower().replace(' ', '_') if s is not None else ''
|
2816
|
+
agent_name = norm(agent_name)
|
2817
|
+
framework = norm(framework)
|
2818
|
+
model_id = norm(model_id)
|
2819
|
+
tool_names = [norm(n) for n in tool_names if n]
|
2820
|
+
tools_joined = ','.join(sorted(set(tool_names)))
|
2821
|
+
return f"[[FINGERPRINT]] agent={agent_name} | framework={framework} | model={model_id} | tools={tools_joined}"
|
2822
|
+
except Exception:
|
2823
|
+
return getattr(agent, 'agent_id', None) or f"agent_{id(agent)}"
|
2824
|
+
|
2825
|
+
agent_fingerprint = _minimal_agent_fingerprint(self._agent)
|
2826
|
+
|
2776
2827
|
response = self._service_adapter.synthesize_rules(
|
2777
2828
|
run_id=None, # Will use stored run_id from pre-run phase
|
2778
2829
|
trace=cleaned_trace,
|
@@ -2783,7 +2834,7 @@ Follow these rules when planning your actions."""
|
|
2783
2834
|
max_rules=self._top_k, # Configurable via top_k parameter
|
2784
2835
|
performance_tracking_id=self._performance_tracking_id, # For rule isolation
|
2785
2836
|
skip_synthesis=skip_synthesis, # Skip expensive synthesis when not needed
|
2786
|
-
agent_fingerprint=
|
2837
|
+
agent_fingerprint=agent_fingerprint, # Reuse fingerprint from pre-run (line 2613)
|
2787
2838
|
step_id=self._current_step_id, # Pass step_id for parallel execution tracking
|
2788
2839
|
post_run_mode=self._post_run, # Pass post_run mode ("full" or "kpi_only")
|
2789
2840
|
wait_for_synthesis=wait_for_synthesis # Wait for synthesis on retry runs (except last)
|
@@ -405,10 +405,26 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
405
405
|
|
406
406
|
# 🚨 OPTIMIZED: For LangGraph, check if kwargs contains 'invocation_params' with messages
|
407
407
|
# Extract the most recent message instead of full history
|
408
|
+
# Use from_end=True to capture the END of system prompts (where user's actual query is)
|
408
409
|
if 'invocation_params' in kwargs and 'messages' in kwargs['invocation_params']:
|
409
410
|
args_excerpt = self._extract_recent_message({'messages': kwargs['invocation_params']['messages']})
|
410
411
|
else:
|
411
|
-
args_excerpt = self._excerpt(" | ".join(modified_prompts))
|
412
|
+
args_excerpt = self._excerpt(" | ".join(modified_prompts), from_end=True)
|
413
|
+
|
414
|
+
# GNN-related fields
|
415
|
+
step_index = len(self._trace)
|
416
|
+
|
417
|
+
# Track which rules triggered at this step (llm_start rules)
|
418
|
+
rule_triggered_here = []
|
419
|
+
if hasattr(self, '_selected_rules') and self._selected_rules:
|
420
|
+
for rule_meta in self._selected_rules:
|
421
|
+
if isinstance(rule_meta, tuple) and len(rule_meta) == 2:
|
422
|
+
rule_obj, _metadata = rule_meta
|
423
|
+
else:
|
424
|
+
rule_obj = rule_meta
|
425
|
+
target_step_type = getattr(rule_obj, 'target_step_type', '')
|
426
|
+
if target_step_type in ['llm_start', 'chain_start']:
|
427
|
+
rule_triggered_here.append(getattr(rule_obj, 'id', 'unknown'))
|
412
428
|
|
413
429
|
step = {
|
414
430
|
"step_type": "llm_start",
|
@@ -419,6 +435,9 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
419
435
|
"run_id": None,
|
420
436
|
"parent_run_id": None,
|
421
437
|
"node": self._current_chain_node, # LangGraph node name (if available)
|
438
|
+
# GNN step-level fields
|
439
|
+
"step_index": step_index,
|
440
|
+
"rule_triggered_here": rule_triggered_here,
|
422
441
|
}
|
423
442
|
self._trace.append(step)
|
424
443
|
# self._vprint(f"[DASEIN][CALLBACK] Captured llm_start: {len(_TRACE)} total steps") # Commented out - too noisy
|
@@ -583,6 +602,15 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
583
602
|
import traceback
|
584
603
|
traceback.print_exc()
|
585
604
|
|
605
|
+
# GNN-related fields: compute tokens_delta
|
606
|
+
step_index = len(self._trace)
|
607
|
+
tokens_delta = 0
|
608
|
+
# Find previous step with tokens_output to compute delta
|
609
|
+
for prev_step in reversed(self._trace):
|
610
|
+
if 'tokens_output' in prev_step and prev_step['tokens_output'] > 0:
|
611
|
+
tokens_delta = output_tokens - prev_step['tokens_output']
|
612
|
+
break
|
613
|
+
|
586
614
|
step = {
|
587
615
|
"step_type": "llm_end",
|
588
616
|
"tool_name": "",
|
@@ -594,6 +622,9 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
594
622
|
"tokens_input": input_tokens,
|
595
623
|
"tokens_output": output_tokens,
|
596
624
|
"node": self._current_chain_node, # LangGraph node name (if available)
|
625
|
+
# GNN step-level fields
|
626
|
+
"step_index": step_index,
|
627
|
+
"tokens_delta": tokens_delta,
|
597
628
|
}
|
598
629
|
self._trace.append(step)
|
599
630
|
|
@@ -673,6 +704,21 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
673
704
|
|
674
705
|
args_excerpt = self._excerpt(modified_input)
|
675
706
|
|
707
|
+
# GNN-related fields: capture step-level metrics
|
708
|
+
step_index = len(self._trace)
|
709
|
+
tool_input_chars = len(str(input_str))
|
710
|
+
|
711
|
+
# Track which rules triggered at this step
|
712
|
+
rule_triggered_here = []
|
713
|
+
if hasattr(self, '_selected_rules') and self._selected_rules:
|
714
|
+
for rule_meta in self._selected_rules:
|
715
|
+
if isinstance(rule_meta, tuple) and len(rule_meta) == 2:
|
716
|
+
rule_obj, _metadata = rule_meta
|
717
|
+
else:
|
718
|
+
rule_obj = rule_meta
|
719
|
+
if getattr(rule_obj, 'target_step_type', '') == "tool_start":
|
720
|
+
rule_triggered_here.append(getattr(rule_obj, 'id', 'unknown'))
|
721
|
+
|
676
722
|
step = {
|
677
723
|
"step_type": "tool_start",
|
678
724
|
"tool_name": tool_name,
|
@@ -682,6 +728,10 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
682
728
|
"run_id": run_id,
|
683
729
|
"parent_run_id": parent_run_id,
|
684
730
|
"node": self._current_chain_node, # LangGraph node name (if available)
|
731
|
+
# GNN step-level fields
|
732
|
+
"step_index": step_index,
|
733
|
+
"tool_input_chars": tool_input_chars,
|
734
|
+
"rule_triggered_here": rule_triggered_here,
|
685
735
|
}
|
686
736
|
self._trace.append(step)
|
687
737
|
|
@@ -707,6 +757,24 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
707
757
|
# self._vprint(f"[DASEIN][CALLBACK] Output length: {len(output_str)} chars") # Commented out - too noisy
|
708
758
|
# self._vprint(f"[DASEIN][CALLBACK] Outcome length: {len(outcome)} chars") # Commented out - too noisy
|
709
759
|
|
760
|
+
# GNN-related fields: capture tool output metrics
|
761
|
+
step_index = len(self._trace)
|
762
|
+
tool_output_chars = len(output_str)
|
763
|
+
|
764
|
+
# Estimate tool_output_items (heuristic: count lines, or rows if SQL-like)
|
765
|
+
tool_output_items = 0
|
766
|
+
try:
|
767
|
+
# Try to count lines as a proxy for items
|
768
|
+
if output_str:
|
769
|
+
tool_output_items = output_str.count('\n') + 1
|
770
|
+
except:
|
771
|
+
tool_output_items = 0
|
772
|
+
|
773
|
+
# Extract available selectors from DOM-like output (web browse agents)
|
774
|
+
available_selectors = None
|
775
|
+
if tool_name in ['extract_text', 'get_elements', 'extract_hyperlinks', 'extract_content']:
|
776
|
+
available_selectors = self._extract_semantic_selectors(output_str)
|
777
|
+
|
710
778
|
step = {
|
711
779
|
"step_type": "tool_end",
|
712
780
|
"tool_name": tool_name,
|
@@ -716,7 +784,15 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
716
784
|
"run_id": run_id,
|
717
785
|
"parent_run_id": parent_run_id,
|
718
786
|
"node": self._current_chain_node, # LangGraph node name (if available)
|
787
|
+
# GNN step-level fields
|
788
|
+
"step_index": step_index,
|
789
|
+
"tool_output_chars": tool_output_chars,
|
790
|
+
"tool_output_items": tool_output_items,
|
719
791
|
}
|
792
|
+
|
793
|
+
# Add available_selectors only if found (keep trace light)
|
794
|
+
if available_selectors:
|
795
|
+
step["available_selectors"] = available_selectors
|
720
796
|
self._trace.append(step)
|
721
797
|
|
722
798
|
# Clean up the stored tool name
|
@@ -880,12 +956,83 @@ class DaseinCallbackHandler(BaseCallbackHandler):
|
|
880
956
|
# On any error, fall back to original behavior
|
881
957
|
return self._excerpt(str(inputs))
|
882
958
|
|
883
|
-
def _excerpt(self, obj: Any, max_len: int = 250) -> str:
|
884
|
-
"""
|
959
|
+
def _excerpt(self, obj: Any, max_len: int = 250, from_end: bool = False) -> str:
|
960
|
+
"""
|
961
|
+
Truncate text to max_length with ellipsis.
|
962
|
+
|
963
|
+
Args:
|
964
|
+
obj: Object to convert to string and truncate
|
965
|
+
max_len: Maximum length of excerpt
|
966
|
+
from_end: If True, take LAST max_len chars (better for system prompts).
|
967
|
+
If False, take FIRST max_len chars (better for tool args).
|
968
|
+
"""
|
885
969
|
text = str(obj)
|
886
970
|
if len(text) <= max_len:
|
887
971
|
return text
|
888
|
-
|
972
|
+
|
973
|
+
if from_end:
|
974
|
+
# Take last X chars - better for system prompts where the end contains user's actual query
|
975
|
+
return "..." + text[-(max_len-3):]
|
976
|
+
else:
|
977
|
+
# Take first X chars - better for tool inputs
|
978
|
+
return text[:max_len-3] + "..."
|
979
|
+
|
980
|
+
def _extract_semantic_selectors(self, html_text: str) -> List[Dict[str, int]]:
|
981
|
+
"""
|
982
|
+
Extract semantic HTML tags from output for grounding web browse rules.
|
983
|
+
Only extracts semantic tags (nav, header, h1, etc.) to keep trace lightweight.
|
984
|
+
|
985
|
+
Args:
|
986
|
+
html_text: Output text that may contain HTML
|
987
|
+
|
988
|
+
Returns:
|
989
|
+
List of {"tag": str, "count": int} sorted by count descending, or None if no HTML
|
990
|
+
"""
|
991
|
+
import re
|
992
|
+
|
993
|
+
# Quick check: does this look like HTML?
|
994
|
+
if '<' not in html_text or '>' not in html_text:
|
995
|
+
return None
|
996
|
+
|
997
|
+
# Semantic tags we care about (prioritized for web browse agents)
|
998
|
+
semantic_tags = [
|
999
|
+
# Navigation/Structure (highest priority)
|
1000
|
+
'nav', 'header', 'footer', 'main', 'article', 'section', 'aside',
|
1001
|
+
|
1002
|
+
# Headers (critical for "find headers" queries!)
|
1003
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
1004
|
+
|
1005
|
+
# Interactive
|
1006
|
+
'a', 'button', 'form', 'input', 'textarea', 'select', 'label',
|
1007
|
+
|
1008
|
+
# Lists (often used for navigation)
|
1009
|
+
'ul', 'ol', 'li',
|
1010
|
+
|
1011
|
+
# Tables (data extraction)
|
1012
|
+
'table', 'thead', 'tbody', 'tr', 'th', 'td',
|
1013
|
+
|
1014
|
+
# Media
|
1015
|
+
'img', 'video', 'audio'
|
1016
|
+
]
|
1017
|
+
|
1018
|
+
# Count occurrences of each semantic tag
|
1019
|
+
found_tags = {}
|
1020
|
+
for tag in semantic_tags:
|
1021
|
+
# Pattern: <tag ...> or <tag> (opening tags only)
|
1022
|
+
pattern = f'<{tag}[\\s>]'
|
1023
|
+
matches = re.findall(pattern, html_text, re.IGNORECASE)
|
1024
|
+
if matches:
|
1025
|
+
found_tags[tag] = len(matches)
|
1026
|
+
|
1027
|
+
# Return None if no semantic tags found
|
1028
|
+
if not found_tags:
|
1029
|
+
return None
|
1030
|
+
|
1031
|
+
# Convert to list format, sorted by count descending
|
1032
|
+
# Limit to top 15 to keep trace light
|
1033
|
+
result = [{"tag": tag, "count": count}
|
1034
|
+
for tag, count in sorted(found_tags.items(), key=lambda x: -x[1])]
|
1035
|
+
return result[:15] # Top 15 most common tags
|
889
1036
|
|
890
1037
|
def set_selected_rules(self, rules: List[Dict[str, Any]]):
|
891
1038
|
"""Set the rules selected for this run.
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|