jaf-py 2.5.2__tar.gz → 2.5.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.
- {jaf_py-2.5.2/jaf_py.egg-info → jaf_py-2.5.3}/PKG-INFO +4 -4
- {jaf_py-2.5.2 → jaf_py-2.5.3}/README.md +1 -1
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/__init__.py +1 -1
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/tracing.py +196 -59
- {jaf_py-2.5.2 → jaf_py-2.5.3/jaf_py.egg-info}/PKG-INFO +4 -4
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf_py.egg-info/requires.txt +2 -2
- {jaf_py-2.5.2 → jaf_py-2.5.3}/pyproject.toml +3 -3
- {jaf_py-2.5.2 → jaf_py-2.5.3}/LICENSE +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/agent.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/agent_card.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/client.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/client_example.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/integration_example.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/rag_demo/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/server_demo/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/examples/server_example.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/cleanup.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/factory.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/providers/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/providers/composite.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/providers/in_memory.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/providers/postgres.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/providers/redis.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/serialization.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/run_comprehensive_tests.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/test_cleanup.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/test_serialization.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/test_stress_concurrency.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/tests/test_task_lifecycle.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/memory/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/protocol.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/server.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/standalone_client.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/run_tests.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/test_agent.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/test_client.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/test_integration.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/test_protocol.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/tests/test_types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/a2a/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/cli.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/agent_tool.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/analytics.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/composition.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/engine.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/errors.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/guardrails.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/handoff.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/parallel_agents.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/performance.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/proxy.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/proxy_helpers.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/state.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/streaming.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/tool_results.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/tools.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/core/workflows.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/exceptions.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/approval_storage.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/factory.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/providers/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/providers/in_memory.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/providers/postgres.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/providers/redis.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/memory/utils.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/plugins/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/plugins/base.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/policies/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/policies/handoff.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/policies/validation.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/providers/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/providers/mcp.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/providers/model.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/server/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/server/main.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/server/server.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/server/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/utils/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/utils/attachments.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/utils/document_processor.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/__init__.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/example.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/functional_core.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/graphviz.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/imperative_shell.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf/visualization/types.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf_py.egg-info/SOURCES.txt +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf_py.egg-info/dependency_links.txt +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf_py.egg-info/entry_points.txt +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/jaf_py.egg-info/top_level.txt +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/setup.cfg +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/setup.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_a2a_deep.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_a2a_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_api_reference_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_attachments.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_callback_system_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_coffee_tool.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_conversation_id_fix.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_deployment_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_docs_code_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_engine.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_engine_manual.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_error_handling_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_getting_started_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_manual.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_math_tool.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_mcp_comprehensive.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_mcp_docs.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_mcp_real_functionality.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_mcp_transports.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_memory_system_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_model_providers_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_property_based.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_proxy_simple.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_redis_fixes.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_redis_memory.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_server_api_examples.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_session_continuity.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_streamable_http_mcp_example.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_timeout_functionality.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_tool_integration.py +0 -0
- {jaf_py-2.5.2 → jaf_py-2.5.3}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
|
|
5
5
|
Author: JAF Contributors
|
|
6
6
|
Maintainer: JAF Contributors
|
|
@@ -42,13 +42,13 @@ Requires-Dist: fastmcp>=0.1.0
|
|
|
42
42
|
Requires-Dist: opentelemetry-api>=1.22.0
|
|
43
43
|
Requires-Dist: opentelemetry-sdk>=1.22.0
|
|
44
44
|
Requires-Dist: opentelemetry-exporter-otlp>=1.22.0
|
|
45
|
-
Requires-Dist: langfuse<
|
|
45
|
+
Requires-Dist: langfuse<3.0.0
|
|
46
46
|
Requires-Dist: litellm>=1.76.3
|
|
47
47
|
Provides-Extra: tracing
|
|
48
48
|
Requires-Dist: opentelemetry-api>=1.22.0; extra == "tracing"
|
|
49
49
|
Requires-Dist: opentelemetry-sdk>=1.22.0; extra == "tracing"
|
|
50
50
|
Requires-Dist: opentelemetry-exporter-otlp>=1.22.0; extra == "tracing"
|
|
51
|
-
Requires-Dist: langfuse<
|
|
51
|
+
Requires-Dist: langfuse<3.0.0; extra == "tracing"
|
|
52
52
|
Provides-Extra: attachments
|
|
53
53
|
Requires-Dist: PyPDF2>=3.0.0; extra == "attachments"
|
|
54
54
|
Requires-Dist: python-docx>=1.1.0; extra == "attachments"
|
|
@@ -82,7 +82,7 @@ Dynamic: license-file
|
|
|
82
82
|
|
|
83
83
|
<!--  -->
|
|
84
84
|
|
|
85
|
-
[](https://github.com/xynehq/jaf-py)
|
|
86
86
|
[](https://www.python.org/)
|
|
87
87
|
[](https://xynehq.github.io/jaf-py/)
|
|
88
88
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!--  -->
|
|
4
4
|
|
|
5
|
-
[](https://github.com/xynehq/jaf-py)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
7
|
[](https://xynehq.github.io/jaf-py/)
|
|
8
8
|
|
|
@@ -191,7 +191,7 @@ def generate_run_id() -> RunId:
|
|
|
191
191
|
"""Generate a new run ID."""
|
|
192
192
|
return create_run_id(str(uuid.uuid4()))
|
|
193
193
|
|
|
194
|
-
__version__ = "2.5.
|
|
194
|
+
__version__ = "2.5.3"
|
|
195
195
|
__all__ = [
|
|
196
196
|
# Core types and functions
|
|
197
197
|
"TraceId", "RunId", "ValidationResult", "Message", "ModelConfig",
|
|
@@ -443,10 +443,18 @@ class LangfuseTraceCollector:
|
|
|
443
443
|
public_key=public_key,
|
|
444
444
|
secret_key=secret_key,
|
|
445
445
|
host=host,
|
|
446
|
-
release="jaf-py-v2.5.
|
|
446
|
+
release="jaf-py-v2.5.3",
|
|
447
447
|
httpx_client=client
|
|
448
448
|
)
|
|
449
449
|
self._httpx_client = client
|
|
450
|
+
|
|
451
|
+
# Detect Langfuse version (v2 has trace() method, v3 does not)
|
|
452
|
+
self._is_langfuse_v3 = not hasattr(self.langfuse, 'trace')
|
|
453
|
+
if self._is_langfuse_v3:
|
|
454
|
+
print("[LANGFUSE] Detected Langfuse v3.x - using OpenTelemetry-based API")
|
|
455
|
+
else:
|
|
456
|
+
print("[LANGFUSE] Detected Langfuse v2.x - using legacy API")
|
|
457
|
+
|
|
450
458
|
self.active_spans: Dict[str, Any] = {}
|
|
451
459
|
self.trace_spans: Dict[TraceId, Any] = {}
|
|
452
460
|
# Track tool calls and results for each trace
|
|
@@ -465,6 +473,113 @@ class LangfuseTraceCollector:
|
|
|
465
473
|
except Exception as e:
|
|
466
474
|
print(f"[LANGFUSE] Warning: Failed to close httpx client: {e}")
|
|
467
475
|
|
|
476
|
+
def _get_event_data(self, event: TraceEvent, key: str, default: Any = None) -> Any:
|
|
477
|
+
"""Extract data from event, handling both dict and dataclass."""
|
|
478
|
+
if not hasattr(event, 'data'):
|
|
479
|
+
return default
|
|
480
|
+
|
|
481
|
+
# Handle dict
|
|
482
|
+
if isinstance(event.data, dict):
|
|
483
|
+
return event.data.get(key, default)
|
|
484
|
+
|
|
485
|
+
# Handle dataclass/object with attributes
|
|
486
|
+
return getattr(event.data, key, default)
|
|
487
|
+
|
|
488
|
+
def _create_trace(self, trace_id: TraceId, **kwargs) -> Any:
|
|
489
|
+
"""Create a trace using the appropriate API for the Langfuse version."""
|
|
490
|
+
if self._is_langfuse_v3:
|
|
491
|
+
# Langfuse v3: Use start_span() to create a root span (creates trace implicitly)
|
|
492
|
+
# Extract parameters for v3 API
|
|
493
|
+
name = kwargs.get('name', 'trace')
|
|
494
|
+
input_data = kwargs.get('input')
|
|
495
|
+
metadata = kwargs.get('metadata', {})
|
|
496
|
+
user_id = kwargs.get('user_id')
|
|
497
|
+
session_id = kwargs.get('session_id')
|
|
498
|
+
tags = kwargs.get('tags', [])
|
|
499
|
+
|
|
500
|
+
# Add user_id, session_id, and tags to metadata for v3
|
|
501
|
+
if user_id:
|
|
502
|
+
metadata['user_id'] = user_id
|
|
503
|
+
if session_id:
|
|
504
|
+
metadata['session_id'] = session_id
|
|
505
|
+
if tags:
|
|
506
|
+
metadata['tags'] = tags
|
|
507
|
+
|
|
508
|
+
# Create root span
|
|
509
|
+
trace = self.langfuse.start_span(
|
|
510
|
+
name=name,
|
|
511
|
+
input=input_data,
|
|
512
|
+
metadata=metadata
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Update trace properties using update_trace()
|
|
516
|
+
update_params = {}
|
|
517
|
+
if user_id:
|
|
518
|
+
update_params['user_id'] = user_id
|
|
519
|
+
if session_id:
|
|
520
|
+
update_params['session_id'] = session_id
|
|
521
|
+
if tags:
|
|
522
|
+
update_params['tags'] = tags
|
|
523
|
+
|
|
524
|
+
if update_params:
|
|
525
|
+
trace.update_trace(**update_params)
|
|
526
|
+
|
|
527
|
+
return trace
|
|
528
|
+
else:
|
|
529
|
+
# Langfuse v2: Use trace() method
|
|
530
|
+
return self.langfuse.trace(**kwargs)
|
|
531
|
+
|
|
532
|
+
def _create_generation(self, parent_span: Any, **kwargs) -> Any:
|
|
533
|
+
"""Create a generation using the appropriate API for the Langfuse version."""
|
|
534
|
+
if self._is_langfuse_v3:
|
|
535
|
+
# Langfuse v3: Use start_generation() method
|
|
536
|
+
return parent_span.start_generation(**kwargs)
|
|
537
|
+
else:
|
|
538
|
+
# Langfuse v2: Use generation() method
|
|
539
|
+
return parent_span.generation(**kwargs)
|
|
540
|
+
|
|
541
|
+
def _create_span(self, parent_span: Any, **kwargs) -> Any:
|
|
542
|
+
"""Create a span using the appropriate API for the Langfuse version."""
|
|
543
|
+
if self._is_langfuse_v3:
|
|
544
|
+
# Langfuse v3: Use start_span() method
|
|
545
|
+
return parent_span.start_span(**kwargs)
|
|
546
|
+
else:
|
|
547
|
+
# Langfuse v2: Use span() method
|
|
548
|
+
return parent_span.span(**kwargs)
|
|
549
|
+
|
|
550
|
+
def _create_event(self, parent_span: Any, **kwargs) -> Any:
|
|
551
|
+
"""Create an event using the appropriate API for the Langfuse version."""
|
|
552
|
+
if self._is_langfuse_v3:
|
|
553
|
+
# Langfuse v3: Use create_event() method
|
|
554
|
+
return parent_span.create_event(**kwargs)
|
|
555
|
+
else:
|
|
556
|
+
# Langfuse v2: Use event() method
|
|
557
|
+
return parent_span.event(**kwargs)
|
|
558
|
+
|
|
559
|
+
def _end_span(self, span: Any, **kwargs) -> None:
|
|
560
|
+
"""End a span/generation using the appropriate API for the Langfuse version."""
|
|
561
|
+
if self._is_langfuse_v3:
|
|
562
|
+
# Langfuse v3: Call update() first with output/metadata, then end()
|
|
563
|
+
update_params = {}
|
|
564
|
+
end_params = {}
|
|
565
|
+
|
|
566
|
+
# Separate parameters for update() vs end()
|
|
567
|
+
for key, value in kwargs.items():
|
|
568
|
+
if key in ['output', 'metadata', 'model', 'usage']:
|
|
569
|
+
update_params[key] = value
|
|
570
|
+
elif key == 'end_time':
|
|
571
|
+
end_params[key] = value
|
|
572
|
+
|
|
573
|
+
# Update first if there are parameters
|
|
574
|
+
if update_params:
|
|
575
|
+
span.update(**update_params)
|
|
576
|
+
|
|
577
|
+
# Then end
|
|
578
|
+
span.end(**end_params)
|
|
579
|
+
else:
|
|
580
|
+
# Langfuse v2: Call end() directly with all parameters
|
|
581
|
+
span.end(**kwargs)
|
|
582
|
+
|
|
468
583
|
def collect(self, event: TraceEvent) -> None:
|
|
469
584
|
"""Collect a trace event and send it to Langfuse."""
|
|
470
585
|
try:
|
|
@@ -489,15 +604,15 @@ class LangfuseTraceCollector:
|
|
|
489
604
|
conversation_history = []
|
|
490
605
|
|
|
491
606
|
# Debug: Print the event data structure to understand what we're working with
|
|
492
|
-
if
|
|
493
|
-
context = event
|
|
607
|
+
if self._get_event_data(event, "context"):
|
|
608
|
+
context = self._get_event_data(event, "context")
|
|
494
609
|
print(f"[LANGFUSE DEBUG] Context type: {type(context)}")
|
|
495
610
|
print(f"[LANGFUSE DEBUG] Context attributes: {dir(context) if hasattr(context, '__dict__') else 'Not an object'}")
|
|
496
611
|
if hasattr(context, '__dict__'):
|
|
497
612
|
print(f"[LANGFUSE DEBUG] Context dict: {context.__dict__}")
|
|
498
613
|
|
|
499
614
|
# Try to extract from context first
|
|
500
|
-
context =
|
|
615
|
+
context = self._get_event_data(event, "context")
|
|
501
616
|
if context:
|
|
502
617
|
# Try direct attribute access
|
|
503
618
|
if hasattr(context, 'query'):
|
|
@@ -527,7 +642,7 @@ class LangfuseTraceCollector:
|
|
|
527
642
|
print(f"[LANGFUSE DEBUG] Extracted user_id from attr: {user_id}")
|
|
528
643
|
|
|
529
644
|
# Extract conversation history and current user query from messages
|
|
530
|
-
messages =
|
|
645
|
+
messages = self._get_event_data(event, "messages", [])
|
|
531
646
|
if messages:
|
|
532
647
|
print(f"[LANGFUSE DEBUG] Processing {len(messages)} messages")
|
|
533
648
|
|
|
@@ -619,20 +734,22 @@ class LangfuseTraceCollector:
|
|
|
619
734
|
trace_input = {
|
|
620
735
|
"user_query": user_query,
|
|
621
736
|
"run_id": str(trace_id),
|
|
622
|
-
"agent_name":
|
|
737
|
+
"agent_name": self._get_event_data(event, "agent_name", "analytics_agent_jaf"),
|
|
623
738
|
"session_info": {
|
|
624
|
-
"session_id":
|
|
625
|
-
"user_id": user_id or
|
|
739
|
+
"session_id": self._get_event_data(event, "session_id"),
|
|
740
|
+
"user_id": user_id or self._get_event_data(event, "user_id")
|
|
626
741
|
}
|
|
627
742
|
}
|
|
628
743
|
|
|
629
744
|
# Extract agent_name for tagging
|
|
630
|
-
agent_name =
|
|
745
|
+
agent_name = self._get_event_data(event, "agent_name") or "analytics_agent_jaf"
|
|
631
746
|
|
|
632
|
-
|
|
747
|
+
# Use compatibility layer to create trace (works with both v2 and v3)
|
|
748
|
+
trace = self._create_trace(
|
|
749
|
+
trace_id=trace_id,
|
|
633
750
|
name=agent_name,
|
|
634
|
-
user_id=user_id or
|
|
635
|
-
session_id=
|
|
751
|
+
user_id=user_id or self._get_event_data(event, "user_id"),
|
|
752
|
+
session_id=self._get_event_data(event, "session_id"),
|
|
636
753
|
input=trace_input,
|
|
637
754
|
tags=[agent_name], # Add agent_name as a tag for dashboard filtering
|
|
638
755
|
metadata={
|
|
@@ -640,17 +757,17 @@ class LangfuseTraceCollector:
|
|
|
640
757
|
"event_type": "run_start",
|
|
641
758
|
"trace_id": str(trace_id),
|
|
642
759
|
"user_query": user_query,
|
|
643
|
-
"user_id": user_id or
|
|
760
|
+
"user_id": user_id or self._get_event_data(event, "user_id"),
|
|
644
761
|
"agent_name": agent_name,
|
|
645
762
|
"conversation_history": conversation_history,
|
|
646
763
|
"tool_calls": [],
|
|
647
764
|
"tool_results": [],
|
|
648
|
-
"user_info":
|
|
765
|
+
"user_info": self._get_event_data(event, "context").user_info if self._get_event_data(event, "context") and hasattr(self._get_event_data(event, "context"), 'user_info') else None
|
|
649
766
|
}
|
|
650
767
|
)
|
|
651
768
|
self.trace_spans[trace_id] = trace
|
|
652
769
|
# Store user_id, user_query, and conversation_history for later use
|
|
653
|
-
trace._user_id = user_id or
|
|
770
|
+
trace._user_id = user_id or self._get_event_data(event, "user_id")
|
|
654
771
|
trace._user_query = user_query
|
|
655
772
|
trace._conversation_history = conversation_history
|
|
656
773
|
print(f"[LANGFUSE] Created trace with user query: {user_query[:100] if user_query else 'None'}...")
|
|
@@ -667,7 +784,7 @@ class LangfuseTraceCollector:
|
|
|
667
784
|
"trace_id": str(trace_id),
|
|
668
785
|
"user_query": getattr(self.trace_spans[trace_id], '_user_query', None),
|
|
669
786
|
"user_id": getattr(self.trace_spans[trace_id], '_user_id', None),
|
|
670
|
-
"agent_name":
|
|
787
|
+
"agent_name": self._get_event_data(event, "agent_name", "analytics_agent_jaf"),
|
|
671
788
|
"conversation_history": conversation_history,
|
|
672
789
|
"tool_calls": self.trace_tool_calls.get(trace_id, []),
|
|
673
790
|
"tool_results": self.trace_tool_results.get(trace_id, [])
|
|
@@ -695,7 +812,7 @@ class LangfuseTraceCollector:
|
|
|
695
812
|
|
|
696
813
|
elif event.type == "llm_call_start":
|
|
697
814
|
# Start a generation for LLM calls
|
|
698
|
-
model =
|
|
815
|
+
model = self._get_event_data(event, "model", "unknown")
|
|
699
816
|
print(f"[LANGFUSE] Starting generation for LLM call with model: {model}")
|
|
700
817
|
|
|
701
818
|
# Get stored user information from the trace
|
|
@@ -703,11 +820,13 @@ class LangfuseTraceCollector:
|
|
|
703
820
|
user_id = getattr(trace, '_user_id', None)
|
|
704
821
|
user_query = getattr(trace, '_user_query', None)
|
|
705
822
|
|
|
706
|
-
|
|
823
|
+
# Use compatibility layer to create generation (works with both v2 and v3)
|
|
824
|
+
generation = self._create_generation(
|
|
825
|
+
parent_span=trace,
|
|
707
826
|
name=f"llm-call-{model}",
|
|
708
|
-
input=
|
|
827
|
+
input=self._get_event_data(event, "messages"),
|
|
709
828
|
metadata={
|
|
710
|
-
"agent_name":
|
|
829
|
+
"agent_name": self._get_event_data(event, "agent_name"),
|
|
711
830
|
"model": model,
|
|
712
831
|
"user_id": user_id,
|
|
713
832
|
"user_query": user_query
|
|
@@ -723,10 +842,10 @@ class LangfuseTraceCollector:
|
|
|
723
842
|
print(f"[LANGFUSE] Ending generation for LLM call")
|
|
724
843
|
# End the generation
|
|
725
844
|
generation = self.active_spans[span_id]
|
|
726
|
-
choice =
|
|
727
|
-
|
|
845
|
+
choice = self._get_event_data(event, "choice", {})
|
|
846
|
+
|
|
728
847
|
# Extract usage from the event data
|
|
729
|
-
usage =
|
|
848
|
+
usage = self._get_event_data(event, "usage", {})
|
|
730
849
|
|
|
731
850
|
# Extract model information from choice data or event data
|
|
732
851
|
model = choice.get("model", "unknown")
|
|
@@ -752,8 +871,10 @@ class LangfuseTraceCollector:
|
|
|
752
871
|
print(f"[LANGFUSE] Usage data for automatic cost calculation: {langfuse_usage}")
|
|
753
872
|
|
|
754
873
|
# Include model information in the generation end - Langfuse will calculate costs automatically
|
|
755
|
-
|
|
756
|
-
|
|
874
|
+
# Use compatibility wrapper for ending spans/generations
|
|
875
|
+
self._end_span(
|
|
876
|
+
span=generation,
|
|
877
|
+
output=choice,
|
|
757
878
|
usage=langfuse_usage,
|
|
758
879
|
model=model, # Pass model directly for automatic cost calculation
|
|
759
880
|
metadata={
|
|
@@ -772,9 +893,9 @@ class LangfuseTraceCollector:
|
|
|
772
893
|
|
|
773
894
|
elif event.type == "tool_call_start":
|
|
774
895
|
# Start a span for tool calls with detailed input information
|
|
775
|
-
tool_name =
|
|
776
|
-
tool_args =
|
|
777
|
-
call_id =
|
|
896
|
+
tool_name = self._get_event_data(event, 'tool_name', 'unknown')
|
|
897
|
+
tool_args = self._get_event_data(event, "args", {})
|
|
898
|
+
call_id = self._get_event_data(event, "call_id")
|
|
778
899
|
if not call_id:
|
|
779
900
|
call_id = f"{tool_name}-{uuid.uuid4().hex[:8]}"
|
|
780
901
|
try:
|
|
@@ -807,7 +928,9 @@ class LangfuseTraceCollector:
|
|
|
807
928
|
"timestamp": datetime.now().isoformat()
|
|
808
929
|
}
|
|
809
930
|
|
|
810
|
-
|
|
931
|
+
# Use compatibility layer to create span (works with both v2 and v3)
|
|
932
|
+
span = self._create_span(
|
|
933
|
+
parent_span=self.trace_spans[trace_id],
|
|
811
934
|
name=f"tool-{tool_name}",
|
|
812
935
|
input=tool_input,
|
|
813
936
|
metadata={
|
|
@@ -824,9 +947,9 @@ class LangfuseTraceCollector:
|
|
|
824
947
|
elif event.type == "tool_call_end":
|
|
825
948
|
span_id = self._get_span_id(event)
|
|
826
949
|
if span_id in self.active_spans:
|
|
827
|
-
tool_name =
|
|
828
|
-
tool_result =
|
|
829
|
-
call_id =
|
|
950
|
+
tool_name = self._get_event_data(event, 'tool_name', 'unknown')
|
|
951
|
+
tool_result = self._get_event_data(event, "result")
|
|
952
|
+
call_id = self._get_event_data(event, "call_id")
|
|
830
953
|
|
|
831
954
|
print(f"[LANGFUSE] Ending span for tool call: {tool_name} ({call_id})")
|
|
832
955
|
|
|
@@ -836,9 +959,9 @@ class LangfuseTraceCollector:
|
|
|
836
959
|
"result": tool_result,
|
|
837
960
|
"call_id": call_id,
|
|
838
961
|
"timestamp": datetime.now().isoformat(),
|
|
839
|
-
"execution_status":
|
|
840
|
-
"status":
|
|
841
|
-
"tool_result":
|
|
962
|
+
"execution_status": self._get_event_data(event, "execution_status", "completed"),
|
|
963
|
+
"status": self._get_event_data(event, "execution_status", "completed"), # DEPRECATED: backward compatibility
|
|
964
|
+
"tool_result": self._get_event_data(event, "tool_result")
|
|
842
965
|
}
|
|
843
966
|
|
|
844
967
|
if trace_id not in self.trace_tool_results:
|
|
@@ -852,13 +975,15 @@ class LangfuseTraceCollector:
|
|
|
852
975
|
"result": tool_result,
|
|
853
976
|
"call_id": call_id,
|
|
854
977
|
"timestamp": datetime.now().isoformat(),
|
|
855
|
-
"execution_status":
|
|
856
|
-
"status":
|
|
978
|
+
"execution_status": self._get_event_data(event, "execution_status", "completed"),
|
|
979
|
+
"status": self._get_event_data(event, "execution_status", "completed") # DEPRECATED: backward compatibility
|
|
857
980
|
}
|
|
858
981
|
|
|
859
982
|
# End the span with detailed output
|
|
983
|
+
# Use compatibility wrapper for ending spans/generations
|
|
860
984
|
span = self.active_spans[span_id]
|
|
861
|
-
|
|
985
|
+
self._end_span(
|
|
986
|
+
span=span,
|
|
862
987
|
output=tool_output,
|
|
863
988
|
metadata={
|
|
864
989
|
"tool_name": tool_name,
|
|
@@ -878,17 +1003,21 @@ class LangfuseTraceCollector:
|
|
|
878
1003
|
elif event.type == "handoff":
|
|
879
1004
|
# Create an event for handoffs
|
|
880
1005
|
print(f"[LANGFUSE] Creating event for handoff")
|
|
881
|
-
|
|
1006
|
+
# Use compatibility layer to create event (works with both v2 and v3)
|
|
1007
|
+
self._create_event(
|
|
1008
|
+
parent_span=self.trace_spans[trace_id],
|
|
882
1009
|
name="agent-handoff",
|
|
883
|
-
input={"from":
|
|
1010
|
+
input={"from": self._get_event_data(event, "from"), "to": self._get_event_data(event, "to")},
|
|
884
1011
|
metadata=event.data
|
|
885
1012
|
)
|
|
886
1013
|
print(f"[LANGFUSE] Handoff event created")
|
|
887
|
-
|
|
1014
|
+
|
|
888
1015
|
else:
|
|
889
1016
|
# Create a generic event for other event types
|
|
890
1017
|
print(f"[LANGFUSE] Creating generic event for: {event.type}")
|
|
891
|
-
|
|
1018
|
+
# Use compatibility layer to create event (works with both v2 and v3)
|
|
1019
|
+
self._create_event(
|
|
1020
|
+
parent_span=self.trace_spans[trace_id],
|
|
892
1021
|
name=event.type,
|
|
893
1022
|
input=event.data,
|
|
894
1023
|
metadata={"framework": "jaf", "event_type": event.type}
|
|
@@ -902,20 +1031,28 @@ class LangfuseTraceCollector:
|
|
|
902
1031
|
traceback.print_exc()
|
|
903
1032
|
|
|
904
1033
|
def _get_trace_id(self, event: TraceEvent) -> Optional[TraceId]:
|
|
905
|
-
"""Extract trace ID from event data."""
|
|
906
|
-
if hasattr(event, 'data')
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
#
|
|
1034
|
+
"""Extract trace ID from event data, handling both dict and dataclass."""
|
|
1035
|
+
if not hasattr(event, 'data'):
|
|
1036
|
+
return None
|
|
1037
|
+
|
|
1038
|
+
# Try snake_case first (Python convention)
|
|
1039
|
+
trace_id = self._get_event_data(event, 'trace_id')
|
|
1040
|
+
if trace_id:
|
|
1041
|
+
return trace_id
|
|
1042
|
+
|
|
1043
|
+
run_id = self._get_event_data(event, 'run_id')
|
|
1044
|
+
if run_id:
|
|
1045
|
+
return TraceId(run_id)
|
|
1046
|
+
|
|
1047
|
+
# Fallback to camelCase (for compatibility)
|
|
1048
|
+
trace_id = self._get_event_data(event, 'traceId')
|
|
1049
|
+
if trace_id:
|
|
1050
|
+
return trace_id
|
|
1051
|
+
|
|
1052
|
+
run_id = self._get_event_data(event, 'runId')
|
|
1053
|
+
if run_id:
|
|
1054
|
+
return TraceId(run_id)
|
|
1055
|
+
|
|
919
1056
|
return None
|
|
920
1057
|
|
|
921
1058
|
def _get_span_id(self, event: TraceEvent) -> str:
|
|
@@ -924,15 +1061,15 @@ class LangfuseTraceCollector:
|
|
|
924
1061
|
|
|
925
1062
|
# Use consistent identifiers that don't depend on timestamp
|
|
926
1063
|
if event.type.startswith('tool_call'):
|
|
927
|
-
call_id =
|
|
1064
|
+
call_id = self._get_event_data(event, 'call_id') or self._get_event_data(event, 'tool_call_id')
|
|
928
1065
|
if call_id:
|
|
929
1066
|
return f"tool-{trace_id}-{call_id}"
|
|
930
|
-
tool_name =
|
|
1067
|
+
tool_name = self._get_event_data(event, 'tool_name') or self._get_event_data(event, 'toolName', 'unknown')
|
|
931
1068
|
return f"tool-{tool_name}-{trace_id}"
|
|
932
1069
|
elif event.type.startswith('llm_call'):
|
|
933
1070
|
# For LLM calls, use a simpler consistent ID that matches between start and end
|
|
934
1071
|
# Get run_id for more consistent matching
|
|
935
|
-
run_id =
|
|
1072
|
+
run_id = self._get_event_data(event, 'run_id') or self._get_event_data(event, 'runId', trace_id)
|
|
936
1073
|
return f"llm-{run_id}"
|
|
937
1074
|
else:
|
|
938
1075
|
return f"{event.type}-{trace_id}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.3
|
|
4
4
|
Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
|
|
5
5
|
Author: JAF Contributors
|
|
6
6
|
Maintainer: JAF Contributors
|
|
@@ -42,13 +42,13 @@ Requires-Dist: fastmcp>=0.1.0
|
|
|
42
42
|
Requires-Dist: opentelemetry-api>=1.22.0
|
|
43
43
|
Requires-Dist: opentelemetry-sdk>=1.22.0
|
|
44
44
|
Requires-Dist: opentelemetry-exporter-otlp>=1.22.0
|
|
45
|
-
Requires-Dist: langfuse<
|
|
45
|
+
Requires-Dist: langfuse<3.0.0
|
|
46
46
|
Requires-Dist: litellm>=1.76.3
|
|
47
47
|
Provides-Extra: tracing
|
|
48
48
|
Requires-Dist: opentelemetry-api>=1.22.0; extra == "tracing"
|
|
49
49
|
Requires-Dist: opentelemetry-sdk>=1.22.0; extra == "tracing"
|
|
50
50
|
Requires-Dist: opentelemetry-exporter-otlp>=1.22.0; extra == "tracing"
|
|
51
|
-
Requires-Dist: langfuse<
|
|
51
|
+
Requires-Dist: langfuse<3.0.0; extra == "tracing"
|
|
52
52
|
Provides-Extra: attachments
|
|
53
53
|
Requires-Dist: PyPDF2>=3.0.0; extra == "attachments"
|
|
54
54
|
Requires-Dist: python-docx>=1.1.0; extra == "attachments"
|
|
@@ -82,7 +82,7 @@ Dynamic: license-file
|
|
|
82
82
|
|
|
83
83
|
<!--  -->
|
|
84
84
|
|
|
85
|
-
[](https://github.com/xynehq/jaf-py)
|
|
86
86
|
[](https://www.python.org/)
|
|
87
87
|
[](https://xynehq.github.io/jaf-py/)
|
|
88
88
|
|
|
@@ -14,7 +14,7 @@ fastmcp>=0.1.0
|
|
|
14
14
|
opentelemetry-api>=1.22.0
|
|
15
15
|
opentelemetry-sdk>=1.22.0
|
|
16
16
|
opentelemetry-exporter-otlp>=1.22.0
|
|
17
|
-
langfuse<
|
|
17
|
+
langfuse<3.0.0
|
|
18
18
|
litellm>=1.76.3
|
|
19
19
|
|
|
20
20
|
[all]
|
|
@@ -55,7 +55,7 @@ uvicorn[standard]>=0.35.0
|
|
|
55
55
|
opentelemetry-api>=1.22.0
|
|
56
56
|
opentelemetry-sdk>=1.22.0
|
|
57
57
|
opentelemetry-exporter-otlp>=1.22.0
|
|
58
|
-
langfuse<
|
|
58
|
+
langfuse<3.0.0
|
|
59
59
|
|
|
60
60
|
[visualization]
|
|
61
61
|
graphviz>=0.20.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jaf-py"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.3"
|
|
8
8
|
description = "A purely functional agent framework with immutable state and composable tools - Python implementation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -47,7 +47,7 @@ dependencies = [
|
|
|
47
47
|
"opentelemetry-api>=1.22.0",
|
|
48
48
|
"opentelemetry-sdk>=1.22.0",
|
|
49
49
|
"opentelemetry-exporter-otlp>=1.22.0",
|
|
50
|
-
"langfuse
|
|
50
|
+
"langfuse<3.0.0",
|
|
51
51
|
"litellm>=1.76.3"
|
|
52
52
|
]
|
|
53
53
|
|
|
@@ -56,7 +56,7 @@ tracing = [
|
|
|
56
56
|
"opentelemetry-api>=1.22.0",
|
|
57
57
|
"opentelemetry-sdk>=1.22.0",
|
|
58
58
|
"opentelemetry-exporter-otlp>=1.22.0",
|
|
59
|
-
"langfuse
|
|
59
|
+
"langfuse<3.0.0",
|
|
60
60
|
]
|
|
61
61
|
attachments = [
|
|
62
62
|
"PyPDF2>=3.0.0",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|