jaf-py 2.2.2__py3-none-any.whl → 2.2.4__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/__init__.py +5 -0
- jaf/core/engine.py +6 -2
- jaf/core/proxy.py +141 -0
- jaf/core/proxy_helpers.py +126 -0
- jaf/core/types.py +1 -0
- jaf/providers/model.py +27 -6
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/METADATA +144 -5
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/RECORD +12 -10
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/WHEEL +0 -0
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.2.2.dist-info → jaf_py-2.2.4.dist-info}/top_level.txt +0 -0
jaf/core/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ from .agent_tool import (
|
|
|
13
13
|
get_current_run_config,
|
|
14
14
|
set_current_run_config,
|
|
15
15
|
)
|
|
16
|
+
from .proxy import ProxyConfig, ProxyAuth, create_proxy_config, get_default_proxy_config
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
18
19
|
"Agent",
|
|
@@ -22,6 +23,8 @@ __all__ = [
|
|
|
22
23
|
"Message",
|
|
23
24
|
"ModelConfig",
|
|
24
25
|
"ModelProvider",
|
|
26
|
+
"ProxyAuth",
|
|
27
|
+
"ProxyConfig",
|
|
25
28
|
"RunConfig",
|
|
26
29
|
"RunId",
|
|
27
30
|
"RunResult",
|
|
@@ -39,9 +42,11 @@ __all__ = [
|
|
|
39
42
|
"create_conditional_enabler",
|
|
40
43
|
"create_default_output_extractor",
|
|
41
44
|
"create_json_output_extractor",
|
|
45
|
+
"create_proxy_config",
|
|
42
46
|
"create_run_id",
|
|
43
47
|
"create_trace_id",
|
|
44
48
|
"get_current_run_config",
|
|
49
|
+
"get_default_proxy_config",
|
|
45
50
|
"require_permissions",
|
|
46
51
|
"run",
|
|
47
52
|
"set_current_run_config",
|
jaf/core/engine.py
CHANGED
|
@@ -91,7 +91,11 @@ 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
|
+
))))
|
|
95
99
|
|
|
96
100
|
state_with_memory = await _load_conversation_history(initial_state, config)
|
|
97
101
|
result = await _run_internal(state_with_memory, config)
|
|
@@ -631,7 +635,7 @@ async def _execute_tool_calls(
|
|
|
631
635
|
result=result_string,
|
|
632
636
|
trace_id=state.trace_id,
|
|
633
637
|
run_id=state.run_id,
|
|
634
|
-
tool_result=
|
|
638
|
+
tool_result=tool_result,
|
|
635
639
|
status='success'
|
|
636
640
|
))))
|
|
637
641
|
|
jaf/core/proxy.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proxy configuration for JAF agents and HTTP clients.
|
|
3
|
+
|
|
4
|
+
This module provides unified proxy configuration that can be used across
|
|
5
|
+
different HTTP clients (httpx, OpenAI, etc.) in the JAF framework.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional, Dict, Any, Union
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ProxyAuth:
|
|
16
|
+
"""Proxy authentication configuration."""
|
|
17
|
+
username: str
|
|
18
|
+
password: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ProxyConfig:
|
|
23
|
+
"""Proxy configuration for HTTP clients."""
|
|
24
|
+
http_proxy: Optional[str] = None
|
|
25
|
+
https_proxy: Optional[str] = None
|
|
26
|
+
no_proxy: Optional[str] = None
|
|
27
|
+
auth: Optional[ProxyAuth] = None
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_environment(cls) -> 'ProxyConfig':
|
|
31
|
+
"""Create proxy configuration from environment variables."""
|
|
32
|
+
return cls(
|
|
33
|
+
http_proxy=os.getenv('HTTP_PROXY') or os.getenv('http_proxy'),
|
|
34
|
+
https_proxy=os.getenv('HTTPS_PROXY') or os.getenv('https_proxy'),
|
|
35
|
+
no_proxy=os.getenv('NO_PROXY') or os.getenv('no_proxy'),
|
|
36
|
+
auth=ProxyAuth(
|
|
37
|
+
username=os.getenv('PROXY_USERNAME', ''),
|
|
38
|
+
password=os.getenv('PROXY_PASSWORD', '')
|
|
39
|
+
) if os.getenv('PROXY_USERNAME') else None
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_url(cls, proxy_url: str, auth: Optional[ProxyAuth] = None) -> 'ProxyConfig':
|
|
44
|
+
"""Create proxy configuration from a single URL."""
|
|
45
|
+
return cls(
|
|
46
|
+
http_proxy=proxy_url,
|
|
47
|
+
https_proxy=proxy_url,
|
|
48
|
+
auth=auth
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def to_httpx_proxies(self) -> Optional[Dict[str, str]]:
|
|
52
|
+
"""Convert to httpx proxies format."""
|
|
53
|
+
if not self.http_proxy and not self.https_proxy:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
proxies = {}
|
|
57
|
+
|
|
58
|
+
if self.http_proxy:
|
|
59
|
+
proxies['http://'] = self._add_auth_to_url(self.http_proxy)
|
|
60
|
+
|
|
61
|
+
if self.https_proxy:
|
|
62
|
+
proxies['https://'] = self._add_auth_to_url(self.https_proxy)
|
|
63
|
+
|
|
64
|
+
return proxies if proxies else None
|
|
65
|
+
|
|
66
|
+
def to_openai_proxies(self) -> Optional[Dict[str, str]]:
|
|
67
|
+
"""Convert to OpenAI client proxies format."""
|
|
68
|
+
# OpenAI client supports httpx-style proxy configuration
|
|
69
|
+
return self.to_httpx_proxies()
|
|
70
|
+
|
|
71
|
+
def _add_auth_to_url(self, url: str) -> str:
|
|
72
|
+
"""Add authentication to proxy URL if configured."""
|
|
73
|
+
if not self.auth or not self.auth.username:
|
|
74
|
+
return url
|
|
75
|
+
|
|
76
|
+
parsed = urlparse(url)
|
|
77
|
+
|
|
78
|
+
# If URL already has auth, don't override
|
|
79
|
+
if '@' in parsed.netloc:
|
|
80
|
+
return url
|
|
81
|
+
|
|
82
|
+
auth_string = f"{self.auth.username}:{self.auth.password}"
|
|
83
|
+
|
|
84
|
+
# Reconstruct URL with auth
|
|
85
|
+
if parsed.port:
|
|
86
|
+
netloc = f"{auth_string}@{parsed.hostname}:{parsed.port}"
|
|
87
|
+
else:
|
|
88
|
+
netloc = f"{auth_string}@{parsed.hostname}"
|
|
89
|
+
|
|
90
|
+
return f"{parsed.scheme}://{netloc}{parsed.path}"
|
|
91
|
+
|
|
92
|
+
def should_bypass_proxy(self, host: str) -> bool:
|
|
93
|
+
"""Check if a host should bypass the proxy based on no_proxy settings."""
|
|
94
|
+
if not self.no_proxy:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
no_proxy_hosts = [h.strip() for h in self.no_proxy.split(',')]
|
|
98
|
+
|
|
99
|
+
for no_proxy_host in no_proxy_hosts:
|
|
100
|
+
if not no_proxy_host:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Exact match
|
|
104
|
+
if host == no_proxy_host:
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
# Wildcard match (e.g., *.example.com)
|
|
108
|
+
if no_proxy_host.startswith('*'):
|
|
109
|
+
suffix = no_proxy_host[1:]
|
|
110
|
+
if host.endswith(suffix):
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
# Domain suffix match
|
|
114
|
+
if no_proxy_host.startswith('.'):
|
|
115
|
+
if host.endswith(no_proxy_host) or host == no_proxy_host[1:]:
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def create_proxy_config(
|
|
122
|
+
proxy_url: Optional[str] = None,
|
|
123
|
+
username: Optional[str] = None,
|
|
124
|
+
password: Optional[str] = None,
|
|
125
|
+
no_proxy: Optional[str] = None
|
|
126
|
+
) -> ProxyConfig:
|
|
127
|
+
"""Create a proxy configuration with optional parameters."""
|
|
128
|
+
auth = ProxyAuth(username, password) if username else None
|
|
129
|
+
|
|
130
|
+
if proxy_url:
|
|
131
|
+
config = ProxyConfig.from_url(proxy_url, auth)
|
|
132
|
+
if no_proxy:
|
|
133
|
+
config.no_proxy = no_proxy
|
|
134
|
+
return config
|
|
135
|
+
|
|
136
|
+
return ProxyConfig.from_environment()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_default_proxy_config() -> ProxyConfig:
|
|
140
|
+
"""Get the default proxy configuration from environment variables."""
|
|
141
|
+
return ProxyConfig.from_environment()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for creating JAF core components with proxy support.
|
|
3
|
+
|
|
4
|
+
This module provides convenience functions to easily create model providers
|
|
5
|
+
with proxy configuration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
from .proxy import ProxyConfig, get_default_proxy_config
|
|
10
|
+
from ..providers.model import make_litellm_provider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_proxy_enabled_litellm_provider(
|
|
14
|
+
base_url: str,
|
|
15
|
+
api_key: str = "anything",
|
|
16
|
+
default_timeout: Optional[float] = None,
|
|
17
|
+
proxy_url: Optional[str] = None,
|
|
18
|
+
proxy_username: Optional[str] = None,
|
|
19
|
+
proxy_password: Optional[str] = None,
|
|
20
|
+
use_env_proxy: bool = True
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Create a LiteLLM provider with proxy support.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
base_url: Base URL for the LiteLLM server
|
|
27
|
+
api_key: API key (defaults to "anything" for local servers)
|
|
28
|
+
default_timeout: Default timeout for model API calls in seconds
|
|
29
|
+
proxy_url: Proxy URL (if not using environment variables)
|
|
30
|
+
proxy_username: Proxy username for authentication
|
|
31
|
+
proxy_password: Proxy password for authentication
|
|
32
|
+
use_env_proxy: Whether to use proxy settings from environment variables
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
ModelProvider with proxy configuration
|
|
36
|
+
"""
|
|
37
|
+
proxy_config = None
|
|
38
|
+
|
|
39
|
+
if proxy_url:
|
|
40
|
+
# Use explicitly provided proxy settings
|
|
41
|
+
from .proxy import ProxyAuth, create_proxy_config
|
|
42
|
+
proxy_config = create_proxy_config(
|
|
43
|
+
proxy_url=proxy_url,
|
|
44
|
+
username=proxy_username,
|
|
45
|
+
password=proxy_password
|
|
46
|
+
)
|
|
47
|
+
elif use_env_proxy:
|
|
48
|
+
# Use environment-based proxy settings
|
|
49
|
+
proxy_config = get_default_proxy_config()
|
|
50
|
+
# Only use proxy if actually configured in environment
|
|
51
|
+
if not proxy_config.http_proxy and not proxy_config.https_proxy:
|
|
52
|
+
proxy_config = None
|
|
53
|
+
|
|
54
|
+
return make_litellm_provider(
|
|
55
|
+
base_url=base_url,
|
|
56
|
+
api_key=api_key,
|
|
57
|
+
default_timeout=default_timeout,
|
|
58
|
+
proxy_config=proxy_config
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_proxy_info() -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Get information about current proxy configuration from environment.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dictionary with proxy configuration details
|
|
70
|
+
"""
|
|
71
|
+
proxy_config = get_default_proxy_config()
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"http_proxy": proxy_config.http_proxy,
|
|
75
|
+
"https_proxy": proxy_config.https_proxy,
|
|
76
|
+
"no_proxy": proxy_config.no_proxy,
|
|
77
|
+
"has_auth": proxy_config.auth is not None,
|
|
78
|
+
"auth_username": proxy_config.auth.username if proxy_config.auth else None,
|
|
79
|
+
"is_configured": bool(proxy_config.http_proxy or proxy_config.https_proxy)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def validate_proxy_config(proxy_config: ProxyConfig) -> Dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Validate proxy configuration and return validation results.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
proxy_config: Proxy configuration to validate
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with validation results
|
|
92
|
+
"""
|
|
93
|
+
results = {
|
|
94
|
+
"valid": True,
|
|
95
|
+
"warnings": [],
|
|
96
|
+
"errors": []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Check if at least one proxy is configured
|
|
100
|
+
if not proxy_config.http_proxy and not proxy_config.https_proxy:
|
|
101
|
+
results["warnings"].append("No proxy URLs configured")
|
|
102
|
+
|
|
103
|
+
# Validate proxy URLs if configured
|
|
104
|
+
for proxy_type, proxy_url in [("HTTP", proxy_config.http_proxy), ("HTTPS", proxy_config.https_proxy)]:
|
|
105
|
+
if proxy_url:
|
|
106
|
+
try:
|
|
107
|
+
from urllib.parse import urlparse
|
|
108
|
+
parsed = urlparse(proxy_url)
|
|
109
|
+
if not parsed.scheme:
|
|
110
|
+
results["errors"].append(f"{proxy_type} proxy URL missing scheme: {proxy_url}")
|
|
111
|
+
results["valid"] = False
|
|
112
|
+
if not parsed.netloc:
|
|
113
|
+
results["errors"].append(f"{proxy_type} proxy URL missing host: {proxy_url}")
|
|
114
|
+
results["valid"] = False
|
|
115
|
+
except Exception as e:
|
|
116
|
+
results["errors"].append(f"Invalid {proxy_type} proxy URL: {e}")
|
|
117
|
+
results["valid"] = False
|
|
118
|
+
|
|
119
|
+
# Check authentication consistency
|
|
120
|
+
if proxy_config.auth:
|
|
121
|
+
if not proxy_config.auth.username:
|
|
122
|
+
results["warnings"].append("Proxy authentication configured but username is empty")
|
|
123
|
+
if not proxy_config.auth.password:
|
|
124
|
+
results["warnings"].append("Proxy authentication configured but password is empty")
|
|
125
|
+
|
|
126
|
+
return results
|
jaf/core/types.py
CHANGED
jaf/providers/model.py
CHANGED
|
@@ -6,18 +6,21 @@ starting with LiteLLM for multi-provider support.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from typing import Any, Dict, Optional, TypeVar
|
|
9
|
+
import httpx
|
|
9
10
|
|
|
10
11
|
from openai import OpenAI
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
|
|
13
14
|
from ..core.types import Agent, ContentRole, Message, ModelProvider, RunConfig, RunState
|
|
15
|
+
from ..core.proxy import ProxyConfig
|
|
14
16
|
|
|
15
17
|
Ctx = TypeVar('Ctx')
|
|
16
18
|
|
|
17
19
|
def make_litellm_provider(
|
|
18
20
|
base_url: str,
|
|
19
21
|
api_key: str = "anything",
|
|
20
|
-
default_timeout: Optional[float] = None
|
|
22
|
+
default_timeout: Optional[float] = None,
|
|
23
|
+
proxy_config: Optional[ProxyConfig] = None
|
|
21
24
|
) -> ModelProvider[Ctx]:
|
|
22
25
|
"""
|
|
23
26
|
Create a LiteLLM-compatible model provider.
|
|
@@ -26,6 +29,7 @@ def make_litellm_provider(
|
|
|
26
29
|
base_url: Base URL for the LiteLLM server
|
|
27
30
|
api_key: API key (defaults to "anything" for local servers)
|
|
28
31
|
default_timeout: Default timeout for model API calls in seconds
|
|
32
|
+
proxy_config: Optional proxy configuration
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
ModelProvider instance
|
|
@@ -35,11 +39,28 @@ def make_litellm_provider(
|
|
|
35
39
|
def __init__(self):
|
|
36
40
|
# Default to "anything" if api_key is not provided, for local servers
|
|
37
41
|
effective_api_key = api_key if api_key is not None else "anything"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Configure HTTP client with proxy support
|
|
44
|
+
client_kwargs = {
|
|
45
|
+
"base_url": base_url,
|
|
46
|
+
"api_key": effective_api_key,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if proxy_config:
|
|
50
|
+
proxies = proxy_config.to_httpx_proxies()
|
|
51
|
+
if proxies:
|
|
52
|
+
# Create httpx client with proxy configuration
|
|
53
|
+
try:
|
|
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
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"Warning: Could not configure proxy: {e}")
|
|
61
|
+
# Fall back to environment variables for proxy
|
|
62
|
+
|
|
63
|
+
self.client = OpenAI(**client_kwargs)
|
|
43
64
|
self.default_timeout = default_timeout
|
|
44
65
|
|
|
45
66
|
async def get_completion(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaf-py
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
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
|
|
|
@@ -110,18 +110,27 @@ A purely functional agent framework with immutable state and composable tools, p
|
|
|
110
110
|
- ✅ **Output Guardrails**: Response sanitization
|
|
111
111
|
- ✅ **Permission System**: Role-based access control
|
|
112
112
|
- ✅ **Audit Logging**: Complete interaction tracing
|
|
113
|
+
- ✅ **Proxy Support**: Corporate proxy integration with authentication
|
|
113
114
|
|
|
114
115
|
### 📊 **Observability & Monitoring**
|
|
115
116
|
- ✅ **Real-time Tracing**: Event-driven observability
|
|
117
|
+
- ✅ **OpenTelemetry Integration**: Distributed tracing with OTLP
|
|
118
|
+
- ✅ **Langfuse Tracing**: LLM observability and analytics
|
|
116
119
|
- ✅ **Structured Logging**: JSON-formatted logs
|
|
117
120
|
- ✅ **Error Handling**: Comprehensive error types and recovery
|
|
118
121
|
- ✅ **Performance Metrics**: Built-in timing and counters
|
|
119
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
|
+
|
|
120
129
|
### 🔧 **Developer Experience**
|
|
121
130
|
- ✅ **CLI Tools**: Project initialization and management
|
|
122
131
|
- ✅ **Hot Reload**: Development server with auto-reload
|
|
123
132
|
- ✅ **Type Hints**: Full mypy compatibility
|
|
124
|
-
- ✅ **Rich Examples**: RAG, multi-agent, and server demos
|
|
133
|
+
- ✅ **Rich Examples**: RAG, multi-agent, agent-as-tool, and server demos
|
|
125
134
|
- ✅ **Visual Architecture**: Graphviz-powered agent and tool diagrams
|
|
126
135
|
|
|
127
136
|
## 🎯 Core Philosophy
|
|
@@ -147,6 +156,7 @@ pip install "jaf-py[all] @ git+https://github.com/xynehq/jaf-py.git"
|
|
|
147
156
|
pip install "jaf-py[server] @ git+https://github.com/xynehq/jaf-py.git" # FastAPI server support
|
|
148
157
|
pip install "jaf-py[memory] @ git+https://github.com/xynehq/jaf-py.git" # Redis/PostgreSQL memory providers
|
|
149
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
|
|
150
160
|
pip install "jaf-py[dev] @ git+https://github.com/xynehq/jaf-py.git" # Development tools
|
|
151
161
|
```
|
|
152
162
|
|
|
@@ -211,6 +221,7 @@ For offline access, documentation is also available in the [`docs/`](docs/) dire
|
|
|
211
221
|
- **[🔧 Tools Guide](docs/tools.md)** - Creating and using tools
|
|
212
222
|
- **[💾 Memory System](docs/memory-system.md)** - Persistence and memory providers
|
|
213
223
|
- **[🤖 Model Providers](docs/model-providers.md)** - LiteLLM integration
|
|
224
|
+
- **[📊 Monitoring](docs/monitoring.md)** - Observability, metrics, and alerting
|
|
214
225
|
- **[🌐 Server API](docs/server-api.md)** - FastAPI endpoints reference
|
|
215
226
|
- **[📦 Deployment](docs/deployment.md)** - Production deployment guide
|
|
216
227
|
- **[🎮 Examples](docs/examples.md)** - Detailed example walkthroughs
|
|
@@ -438,6 +449,58 @@ config = RunConfig(
|
|
|
438
449
|
)
|
|
439
450
|
```
|
|
440
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
|
+
|
|
441
504
|
## 🔗 Agent Handoffs
|
|
442
505
|
|
|
443
506
|
```python
|
|
@@ -487,6 +550,48 @@ config = RunConfig(
|
|
|
487
550
|
)
|
|
488
551
|
```
|
|
489
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
|
+
|
|
490
595
|
### Error Handling
|
|
491
596
|
|
|
492
597
|
```python
|
|
@@ -662,7 +767,41 @@ python server_example.py
|
|
|
662
767
|
- `POST /chat` - Chat with any agent
|
|
663
768
|
- `GET /docs` - Interactive API documentation
|
|
664
769
|
|
|
665
|
-
### 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
|
|
666
805
|
|
|
667
806
|
```bash
|
|
668
807
|
cd examples/mcp_demo
|
|
@@ -720,4 +859,4 @@ MIT
|
|
|
720
859
|
|
|
721
860
|
---
|
|
722
861
|
|
|
723
|
-
**JAF (Juspay Agentic Framework) v2.
|
|
862
|
+
**JAF (Juspay Agentic Framework) v2.2** - Building the future of functional AI agent systems 🚀
|
|
@@ -38,18 +38,20 @@ jaf/a2a/tests/test_client.py,sha256=L5h7DtQRVlULiRhRLtrmaCoYdvmbXsgLTy3QQ6KgmNM,
|
|
|
38
38
|
jaf/a2a/tests/test_integration.py,sha256=I7LdgwN99mAOljM9kYtK7dGMMntTSWKMw_oLOcJjinU,18454
|
|
39
39
|
jaf/a2a/tests/test_protocol.py,sha256=He3vGlBfIazpppAnuSybutrvjIN3VGxEleAohrVd9hc,23287
|
|
40
40
|
jaf/a2a/tests/test_types.py,sha256=PgRjDVJrHSXuu05z0B5lsSUUY5qEdQLFJbLBIExyVgI,18384
|
|
41
|
-
jaf/core/__init__.py,sha256=
|
|
41
|
+
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=ydlF2m9GWyHXWAP6dng8sOwxEWcaaH8etkImorfY0aY,26954
|
|
46
46
|
jaf/core/errors.py,sha256=5fwTNhkojKRQ4wZj3lZlgDnAsrYyjYOwXJkIr5EGNUc,5539
|
|
47
47
|
jaf/core/performance.py,sha256=jedQmTEkrKMD6_Aw1h8PdG-5TsdYSFFT7Or6k5dmN2g,9974
|
|
48
|
+
jaf/core/proxy.py,sha256=_WM3cpRlSQLYpgSBrnY30UPMe2iZtlqDQ65kppE-WY0,4609
|
|
49
|
+
jaf/core/proxy_helpers.py,sha256=i7a5fAX9rLmO4FMBX51-yRkTFwfWedzQNgnLmeLUd_A,4370
|
|
48
50
|
jaf/core/streaming.py,sha256=c5o9iqpjoYV2LrUpG6qLWCYrWcP-DCcZsvMbyqKunp8,16089
|
|
49
51
|
jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
|
|
50
52
|
jaf/core/tools.py,sha256=SbJRRr4y_xxNYNTulZg6OiyNaHBlo_qXWYY510jxQEs,16489
|
|
51
53
|
jaf/core/tracing.py,sha256=r96LCi4dV7BSoqVuFMXI7j72G9jOwpbEjnhYqubuQzc,23455
|
|
52
|
-
jaf/core/types.py,sha256=
|
|
54
|
+
jaf/core/types.py,sha256=fF7_UMFTPUzGQQxZ9tGoWdjF5LSI02RHoJ9vvSe3W8I,16905
|
|
53
55
|
jaf/core/workflows.py,sha256=Ul-82gzjIXtkhnSMSPv-8igikjkMtW1EBo9yrfodtvI,26294
|
|
54
56
|
jaf/memory/__init__.py,sha256=-L98xlvihurGAzF0DnXtkueDVvO_wV2XxxEwAWdAj50,1400
|
|
55
57
|
jaf/memory/factory.py,sha256=Fh6JyvQtCKe38DZV5-NnC9vPRCvzBgSSPFIGaX7Nt5E,2958
|
|
@@ -66,7 +68,7 @@ jaf/policies/handoff.py,sha256=KJYYuL9T6v6DECRhnsS2Je6q4Aj9_zC5d_KBnvEnZNE,8318
|
|
|
66
68
|
jaf/policies/validation.py,sha256=wn-7ynH10E5nk-_r1_kHIYHrBGmLX0EFr-FUTHrsxvc,10903
|
|
67
69
|
jaf/providers/__init__.py,sha256=j_o-Rubr8d9tNYlFWb6fvzkxIBl3JKK_iabj9wTFia0,2114
|
|
68
70
|
jaf/providers/mcp.py,sha256=WxcC8gUFpDBBYyhorMcc1jHq3xMDMBtnwyRPthfL0S0,13074
|
|
69
|
-
jaf/providers/model.py,sha256=
|
|
71
|
+
jaf/providers/model.py,sha256=9w2mph6g1TaLOFp3t0VtN2kYEfTa-lyYAVjAqQg3wrU,7748
|
|
70
72
|
jaf/server/__init__.py,sha256=K0vSgTfzn3oM54UX9BeAROpaksYY43mFtfjSXoQrhXA,339
|
|
71
73
|
jaf/server/main.py,sha256=CTb0ywbPIq9ELfay5MKChVR7BpIQOoEbPjPfpzo2aBQ,2152
|
|
72
74
|
jaf/server/server.py,sha256=o-n6dPWmlu4_v5ozVW3BTOm1b5kHBQhqRYlHh5sXL-k,8048
|
|
@@ -77,9 +79,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
|
|
|
77
79
|
jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
|
|
78
80
|
jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
|
|
79
81
|
jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
|
|
80
|
-
jaf_py-2.2.
|
|
81
|
-
jaf_py-2.2.
|
|
82
|
-
jaf_py-2.2.
|
|
83
|
-
jaf_py-2.2.
|
|
84
|
-
jaf_py-2.2.
|
|
85
|
-
jaf_py-2.2.
|
|
82
|
+
jaf_py-2.2.4.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
|
|
83
|
+
jaf_py-2.2.4.dist-info/METADATA,sha256=LRWLY4HHpn57BSrHesTA1i5gXSP95zA7Wt1vjIx5P7w,27613
|
|
84
|
+
jaf_py-2.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
85
|
+
jaf_py-2.2.4.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
|
|
86
|
+
jaf_py-2.2.4.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
|
|
87
|
+
jaf_py-2.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|