jaf-py 2.2.3__py3-none-any.whl → 2.3.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.
jaf/core/engine.py CHANGED
@@ -91,7 +91,13 @@ async def run(
91
91
  set_current_run_config(config)
92
92
 
93
93
  if config.on_event:
94
- config.on_event(RunStartEvent(data=to_event_data(RunStartEventData(run_id=initial_state.run_id, trace_id=initial_state.trace_id))))
94
+ config.on_event(RunStartEvent(data=to_event_data(RunStartEventData(
95
+ run_id=initial_state.run_id,
96
+ trace_id=initial_state.trace_id,
97
+ session_id=config.conversation_id,
98
+ context=initial_state.context,
99
+ messages=initial_state.messages
100
+ ))))
95
101
 
96
102
  state_with_memory = await _load_conversation_history(initial_state, config)
97
103
  result = await _run_internal(state_with_memory, config)
@@ -289,7 +295,9 @@ async def _run_internal(
289
295
  agent_name=current_agent.name,
290
296
  model=model,
291
297
  trace_id=state.trace_id,
292
- run_id=state.run_id
298
+ run_id=state.run_id,
299
+ context=state.context,
300
+ messages=state.messages
293
301
  ))))
294
302
 
295
303
  # Get completion from model provider
jaf/core/tracing.py CHANGED
@@ -358,15 +358,99 @@ class LangfuseTraceCollector:
358
358
  if event.type == "run_start":
359
359
  # Start a new trace for the entire run
360
360
  print(f"[LANGFUSE] Starting trace for run: {trace_id}")
361
+
362
+ # Extract user query from the run_start data
363
+ user_query = None
364
+ user_id = None
365
+
366
+ # Debug: Print the event data structure to understand what we're working with
367
+ print(f"[LANGFUSE DEBUG] Event data keys: {list(event.data.keys()) if event.data else 'No data'}")
368
+ if event.data.get("context"):
369
+ context = event.data["context"]
370
+ print(f"[LANGFUSE DEBUG] Context type: {type(context)}")
371
+ print(f"[LANGFUSE DEBUG] Context attributes: {dir(context) if hasattr(context, '__dict__') else 'Not an object'}")
372
+ if hasattr(context, '__dict__'):
373
+ print(f"[LANGFUSE DEBUG] Context dict: {context.__dict__}")
374
+
375
+ # Try to extract from context first
376
+ context = event.data.get("context")
377
+ if context:
378
+ # Try direct attribute access
379
+ if hasattr(context, 'query'):
380
+ user_query = context.query
381
+ print(f"[LANGFUSE DEBUG] Found user_query from context.query: {user_query}")
382
+
383
+ # Try to extract from combined_history
384
+ if hasattr(context, 'combined_history') and context.combined_history:
385
+ history = context.combined_history
386
+ print(f"[LANGFUSE DEBUG] Found combined_history with {len(history)} messages")
387
+ for i, msg in enumerate(reversed(history)):
388
+ print(f"[LANGFUSE DEBUG] History message {i}: {msg}")
389
+ if isinstance(msg, dict) and msg.get("role") == "user":
390
+ user_query = msg.get("content", "")
391
+ print(f"[LANGFUSE DEBUG] Found user_query from history: {user_query}")
392
+ break
393
+
394
+ # Try to extract user_id from token_response
395
+ if hasattr(context, 'token_response'):
396
+ token_response = context.token_response
397
+ print(f"[LANGFUSE DEBUG] Found token_response: {type(token_response)}")
398
+ if isinstance(token_response, dict):
399
+ user_id = token_response.get("email") or token_response.get("username")
400
+ print(f"[LANGFUSE DEBUG] Extracted user_id: {user_id}")
401
+ elif hasattr(token_response, 'email'):
402
+ user_id = token_response.email
403
+ print(f"[LANGFUSE DEBUG] Extracted user_id from attr: {user_id}")
404
+
405
+ # Fallback: try to extract from messages if context didn't work
406
+ if not user_query and event.data.get("messages"):
407
+ print(f"[LANGFUSE DEBUG] Trying fallback from messages")
408
+ messages = event.data["messages"]
409
+ print(f"[LANGFUSE DEBUG] Found {len(messages)} messages")
410
+ # Find the last user message which should be the current query
411
+ for i, msg in enumerate(reversed(messages)):
412
+ print(f"[LANGFUSE DEBUG] Message {i}: {msg}")
413
+ if isinstance(msg, dict) and msg.get("role") == "user":
414
+ user_query = msg.get("content", "")
415
+ print(f"[LANGFUSE DEBUG] Found user_query from messages: {user_query}")
416
+ break
417
+ elif hasattr(msg, 'role') and msg.role == 'user':
418
+ user_query = msg.content
419
+ print(f"[LANGFUSE DEBUG] Found user_query from message attr: {user_query}")
420
+ break
421
+
422
+ print(f"[LANGFUSE DEBUG] Final extracted - user_query: {user_query}, user_id: {user_id}")
423
+
424
+ # Create comprehensive input data for the trace
425
+ trace_input = {
426
+ "user_query": user_query,
427
+ "run_id": str(trace_id),
428
+ "agent_name": event.data.get("agent_name", "analytics_agent_jaf"),
429
+ "session_info": {
430
+ "session_id": event.data.get("session_id"),
431
+ "user_id": user_id or event.data.get("user_id")
432
+ }
433
+ }
434
+
361
435
  trace = self.langfuse.trace(
362
436
  name=f"jaf-run-{trace_id}",
363
- user_id=event.data.get("user_id"),
437
+ user_id=user_id or event.data.get("user_id"),
364
438
  session_id=event.data.get("session_id"),
365
- input=event.data,
366
- metadata={"framework": "jaf", "event_type": "run_start", "trace_id": str(trace_id)}
439
+ input=trace_input,
440
+ metadata={
441
+ "framework": "jaf",
442
+ "event_type": "run_start",
443
+ "trace_id": str(trace_id),
444
+ "user_query": user_query,
445
+ "user_id": user_id or event.data.get("user_id"),
446
+ "agent_name": event.data.get("agent_name", "analytics_agent_jaf")
447
+ }
367
448
  )
368
449
  self.trace_spans[trace_id] = trace
369
- print(f"[LANGFUSE] Created trace: {trace}")
450
+ # Store user_id and user_query for later use in generations
451
+ trace._user_id = user_id or event.data.get("user_id")
452
+ trace._user_query = user_query
453
+ print(f"[LANGFUSE] Created trace with user query: {user_query[:100] if user_query else 'None'}...")
370
454
 
371
455
  elif event.type == "run_end":
372
456
  if trace_id in self.trace_spans:
@@ -386,10 +470,21 @@ class LangfuseTraceCollector:
386
470
  # Start a generation for LLM calls
387
471
  model = event.data.get("model", "unknown")
388
472
  print(f"[LANGFUSE] Starting generation for LLM call with model: {model}")
389
- generation = self.trace_spans[trace_id].generation(
473
+
474
+ # Get stored user information from the trace
475
+ trace = self.trace_spans[trace_id]
476
+ user_id = getattr(trace, '_user_id', None)
477
+ user_query = getattr(trace, '_user_query', None)
478
+
479
+ generation = trace.generation(
390
480
  name=f"llm-call-{model}",
391
481
  input=event.data.get("messages"),
392
- metadata={"agent_name": event.data.get("agent_name"), "model": model}
482
+ metadata={
483
+ "agent_name": event.data.get("agent_name"),
484
+ "model": model,
485
+ "user_id": user_id,
486
+ "user_query": user_query
487
+ }
393
488
  )
394
489
  span_id = self._get_span_id(event)
395
490
  self.active_spans[span_id] = generation
@@ -405,47 +500,111 @@ class LangfuseTraceCollector:
405
500
 
406
501
  # Extract usage from the event data
407
502
  usage = event.data.get("usage", {})
408
-
409
- # Convert to Langfuse v2 format
503
+
504
+ # Extract model information from choice data or event data
505
+ model = choice.get("model", "unknown")
506
+ if model == "unknown":
507
+ # Try to get model from the choice response structure
508
+ if isinstance(choice, dict):
509
+ model = choice.get("model") or choice.get("id", "unknown")
510
+
511
+ # Convert to Langfuse v2 format - let Langfuse handle cost calculation automatically
410
512
  langfuse_usage = None
411
513
  if usage:
514
+ prompt_tokens = usage.get("prompt_tokens", 0)
515
+ completion_tokens = usage.get("completion_tokens", 0)
516
+ total_tokens = usage.get("total_tokens", 0)
517
+
412
518
  langfuse_usage = {
413
- "input": usage.get("prompt_tokens", 0),
414
- "output": usage.get("completion_tokens", 0),
415
- "total": usage.get("total_tokens", 0),
519
+ "input": prompt_tokens,
520
+ "output": completion_tokens,
521
+ "total": total_tokens,
416
522
  "unit": "TOKENS"
417
523
  }
418
-
419
- generation.end(output=choice, usage=langfuse_usage)
524
+
525
+ print(f"[LANGFUSE] Usage data for automatic cost calculation: {langfuse_usage}")
526
+
527
+ # Include model information in the generation end - Langfuse will calculate costs automatically
528
+ generation.end(
529
+ output=choice,
530
+ usage=langfuse_usage,
531
+ model=model, # Pass model directly for automatic cost calculation
532
+ metadata={
533
+ "model": model,
534
+ "system_fingerprint": choice.get("system_fingerprint"),
535
+ "created": choice.get("created"),
536
+ "response_id": choice.get("id")
537
+ }
538
+ )
420
539
 
421
540
  # Clean up the span reference
422
541
  del self.active_spans[span_id]
423
- print(f"[LANGFUSE] Generation ended")
542
+ print(f"[LANGFUSE] Generation ended with cost tracking")
424
543
  else:
425
544
  print(f"[LANGFUSE] No generation found for llm_call_end: {span_id}")
426
545
 
427
546
  elif event.type == "tool_call_start":
428
- # Start a span for tool calls
429
- print(f"[LANGFUSE] Starting span for tool call: {event.data.get('tool_name')}")
547
+ # Start a span for tool calls with detailed input information
548
+ tool_name = event.data.get('tool_name', 'unknown')
549
+ tool_args = event.data.get("args", {})
550
+
551
+ print(f"[LANGFUSE] Starting span for tool call: {tool_name}")
552
+
553
+ # Create comprehensive input data for the tool call
554
+ tool_input = {
555
+ "tool_name": tool_name,
556
+ "arguments": tool_args,
557
+ "call_id": event.data.get("call_id"),
558
+ "timestamp": datetime.now().isoformat()
559
+ }
560
+
430
561
  span = self.trace_spans[trace_id].span(
431
- name=f"tool-{event.data.get('tool_name', 'unknown')}",
432
- input=event.data.get("args"),
433
- metadata={"tool_name": event.data.get("tool_name")}
562
+ name=f"tool-{tool_name}",
563
+ input=tool_input,
564
+ metadata={
565
+ "tool_name": tool_name,
566
+ "call_id": event.data.get("call_id"),
567
+ "framework": "jaf",
568
+ "event_type": "tool_call"
569
+ }
434
570
  )
435
571
  span_id = self._get_span_id(event)
436
572
  self.active_spans[span_id] = span
437
- print(f"[LANGFUSE] Created tool span: {span}")
573
+ print(f"[LANGFUSE] Created tool span for {tool_name} with args: {str(tool_args)[:100]}...")
438
574
 
439
575
  elif event.type == "tool_call_end":
440
576
  span_id = self._get_span_id(event)
441
577
  if span_id in self.active_spans:
442
- print(f"[LANGFUSE] Ending span for tool call")
443
- # End the span
578
+ tool_name = event.data.get('tool_name', 'unknown')
579
+ tool_result = event.data.get("result")
580
+
581
+ print(f"[LANGFUSE] Ending span for tool call: {tool_name}")
582
+
583
+ # Create comprehensive output data for the tool call
584
+ tool_output = {
585
+ "tool_name": tool_name,
586
+ "result": tool_result,
587
+ "call_id": event.data.get("call_id"),
588
+ "timestamp": datetime.now().isoformat(),
589
+ "status": "completed"
590
+ }
591
+
592
+ # End the span with detailed output
444
593
  span = self.active_spans[span_id]
445
- span.end(output=event.data.get("result"))
594
+ span.end(
595
+ output=tool_output,
596
+ metadata={
597
+ "tool_name": tool_name,
598
+ "call_id": event.data.get("call_id"),
599
+ "result_length": len(str(tool_result)) if tool_result else 0,
600
+ "framework": "jaf",
601
+ "event_type": "tool_call_end"
602
+ }
603
+ )
604
+
446
605
  # Clean up the span reference
447
606
  del self.active_spans[span_id]
448
- print(f"[LANGFUSE] Tool span ended")
607
+ print(f"[LANGFUSE] Tool span ended for {tool_name} with result length: {len(str(tool_result)) if tool_result else 0}")
449
608
  else:
450
609
  print(f"[LANGFUSE] No tool span found for tool_call_end: {span_id}")
451
610
 
@@ -502,19 +661,10 @@ class LangfuseTraceCollector:
502
661
  tool_name = event.data.get('tool_name') or event.data.get('toolName', 'unknown')
503
662
  return f"tool-{tool_name}-{trace_id}"
504
663
  elif event.type.startswith('llm_call'):
505
- # For LLM calls, get the model name from the event data
506
- model = event.data.get('model')
507
-
508
- # For llm_call_end events, the model might be in the choice object
509
- if not model and event.type == 'llm_call_end':
510
- choice = event.data.get('choice', {})
511
- if isinstance(choice, dict):
512
- model = choice.get('model')
513
-
514
- # Handle case where model might be empty or None
515
- if not model or model == '':
516
- model = 'unknown'
517
- return f"llm-{model}-{trace_id}"
664
+ # For LLM calls, use a simpler consistent ID that matches between start and end
665
+ # Get run_id for more consistent matching
666
+ run_id = event.data.get('run_id') or event.data.get('runId', trace_id)
667
+ return f"llm-{run_id}"
518
668
  else:
519
669
  return f"{event.type}-{trace_id}"
520
670
 
jaf/core/types.py CHANGED
@@ -378,6 +378,9 @@ class RunStartEventData:
378
378
  """Data for run start events."""
379
379
  run_id: RunId
380
380
  trace_id: TraceId
381
+ session_id: Optional[str] = None
382
+ context: Optional[Any] = None
383
+ messages: Optional[List[Message]] = None
381
384
 
382
385
  @dataclass(frozen=True)
383
386
  class RunStartEvent:
@@ -391,6 +394,8 @@ class LLMCallStartEventData:
391
394
  model: str
392
395
  trace_id: TraceId
393
396
  run_id: RunId
397
+ context: Optional[Any] = None
398
+ messages: Optional[List[Message]] = None
394
399
 
395
400
  @dataclass(frozen=True)
396
401
  class LLMCallStartEvent:
jaf/providers/model.py CHANGED
@@ -51,8 +51,11 @@ def make_litellm_provider(
51
51
  if proxies:
52
52
  # Create httpx client with proxy configuration
53
53
  try:
54
- http_client = httpx.Client(proxies=proxies)
55
- client_kwargs["http_client"] = http_client
54
+ # Use the https proxy if available, otherwise http proxy
55
+ proxy_url = proxies.get('https://') or proxies.get('http://')
56
+ if proxy_url:
57
+ http_client = httpx.Client(proxy=proxy_url)
58
+ client_kwargs["http_client"] = http_client
56
59
  except Exception as e:
57
60
  print(f"Warning: Could not configure proxy: {e}")
58
61
  # Fall back to environment variables for proxy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.2.3
3
+ Version: 2.3.0
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
@@ -73,7 +73,7 @@ Dynamic: license-file
73
73
 
74
74
  <!-- ![JAF Banner](docs/cover.png) -->
75
75
 
76
- [![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](https://github.com/xynehq/jaf-py)
76
+ [![Version](https://img.shields.io/badge/version-2.2.3-blue.svg)](https://github.com/xynehq/jaf-py)
77
77
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
78
78
  [![Docs](https://img.shields.io/badge/Docs-Live-brightgreen)](https://xynehq.github.io/jaf-py/)
79
79
 
@@ -114,15 +114,23 @@ A purely functional agent framework with immutable state and composable tools, p
114
114
 
115
115
  ### 📊 **Observability & Monitoring**
116
116
  - ✅ **Real-time Tracing**: Event-driven observability
117
+ - ✅ **OpenTelemetry Integration**: Distributed tracing with OTLP
118
+ - ✅ **Langfuse Tracing**: LLM observability and analytics
117
119
  - ✅ **Structured Logging**: JSON-formatted logs
118
120
  - ✅ **Error Handling**: Comprehensive error types and recovery
119
121
  - ✅ **Performance Metrics**: Built-in timing and counters
120
122
 
123
+ ### 🤖 **Agent-as-Tool Architecture**
124
+ - ✅ **Hierarchical Orchestration**: Use agents as tools in other agents
125
+ - ✅ **Conditional Tool Enabling**: Enable/disable agent tools based on context
126
+ - ✅ **Session Management**: Configurable session inheritance for sub-agents
127
+ - ✅ **Flexible Output Extraction**: Custom extractors for agent tool outputs
128
+
121
129
  ### 🔧 **Developer Experience**
122
130
  - ✅ **CLI Tools**: Project initialization and management
123
131
  - ✅ **Hot Reload**: Development server with auto-reload
124
132
  - ✅ **Type Hints**: Full mypy compatibility
125
- - ✅ **Rich Examples**: RAG, multi-agent, and server demos
133
+ - ✅ **Rich Examples**: RAG, multi-agent, agent-as-tool, and server demos
126
134
  - ✅ **Visual Architecture**: Graphviz-powered agent and tool diagrams
127
135
 
128
136
  ## 🎯 Core Philosophy
@@ -148,6 +156,7 @@ pip install "jaf-py[all] @ git+https://github.com/xynehq/jaf-py.git"
148
156
  pip install "jaf-py[server] @ git+https://github.com/xynehq/jaf-py.git" # FastAPI server support
149
157
  pip install "jaf-py[memory] @ git+https://github.com/xynehq/jaf-py.git" # Redis/PostgreSQL memory providers
150
158
  pip install "jaf-py[visualization] @ git+https://github.com/xynehq/jaf-py.git" # Graphviz visualization tools
159
+ pip install "jaf-py[tracing] @ git+https://github.com/xynehq/jaf-py.git" # OpenTelemetry and Langfuse tracing
151
160
  pip install "jaf-py[dev] @ git+https://github.com/xynehq/jaf-py.git" # Development tools
152
161
  ```
153
162
 
@@ -212,6 +221,7 @@ For offline access, documentation is also available in the [`docs/`](docs/) dire
212
221
  - **[🔧 Tools Guide](docs/tools.md)** - Creating and using tools
213
222
  - **[💾 Memory System](docs/memory-system.md)** - Persistence and memory providers
214
223
  - **[🤖 Model Providers](docs/model-providers.md)** - LiteLLM integration
224
+ - **[📊 Monitoring](docs/monitoring.md)** - Observability, metrics, and alerting
215
225
  - **[🌐 Server API](docs/server-api.md)** - FastAPI endpoints reference
216
226
  - **[📦 Deployment](docs/deployment.md)** - Production deployment guide
217
227
  - **[🎮 Examples](docs/examples.md)** - Detailed example walkthroughs
@@ -439,6 +449,58 @@ config = RunConfig(
439
449
  )
440
450
  ```
441
451
 
452
+ ## 🤖 Agent-as-Tool Functionality
453
+
454
+ JAF 2.2+ introduces powerful agent-as-tool capabilities, allowing you to use agents as tools within other agents for hierarchical orchestration:
455
+
456
+ ```python
457
+ from jaf.core.agent_tool import create_agent_tool
458
+ from jaf.core.types import create_json_output_extractor
459
+
460
+ # Create specialized agents
461
+ spanish_agent = Agent(
462
+ name="spanish_translator",
463
+ instructions=lambda state: "Translate text to Spanish",
464
+ output_codec=TranslationOutput
465
+ )
466
+
467
+ french_agent = Agent(
468
+ name="french_translator",
469
+ instructions=lambda state: "Translate text to French",
470
+ output_codec=TranslationOutput
471
+ )
472
+
473
+ # Convert agents to tools with conditional enabling
474
+ spanish_tool = spanish_agent.as_tool(
475
+ tool_name="translate_to_spanish",
476
+ tool_description="Translate text to Spanish",
477
+ max_turns=3,
478
+ custom_output_extractor=create_json_output_extractor(),
479
+ is_enabled=True # Always enabled
480
+ )
481
+
482
+ french_tool = french_agent.as_tool(
483
+ tool_name="translate_to_french",
484
+ tool_description="Translate text to French",
485
+ max_turns=3,
486
+ custom_output_extractor=create_json_output_extractor(),
487
+ is_enabled=lambda context, agent: context.language_preference == "french_spanish"
488
+ )
489
+
490
+ # Create orchestrator agent using agent tools
491
+ orchestrator = Agent(
492
+ name="translation_orchestrator",
493
+ instructions=lambda state: "Use translation tools to respond in multiple languages",
494
+ tools=[spanish_tool, french_tool]
495
+ )
496
+ ```
497
+
498
+ ### Key Features:
499
+ - **Conditional Enabling**: Enable/disable agent tools based on runtime context
500
+ - **Session Management**: Configure whether sub-agents inherit parent session state
501
+ - **Custom Output Extraction**: Define how to extract and format agent tool outputs
502
+ - **Error Handling**: Robust error handling for failed agent tool executions
503
+
442
504
  ## 🔗 Agent Handoffs
443
505
 
444
506
  ```python
@@ -488,6 +550,48 @@ config = RunConfig(
488
550
  )
489
551
  ```
490
552
 
553
+ ### OpenTelemetry Integration
554
+
555
+ JAF 2.2+ includes built-in OpenTelemetry support for distributed tracing:
556
+
557
+ ```python
558
+ import os
559
+ from jaf.core.tracing import create_composite_trace_collector, ConsoleTraceCollector
560
+
561
+ # Configure OpenTelemetry endpoint
562
+ os.environ["TRACE_COLLECTOR_URL"] = "http://localhost:4318/v1/traces"
563
+
564
+ # Tracing will be automatically configured when creating a composite collector
565
+ trace_collector = create_composite_trace_collector(ConsoleTraceCollector())
566
+
567
+ config = RunConfig(
568
+ # ... other config
569
+ on_event=trace_collector.collect,
570
+ )
571
+ ```
572
+
573
+ ### Langfuse Integration
574
+
575
+ For LLM-specific observability and analytics:
576
+
577
+ ```python
578
+ import os
579
+ from jaf.core.tracing import create_composite_trace_collector, ConsoleTraceCollector
580
+
581
+ # Configure Langfuse credentials
582
+ os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-your-public-key"
583
+ os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-your-secret-key"
584
+ os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # or your self-hosted instance
585
+
586
+ # Langfuse tracing will be automatically configured
587
+ trace_collector = create_composite_trace_collector(ConsoleTraceCollector())
588
+
589
+ config = RunConfig(
590
+ # ... other config
591
+ on_event=trace_collector.collect,
592
+ )
593
+ ```
594
+
491
595
  ### Error Handling
492
596
 
493
597
  ```python
@@ -663,7 +767,41 @@ python server_example.py
663
767
  - `POST /chat` - Chat with any agent
664
768
  - `GET /docs` - Interactive API documentation
665
769
 
666
- ### 2. MCP Integration Demo
770
+ ### 2. Agent-as-Tool Demo
771
+
772
+ ```bash
773
+ cd examples
774
+ python agent_as_tool_example.py
775
+
776
+ # Or start as server
777
+ python agent_as_tool_example.py --server
778
+ ```
779
+
780
+ **Features demonstrated:**
781
+ - ✅ Hierarchical agent orchestration
782
+ - ✅ Conditional tool enabling based on context
783
+ - ✅ Custom output extraction from agent tools
784
+ - ✅ Session management for sub-agents
785
+ - ✅ Translation agents working together
786
+
787
+ ### 3. Tracing Integration Demos
788
+
789
+ ```bash
790
+ # OpenTelemetry tracing example
791
+ cd examples
792
+ python otel_tracing_demo.py
793
+
794
+ # Langfuse tracing example
795
+ python langfuse_tracing_demo.py
796
+ ```
797
+
798
+ **Features demonstrated:**
799
+ - ✅ OpenTelemetry distributed tracing setup
800
+ - ✅ Langfuse LLM observability integration
801
+ - ✅ Composite trace collectors
802
+ - ✅ Real-time monitoring and analytics
803
+
804
+ ### 4. MCP Integration Demo
667
805
 
668
806
  ```bash
669
807
  cd examples/mcp_demo
@@ -721,4 +859,4 @@ MIT
721
859
 
722
860
  ---
723
861
 
724
- **JAF (Juspay Agentic Framework) v2.0** - Building the future of functional AI agent systems 🚀
862
+ **JAF (Juspay Agentic Framework) v2.2** - Building the future of functional AI agent systems 🚀
@@ -42,7 +42,7 @@ jaf/core/__init__.py,sha256=rBvP_7TGbJICDJnA7a3qyX8yQErCDWaGAn5WzpyH4gU,1339
42
42
  jaf/core/agent_tool.py,sha256=8TcBuSxGmDTW5F_GhBU_m5S43nYqkjO4qTrNERraAig,11656
43
43
  jaf/core/analytics.py,sha256=NrUfOLLTDIhOzdfc65ZqS9AJ4ZAP9BtNtga69q0YdYw,23265
44
44
  jaf/core/composition.py,sha256=IVxRO1Q9nK7JRH32qQ4p8WMIUu66BhqPNrlTNMGFVwE,26317
45
- jaf/core/engine.py,sha256=wakHo9DcNSVH7N5FC_6vi4Nc0yO15YJpNvNTLyyGM_E,26857
45
+ jaf/core/engine.py,sha256=v6yXp-zJF9Cj_9qlaW1rmCz7_N3YuDU02xxlzl-howM,27122
46
46
  jaf/core/errors.py,sha256=5fwTNhkojKRQ4wZj3lZlgDnAsrYyjYOwXJkIr5EGNUc,5539
47
47
  jaf/core/performance.py,sha256=jedQmTEkrKMD6_Aw1h8PdG-5TsdYSFFT7Or6k5dmN2g,9974
48
48
  jaf/core/proxy.py,sha256=_WM3cpRlSQLYpgSBrnY30UPMe2iZtlqDQ65kppE-WY0,4609
@@ -50,8 +50,8 @@ jaf/core/proxy_helpers.py,sha256=i7a5fAX9rLmO4FMBX51-yRkTFwfWedzQNgnLmeLUd_A,437
50
50
  jaf/core/streaming.py,sha256=c5o9iqpjoYV2LrUpG6qLWCYrWcP-DCcZsvMbyqKunp8,16089
51
51
  jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
52
52
  jaf/core/tools.py,sha256=SbJRRr4y_xxNYNTulZg6OiyNaHBlo_qXWYY510jxQEs,16489
53
- jaf/core/tracing.py,sha256=r96LCi4dV7BSoqVuFMXI7j72G9jOwpbEjnhYqubuQzc,23455
54
- jaf/core/types.py,sha256=RWHkWm18bAFx7SMSuCGi1cQnCmQR9yxkyz-FppDpglM,16868
53
+ jaf/core/tracing.py,sha256=1g5n-jRkSVSuQNONIgip0j3pHOfOnu2CBzoYhiknGo8,31902
54
+ jaf/core/types.py,sha256=9UXrPkepw7opgv1VGbPAC1Zx80RP4-ouRxtb6kVTA-A,17063
55
55
  jaf/core/workflows.py,sha256=Ul-82gzjIXtkhnSMSPv-8igikjkMtW1EBo9yrfodtvI,26294
56
56
  jaf/memory/__init__.py,sha256=-L98xlvihurGAzF0DnXtkueDVvO_wV2XxxEwAWdAj50,1400
57
57
  jaf/memory/factory.py,sha256=Fh6JyvQtCKe38DZV5-NnC9vPRCvzBgSSPFIGaX7Nt5E,2958
@@ -68,7 +68,7 @@ jaf/policies/handoff.py,sha256=KJYYuL9T6v6DECRhnsS2Je6q4Aj9_zC5d_KBnvEnZNE,8318
68
68
  jaf/policies/validation.py,sha256=wn-7ynH10E5nk-_r1_kHIYHrBGmLX0EFr-FUTHrsxvc,10903
69
69
  jaf/providers/__init__.py,sha256=j_o-Rubr8d9tNYlFWb6fvzkxIBl3JKK_iabj9wTFia0,2114
70
70
  jaf/providers/mcp.py,sha256=WxcC8gUFpDBBYyhorMcc1jHq3xMDMBtnwyRPthfL0S0,13074
71
- jaf/providers/model.py,sha256=9kCisPgppBeVKXS7JtXeOyA5791Tdh6z0vcJbENAQNs,7535
71
+ jaf/providers/model.py,sha256=9w2mph6g1TaLOFp3t0VtN2kYEfTa-lyYAVjAqQg3wrU,7748
72
72
  jaf/server/__init__.py,sha256=K0vSgTfzn3oM54UX9BeAROpaksYY43mFtfjSXoQrhXA,339
73
73
  jaf/server/main.py,sha256=CTb0ywbPIq9ELfay5MKChVR7BpIQOoEbPjPfpzo2aBQ,2152
74
74
  jaf/server/server.py,sha256=o-n6dPWmlu4_v5ozVW3BTOm1b5kHBQhqRYlHh5sXL-k,8048
@@ -79,9 +79,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
79
79
  jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
80
80
  jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
81
81
  jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
82
- jaf_py-2.2.3.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
83
- jaf_py-2.2.3.dist-info/METADATA,sha256=IqS2qTIVxsXrYE0GLCKRRVv5H4MjwBCbg0UgWuqsZHE,23150
84
- jaf_py-2.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
- jaf_py-2.2.3.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
86
- jaf_py-2.2.3.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
87
- jaf_py-2.2.3.dist-info/RECORD,,
82
+ jaf_py-2.3.0.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
83
+ jaf_py-2.3.0.dist-info/METADATA,sha256=_MyYA624bGLW5kwGHSXg4jNrbD_PJEd7UGwr2_JTkdA,27613
84
+ jaf_py-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ jaf_py-2.3.0.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
86
+ jaf_py-2.3.0.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
87
+ jaf_py-2.3.0.dist-info/RECORD,,
File without changes