agent_os_kernel 3.1.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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
caas/cli.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
"""
|
|
5
|
+
Command-line interface for Context-as-a-Service.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from caas.models import ContentFormat
|
|
13
|
+
from caas.ingestion import ProcessorFactory
|
|
14
|
+
from caas.detection import DocumentTypeDetector, StructureAnalyzer
|
|
15
|
+
from caas.tuning import WeightTuner
|
|
16
|
+
from caas.storage import DocumentStore, ContextExtractor
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""Main CLI entry point."""
|
|
21
|
+
if len(sys.argv) < 2:
|
|
22
|
+
print_usage()
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
command = sys.argv[1]
|
|
26
|
+
|
|
27
|
+
if command == "ingest":
|
|
28
|
+
ingest_command()
|
|
29
|
+
elif command == "analyze":
|
|
30
|
+
analyze_command()
|
|
31
|
+
elif command == "context":
|
|
32
|
+
context_command()
|
|
33
|
+
elif command == "list":
|
|
34
|
+
list_command()
|
|
35
|
+
else:
|
|
36
|
+
print(f"Unknown command: {command}")
|
|
37
|
+
print_usage()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def print_usage():
|
|
41
|
+
"""Print CLI usage information."""
|
|
42
|
+
print("""
|
|
43
|
+
Context-as-a-Service CLI
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
caas ingest <file> <format> [title] Ingest a document
|
|
47
|
+
caas analyze <document_id> Analyze a document
|
|
48
|
+
caas context <document_id> [query] Extract context
|
|
49
|
+
caas list List all documents
|
|
50
|
+
|
|
51
|
+
Formats: pdf, html, code
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
caas ingest contract.pdf pdf "Employment Contract"
|
|
55
|
+
caas analyze abc-123
|
|
56
|
+
caas context abc-123 "termination clause"
|
|
57
|
+
""")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def ingest_command():
|
|
61
|
+
"""Handle ingest command."""
|
|
62
|
+
if len(sys.argv) < 4:
|
|
63
|
+
print("Usage: caas ingest <file> <format> [title]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
file_path = Path(sys.argv[2])
|
|
67
|
+
format_str = sys.argv[3]
|
|
68
|
+
title = sys.argv[4] if len(sys.argv) > 4 else file_path.stem
|
|
69
|
+
|
|
70
|
+
if not file_path.exists():
|
|
71
|
+
print(f"Error: File not found: {file_path}")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# Map format string to enum
|
|
75
|
+
format_map = {
|
|
76
|
+
"pdf": ContentFormat.PDF,
|
|
77
|
+
"html": ContentFormat.HTML,
|
|
78
|
+
"code": ContentFormat.CODE,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
format_enum = format_map.get(format_str.lower())
|
|
82
|
+
if not format_enum:
|
|
83
|
+
print(f"Error: Invalid format: {format_str}")
|
|
84
|
+
print("Valid formats: pdf, html, code")
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
# Process document
|
|
88
|
+
print(f"Ingesting {file_path}...")
|
|
89
|
+
|
|
90
|
+
content = file_path.read_bytes()
|
|
91
|
+
processor = ProcessorFactory.get_processor(format_enum)
|
|
92
|
+
|
|
93
|
+
import uuid
|
|
94
|
+
doc_id = str(uuid.uuid4())
|
|
95
|
+
|
|
96
|
+
metadata = {
|
|
97
|
+
"id": doc_id,
|
|
98
|
+
"title": title,
|
|
99
|
+
"filename": file_path.name,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
document = processor.process(content, metadata)
|
|
103
|
+
|
|
104
|
+
# Auto-detect and tune
|
|
105
|
+
detector = DocumentTypeDetector()
|
|
106
|
+
document.detected_type = detector.detect(document)
|
|
107
|
+
|
|
108
|
+
tuner = WeightTuner()
|
|
109
|
+
document = tuner.tune(document)
|
|
110
|
+
|
|
111
|
+
# Store
|
|
112
|
+
store = DocumentStore("caas_data.json")
|
|
113
|
+
store.add(document)
|
|
114
|
+
|
|
115
|
+
print(f"\n✓ Document ingested successfully!")
|
|
116
|
+
print(f" ID: {document.id}")
|
|
117
|
+
print(f" Title: {document.title}")
|
|
118
|
+
print(f" Detected Type: {document.detected_type}")
|
|
119
|
+
print(f" Sections: {len(document.sections)}")
|
|
120
|
+
print(f"\nTop weighted sections:")
|
|
121
|
+
sorted_sections = sorted(document.sections, key=lambda s: s.weight, reverse=True)
|
|
122
|
+
for section in sorted_sections[:5]:
|
|
123
|
+
print(f" - {section.title}: {section.weight}x")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def analyze_command():
|
|
127
|
+
"""Handle analyze command."""
|
|
128
|
+
if len(sys.argv) < 3:
|
|
129
|
+
print("Usage: caas analyze <document_id>")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
doc_id = sys.argv[2]
|
|
133
|
+
|
|
134
|
+
store = DocumentStore("caas_data.json")
|
|
135
|
+
document = store.get(doc_id)
|
|
136
|
+
|
|
137
|
+
if not document:
|
|
138
|
+
print(f"Error: Document not found: {doc_id}")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
analyzer = StructureAnalyzer()
|
|
142
|
+
detector = DocumentTypeDetector()
|
|
143
|
+
|
|
144
|
+
structure = detector.detect_structure(document)
|
|
145
|
+
analysis = analyzer.analyze(document)
|
|
146
|
+
|
|
147
|
+
print(f"\n=== Document Analysis ===")
|
|
148
|
+
print(f"ID: {document.id}")
|
|
149
|
+
print(f"Title: {document.title}")
|
|
150
|
+
print(f"Type: {document.detected_type}")
|
|
151
|
+
print(f"\nStructure:")
|
|
152
|
+
print(f" Sections: {structure['section_count']}")
|
|
153
|
+
print(f" Clear Structure: {'Yes' if structure['has_clear_sections'] else 'No'}")
|
|
154
|
+
print(f" Quality: {analysis['structure_quality']}")
|
|
155
|
+
print(f"\nKey Sections:")
|
|
156
|
+
for section in structure['key_sections']:
|
|
157
|
+
print(f" - {section}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def context_command():
|
|
161
|
+
"""Handle context command."""
|
|
162
|
+
if len(sys.argv) < 3:
|
|
163
|
+
print("Usage: caas context <document_id> [query]")
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
doc_id = sys.argv[2]
|
|
167
|
+
query = sys.argv[3] if len(sys.argv) > 3 else ""
|
|
168
|
+
|
|
169
|
+
store = DocumentStore("caas_data.json")
|
|
170
|
+
document = store.get(doc_id)
|
|
171
|
+
|
|
172
|
+
if not document:
|
|
173
|
+
print(f"Error: Document not found: {doc_id}")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
extractor = ContextExtractor(store)
|
|
177
|
+
context, metadata = extractor.extract_context(doc_id, query, max_tokens=500)
|
|
178
|
+
|
|
179
|
+
print(f"\n=== Context Extraction ===")
|
|
180
|
+
print(f"Document: {document.title}")
|
|
181
|
+
print(f"Type: {document.detected_type}")
|
|
182
|
+
if query:
|
|
183
|
+
print(f"Query: {query}")
|
|
184
|
+
print(f"\nSections used: {len(metadata['sections_used'])}/{metadata['total_sections']}")
|
|
185
|
+
for section in metadata['sections_used']:
|
|
186
|
+
print(f" - {section}")
|
|
187
|
+
print(f"\n--- Context ---")
|
|
188
|
+
print(context[:1000] + ("..." if len(context) > 1000 else ""))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def list_command():
|
|
192
|
+
"""Handle list command."""
|
|
193
|
+
store = DocumentStore("caas_data.json")
|
|
194
|
+
documents = store.list_all()
|
|
195
|
+
|
|
196
|
+
if not documents:
|
|
197
|
+
print("No documents found.")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
print(f"\n=== Documents ({len(documents)}) ===")
|
|
201
|
+
for doc in documents:
|
|
202
|
+
print(f"\n{doc.id}")
|
|
203
|
+
print(f" Title: {doc.title}")
|
|
204
|
+
print(f" Type: {doc.detected_type}")
|
|
205
|
+
print(f" Format: {doc.format}")
|
|
206
|
+
print(f" Sections: {len(doc.sections)}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
caas/conversation.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Conversation Manager for Sliding Window (FIFO) history management.
|
|
5
|
+
|
|
6
|
+
The Brutal Squeeze Philosophy:
|
|
7
|
+
"Chopping > Summarizing"
|
|
8
|
+
|
|
9
|
+
Instead of asking an AI to summarize conversation history (which costs money and loses nuance),
|
|
10
|
+
we use a brutal "Sliding Window" approach:
|
|
11
|
+
- Keep the last 10 turns perfectly intact
|
|
12
|
+
- Delete turn 11 (FIFO - First In First Out)
|
|
13
|
+
- No summarization = No lossy compression
|
|
14
|
+
- Recent Precision over Vague History
|
|
15
|
+
|
|
16
|
+
Why this works:
|
|
17
|
+
- Users rarely refer back to what they said 20 minutes ago
|
|
18
|
+
- They constantly refer to the exact code snippet they pasted 30 seconds ago
|
|
19
|
+
- Summary = Lossy Compression (loses specific error codes, exact wording)
|
|
20
|
+
- Chopping = Lossless Compression (of the recent past)
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
Turn 1: "I tried X and it failed with error code 500"
|
|
24
|
+
With Summarization: "User attempted troubleshooting" (ERROR CODE LOST!)
|
|
25
|
+
With Chopping: After 10 new turns, this is deleted entirely
|
|
26
|
+
But turns 2-11 are perfectly intact with all details
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from typing import Dict, List, Optional, Any, Union
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
import uuid
|
|
32
|
+
import logging
|
|
33
|
+
|
|
34
|
+
from caas.models import ConversationTurn, ConversationState
|
|
35
|
+
|
|
36
|
+
# Set up logger
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConversationManager:
|
|
41
|
+
"""
|
|
42
|
+
Manages conversation history using a Sliding Window (FIFO) approach.
|
|
43
|
+
|
|
44
|
+
The Sliding Window ensures:
|
|
45
|
+
1. Recent precision: Last N turns are perfectly intact
|
|
46
|
+
2. Zero summarization cost: No AI calls needed
|
|
47
|
+
3. No information loss: What's kept is lossless
|
|
48
|
+
4. Predictable behavior: Always know what's in context
|
|
49
|
+
|
|
50
|
+
Philosophy:
|
|
51
|
+
In a frugal architecture, we value Recent Precision over Vague History.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, max_turns: int = 10):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the conversation manager.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
max_turns: Maximum number of turns to keep (default: 10)
|
|
60
|
+
"""
|
|
61
|
+
self.state = ConversationState(max_turns=max_turns)
|
|
62
|
+
|
|
63
|
+
def add_turn(
|
|
64
|
+
self,
|
|
65
|
+
user_message: str,
|
|
66
|
+
ai_response: Optional[str] = None,
|
|
67
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
68
|
+
) -> str:
|
|
69
|
+
"""
|
|
70
|
+
Add a conversation turn to the history.
|
|
71
|
+
|
|
72
|
+
This implements the FIFO Sliding Window:
|
|
73
|
+
- If we have < max_turns, just add the turn
|
|
74
|
+
- If we have = max_turns, delete the oldest turn and add the new one
|
|
75
|
+
- No summarization, no compression, just brutal chopping
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
user_message: The user's message
|
|
79
|
+
ai_response: The AI's response (optional)
|
|
80
|
+
metadata: Optional metadata for the turn
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The ID of the created turn
|
|
84
|
+
"""
|
|
85
|
+
turn_id = str(uuid.uuid4())
|
|
86
|
+
turn = ConversationTurn(
|
|
87
|
+
id=turn_id,
|
|
88
|
+
user_message=user_message,
|
|
89
|
+
ai_response=ai_response,
|
|
90
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
91
|
+
metadata=metadata or {}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# FIFO: If we're at max capacity, delete the oldest turn
|
|
95
|
+
if len(self.state.turns) >= self.state.max_turns:
|
|
96
|
+
deleted_turn = self.state.turns.pop(0) # Remove first (oldest) turn
|
|
97
|
+
logger.debug(f"FIFO: Deleted oldest turn (ID: {deleted_turn.id[:8]}...) to make room")
|
|
98
|
+
|
|
99
|
+
# Add the new turn at the end
|
|
100
|
+
self.state.turns.append(turn)
|
|
101
|
+
self.state.total_turns_ever += 1
|
|
102
|
+
|
|
103
|
+
return turn_id
|
|
104
|
+
|
|
105
|
+
def update_turn_response(self, turn_id: str, ai_response: str) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Update the AI response for a specific turn.
|
|
108
|
+
|
|
109
|
+
Useful when you add a turn with just the user message
|
|
110
|
+
and want to update it with the AI response later.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
turn_id: The ID of the turn to update
|
|
114
|
+
ai_response: The AI response to add
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
True if turn was found and updated, False otherwise
|
|
118
|
+
"""
|
|
119
|
+
for turn in self.state.turns:
|
|
120
|
+
if turn.id == turn_id:
|
|
121
|
+
turn.ai_response = ai_response
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
def get_conversation_history(
|
|
126
|
+
self,
|
|
127
|
+
include_metadata: bool = False,
|
|
128
|
+
format_as_text: bool = True
|
|
129
|
+
) -> Union[List[ConversationTurn], str]:
|
|
130
|
+
"""
|
|
131
|
+
Get the conversation history (last N turns).
|
|
132
|
+
|
|
133
|
+
Returns the history in FIFO order (oldest to newest).
|
|
134
|
+
All turns are perfectly intact - no summarization, no loss.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
include_metadata: Whether to include metadata in text format
|
|
138
|
+
format_as_text: If True, return formatted text; if False, return list of turns
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Formatted conversation history or list of turns
|
|
142
|
+
"""
|
|
143
|
+
if not format_as_text:
|
|
144
|
+
return self.state.turns
|
|
145
|
+
|
|
146
|
+
if not self.state.turns:
|
|
147
|
+
return "No conversation history."
|
|
148
|
+
|
|
149
|
+
# Format as text
|
|
150
|
+
parts = ["# Conversation History (Sliding Window)\n"]
|
|
151
|
+
parts.append(f"_Keeping last {self.state.max_turns} turns intact (no summarization)_\n")
|
|
152
|
+
parts.append(f"_Total turns ever: {self.state.total_turns_ever}_\n\n")
|
|
153
|
+
|
|
154
|
+
for i, turn in enumerate(self.state.turns, 1):
|
|
155
|
+
parts.append(f"## Turn {i}\n")
|
|
156
|
+
parts.append(f"**User**: {turn.user_message}\n")
|
|
157
|
+
if turn.ai_response:
|
|
158
|
+
parts.append(f"**AI**: {turn.ai_response}\n")
|
|
159
|
+
if include_metadata and turn.metadata:
|
|
160
|
+
parts.append(f"_Metadata: {turn.metadata}_\n")
|
|
161
|
+
parts.append("\n")
|
|
162
|
+
|
|
163
|
+
return "".join(parts)
|
|
164
|
+
|
|
165
|
+
def get_recent_turns(self, n: int = 5) -> List[ConversationTurn]:
|
|
166
|
+
"""
|
|
167
|
+
Get the N most recent turns.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
n: Number of recent turns to get
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of recent turns (newest last)
|
|
174
|
+
"""
|
|
175
|
+
return self.state.turns[-n:] if len(self.state.turns) > n else self.state.turns
|
|
176
|
+
|
|
177
|
+
def clear_conversation(self):
|
|
178
|
+
"""Clear all conversation history."""
|
|
179
|
+
self.state.turns = []
|
|
180
|
+
|
|
181
|
+
def get_state(self) -> ConversationState:
|
|
182
|
+
"""
|
|
183
|
+
Get the current conversation state.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
The current conversation state
|
|
187
|
+
"""
|
|
188
|
+
return self.state
|
|
189
|
+
|
|
190
|
+
def set_state(self, state: ConversationState):
|
|
191
|
+
"""
|
|
192
|
+
Set the conversation state.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
state: The new conversation state
|
|
196
|
+
"""
|
|
197
|
+
self.state = state
|
|
198
|
+
|
|
199
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
200
|
+
"""
|
|
201
|
+
Get statistics about the conversation.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Dictionary with statistics
|
|
205
|
+
"""
|
|
206
|
+
if not self.state.turns:
|
|
207
|
+
return {
|
|
208
|
+
"current_turns": 0,
|
|
209
|
+
"max_turns": self.state.max_turns,
|
|
210
|
+
"total_turns_ever": self.state.total_turns_ever,
|
|
211
|
+
"deleted_turns": self.state.total_turns_ever,
|
|
212
|
+
"oldest_turn": None,
|
|
213
|
+
"newest_turn": None,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
"current_turns": len(self.state.turns),
|
|
218
|
+
"max_turns": self.state.max_turns,
|
|
219
|
+
"total_turns_ever": self.state.total_turns_ever,
|
|
220
|
+
"deleted_turns": self.state.total_turns_ever - len(self.state.turns),
|
|
221
|
+
"oldest_turn": self.state.turns[0].timestamp if self.state.turns else None,
|
|
222
|
+
"newest_turn": self.state.turns[-1].timestamp if self.state.turns else None,
|
|
223
|
+
}
|
caas/decay.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
# Public Preview — basic context/memory management
|
|
4
|
+
"""
|
|
5
|
+
Simple TTL-based decay for document relevance scoring.
|
|
6
|
+
|
|
7
|
+
Returns 1.0 if within TTL, 0.0 if expired, with linear interpolation between.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Default TTL in days
|
|
14
|
+
_DEFAULT_TTL_DAYS = 30.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def calculate_decay_factor(
|
|
18
|
+
ingestion_timestamp: Optional[str],
|
|
19
|
+
reference_time: Optional[datetime] = None,
|
|
20
|
+
decay_rate: float = 1.0,
|
|
21
|
+
) -> float:
|
|
22
|
+
"""
|
|
23
|
+
Calculate a simple TTL-based decay factor.
|
|
24
|
+
|
|
25
|
+
Returns 1.0 when the document is brand-new and linearly decays to 0.0
|
|
26
|
+
at ``_DEFAULT_TTL_DAYS / decay_rate`` days old.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
ingestion_timestamp: ISO-format timestamp of document ingestion.
|
|
30
|
+
reference_time: Time to measure from (defaults to now).
|
|
31
|
+
decay_rate: Higher values shorten the effective TTL.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Decay factor between 0.0 and 1.0.
|
|
35
|
+
"""
|
|
36
|
+
if not ingestion_timestamp:
|
|
37
|
+
return 0.0
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
ingestion_dt = datetime.fromisoformat(
|
|
41
|
+
ingestion_timestamp.replace("Z", "+00:00")
|
|
42
|
+
)
|
|
43
|
+
if reference_time is None:
|
|
44
|
+
reference_time = datetime.now(timezone.utc)
|
|
45
|
+
|
|
46
|
+
days_elapsed = max(
|
|
47
|
+
0.0, (reference_time - ingestion_dt).total_seconds() / 86400.0
|
|
48
|
+
)
|
|
49
|
+
ttl = _DEFAULT_TTL_DAYS / max(decay_rate, 0.001)
|
|
50
|
+
if days_elapsed >= ttl:
|
|
51
|
+
return 0.0
|
|
52
|
+
return 1.0 - (days_elapsed / ttl)
|
|
53
|
+
except (ValueError, AttributeError, TypeError):
|
|
54
|
+
return 0.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def apply_decay_to_score(base_score: float, decay_factor: float) -> float:
|
|
58
|
+
"""Apply *decay_factor* to *base_score*."""
|
|
59
|
+
return base_score * decay_factor
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_time_weighted_score(
|
|
63
|
+
base_score: float,
|
|
64
|
+
ingestion_timestamp: Optional[str],
|
|
65
|
+
reference_time: Optional[datetime] = None,
|
|
66
|
+
decay_rate: float = 1.0,
|
|
67
|
+
) -> float:
|
|
68
|
+
"""Convenience: compute time-weighted score in one call."""
|
|
69
|
+
return apply_decay_to_score(
|
|
70
|
+
base_score,
|
|
71
|
+
calculate_decay_factor(ingestion_timestamp, reference_time, decay_rate),
|
|
72
|
+
)
|