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 +10 -2
- jaf/core/tracing.py +187 -37
- jaf/core/types.py +5 -0
- jaf/providers/model.py +5 -2
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/METADATA +143 -5
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/RECORD +10 -10
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/WHEEL +0 -0
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.2.3.dist-info → jaf_py-2.3.0.dist-info}/top_level.txt +0 -0
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(
|
|
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=
|
|
366
|
-
metadata={
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
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
|
-
#
|
|
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":
|
|
414
|
-
"output":
|
|
415
|
-
"total":
|
|
519
|
+
"input": prompt_tokens,
|
|
520
|
+
"output": completion_tokens,
|
|
521
|
+
"total": total_tokens,
|
|
416
522
|
"unit": "TOKENS"
|
|
417
523
|
}
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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-{
|
|
432
|
-
input=
|
|
433
|
-
metadata={
|
|
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: {
|
|
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
|
-
|
|
443
|
-
|
|
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(
|
|
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,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
<!--  -->
|
|
75
75
|
|
|
76
|
-
[](https://github.com/xynehq/jaf-py)
|
|
77
77
|
[](https://www.python.org/)
|
|
78
78
|
[](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.
|
|
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.
|
|
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=
|
|
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=
|
|
54
|
-
jaf/core/types.py,sha256=
|
|
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=
|
|
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.
|
|
83
|
-
jaf_py-2.
|
|
84
|
-
jaf_py-2.
|
|
85
|
-
jaf_py-2.
|
|
86
|
-
jaf_py-2.
|
|
87
|
-
jaf_py-2.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|