readyagentsdev 0.8.2__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.
- readyagents/__init__.py +38 -0
- readyagents/__main__.py +4 -0
- readyagents/audit.py +67 -0
- readyagents/cli.py +1050 -0
- readyagents/config.py +264 -0
- readyagents/errors.py +129 -0
- readyagents/llm/__init__.py +11 -0
- readyagents/llm/anthropic_provider.py +72 -0
- readyagents/llm/base.py +57 -0
- readyagents/llm/cache.py +86 -0
- readyagents/llm/openai_compat.py +12 -0
- readyagents/llm/openai_provider.py +70 -0
- readyagents/llm/registry.py +112 -0
- readyagents/llm/resilience.py +179 -0
- readyagents/llm/tool_calls.py +286 -0
- readyagents/logging.py +162 -0
- readyagents/mcp/__init__.py +43 -0
- readyagents/mcp/builtin.py +674 -0
- readyagents/mcp/client.py +253 -0
- readyagents/mcp/http.py +585 -0
- readyagents/mcp/run_api.py +1077 -0
- readyagents/mcp/server.py +246 -0
- readyagents/notify.py +63 -0
- readyagents/packs/__init__.py +26 -0
- readyagents/packs/loader.py +157 -0
- readyagents/packs/protocol.py +55 -0
- readyagents/policy.py +127 -0
- readyagents/py.typed +1 -0
- readyagents/report.py +88 -0
- readyagents/scaffold.py +410 -0
- readyagents/secrets.py +120 -0
- readyagents/testing/__init__.py +17 -0
- readyagents/testing/eval.py +219 -0
- readyagents/testing/helpers.py +128 -0
- readyagents/testing/recorded.py +68 -0
- readyagents/tools/__init__.py +67 -0
- readyagents/workflow/__init__.py +3 -0
- readyagents/workflow/cancellation.py +88 -0
- readyagents/workflow/conditions.py +279 -0
- readyagents/workflow/engine.py +354 -0
- readyagents/workflow/nodes.py +944 -0
- readyagents/workflow/runner.py +375 -0
- readyagents/workflow/schema.py +287 -0
- readyagents/workflow/state.py +472 -0
- readyagents/workflow/structured.py +103 -0
- readyagents/workflow/templates.py +125 -0
- readyagentsdev-0.8.2.dist-info/METADATA +215 -0
- readyagentsdev-0.8.2.dist-info/RECORD +51 -0
- readyagentsdev-0.8.2.dist-info/WHEEL +4 -0
- readyagentsdev-0.8.2.dist-info/entry_points.txt +2 -0
- readyagentsdev-0.8.2.dist-info/licenses/LICENSE +201 -0
readyagents/logging.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Process-wide logging setup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
_CONFIGURED = False
|
|
11
|
+
|
|
12
|
+
_FORMAT = "%(asctime)s %(levelname)s %(name)s run=%(run_id)s node=%(node_id)s: %(message)s"
|
|
13
|
+
|
|
14
|
+
_RECORD_SKIP = set(logging.makeLogRecord({}).__dict__) | {
|
|
15
|
+
"message",
|
|
16
|
+
"asctime",
|
|
17
|
+
"msg",
|
|
18
|
+
"args",
|
|
19
|
+
"exc_text",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _RunContextFilter(logging.Filter):
|
|
24
|
+
"""Ensure every record has run_id / node_id so the formatter never KeyErrors."""
|
|
25
|
+
|
|
26
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
27
|
+
if not hasattr(record, "run_id"):
|
|
28
|
+
record.run_id = "-"
|
|
29
|
+
if not hasattr(record, "node_id"):
|
|
30
|
+
record.node_id = "-"
|
|
31
|
+
if not hasattr(record, "event"):
|
|
32
|
+
record.event = "-"
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class JsonLogFormatter(logging.Formatter):
|
|
37
|
+
"""Machine-parseable JSON lines with `run` and `node` on every event."""
|
|
38
|
+
|
|
39
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
40
|
+
payload: dict[str, Any] = {
|
|
41
|
+
"ts": datetime.fromtimestamp(record.created, UTC).isoformat(),
|
|
42
|
+
"level": record.levelname,
|
|
43
|
+
"logger": record.name,
|
|
44
|
+
"run": getattr(record, "run_id", "-"),
|
|
45
|
+
"node": getattr(record, "node_id", "-"),
|
|
46
|
+
"event": getattr(record, "event", "-"),
|
|
47
|
+
"message": record.getMessage(),
|
|
48
|
+
}
|
|
49
|
+
for key, value in record.__dict__.items():
|
|
50
|
+
if key in _RECORD_SKIP or key in {
|
|
51
|
+
"run_id",
|
|
52
|
+
"node_id",
|
|
53
|
+
"event",
|
|
54
|
+
"run",
|
|
55
|
+
"node",
|
|
56
|
+
}:
|
|
57
|
+
continue
|
|
58
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
59
|
+
payload[key] = value
|
|
60
|
+
if record.exc_info:
|
|
61
|
+
payload["exc"] = self.formatException(record.exc_info)
|
|
62
|
+
return json.dumps(payload, ensure_ascii=False)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RedactLogFilter(logging.Filter):
|
|
66
|
+
"""Mask configured PII/secrets in log messages and extra string fields."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, redactor: Any) -> None:
|
|
69
|
+
super().__init__()
|
|
70
|
+
self.redactor = redactor
|
|
71
|
+
|
|
72
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
73
|
+
redact = getattr(self.redactor, "redact_text", None)
|
|
74
|
+
if not callable(redact):
|
|
75
|
+
return True
|
|
76
|
+
if isinstance(record.msg, str):
|
|
77
|
+
record.msg = redact(record.msg)
|
|
78
|
+
if isinstance(record.args, tuple):
|
|
79
|
+
record.args = tuple(redact(a) if isinstance(a, str) else a for a in record.args)
|
|
80
|
+
elif isinstance(record.args, dict):
|
|
81
|
+
record.args = {
|
|
82
|
+
k: redact(v) if isinstance(v, str) else v for k, v in record.args.items()
|
|
83
|
+
}
|
|
84
|
+
for key, value in list(record.__dict__.items()):
|
|
85
|
+
if key in _RECORD_SKIP:
|
|
86
|
+
continue
|
|
87
|
+
if isinstance(value, str):
|
|
88
|
+
setattr(record, key, redact(value))
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def configure_logging(level: str = "INFO", **kwargs: Any) -> None:
|
|
93
|
+
"""Configure the root `readyagents` logger.
|
|
94
|
+
|
|
95
|
+
``fmt`` / ``format`` may be ``text`` (default) or ``json``.
|
|
96
|
+
"""
|
|
97
|
+
global _CONFIGURED
|
|
98
|
+
numeric = getattr(logging, level.upper(), logging.INFO)
|
|
99
|
+
logger = logging.getLogger("readyagents")
|
|
100
|
+
logger.setLevel(numeric)
|
|
101
|
+
fmt_raw = kwargs.pop("fmt", None) or kwargs.pop("format", None)
|
|
102
|
+
formatter: logging.Formatter | None = None
|
|
103
|
+
if fmt_raw is not None:
|
|
104
|
+
fmt = str(fmt_raw).strip().lower()
|
|
105
|
+
if fmt not in {"text", "json"}:
|
|
106
|
+
fmt = "text"
|
|
107
|
+
formatter = JsonLogFormatter() if fmt == "json" else logging.Formatter(_FORMAT)
|
|
108
|
+
redactor = kwargs.pop("redactor", None)
|
|
109
|
+
if not logger.handlers:
|
|
110
|
+
handler = logging.StreamHandler()
|
|
111
|
+
handler.addFilter(_RunContextFilter())
|
|
112
|
+
handler.setFormatter(formatter or logging.Formatter(_FORMAT))
|
|
113
|
+
logger.addHandler(handler)
|
|
114
|
+
logger.propagate = False
|
|
115
|
+
elif formatter is not None:
|
|
116
|
+
for handler in logger.handlers:
|
|
117
|
+
if not any(isinstance(f, _RunContextFilter) for f in handler.filters):
|
|
118
|
+
handler.addFilter(_RunContextFilter())
|
|
119
|
+
handler.setFormatter(formatter)
|
|
120
|
+
else:
|
|
121
|
+
for handler in logger.handlers:
|
|
122
|
+
if not any(isinstance(f, _RunContextFilter) for f in handler.filters):
|
|
123
|
+
handler.addFilter(_RunContextFilter())
|
|
124
|
+
if redactor is not None:
|
|
125
|
+
_install_redactor(logger, redactor)
|
|
126
|
+
_CONFIGURED = True
|
|
127
|
+
if kwargs:
|
|
128
|
+
logger.debug("extra logging kwargs ignored: %s", sorted(kwargs))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _install_redactor(logger: logging.Logger, redactor: Any) -> None:
|
|
132
|
+
for handler in logger.handlers:
|
|
133
|
+
if not any(isinstance(f, RedactLogFilter) for f in handler.filters):
|
|
134
|
+
handler.addFilter(RedactLogFilter(redactor))
|
|
135
|
+
else:
|
|
136
|
+
for filt in handler.filters:
|
|
137
|
+
if isinstance(filt, RedactLogFilter):
|
|
138
|
+
filt.redactor = redactor
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_logger(name: str) -> logging.Logger:
|
|
142
|
+
if not name.startswith("readyagents"):
|
|
143
|
+
name = f"readyagents.{name}"
|
|
144
|
+
return logging.getLogger(name)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def log_event(
|
|
148
|
+
logger: logging.Logger,
|
|
149
|
+
event: str,
|
|
150
|
+
message: str,
|
|
151
|
+
*args: Any,
|
|
152
|
+
run_id: str = "-",
|
|
153
|
+
node_id: str = "-",
|
|
154
|
+
**fields: Any,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Log a structured event. JSON format emits ``event``, ``run``, and ``node``."""
|
|
157
|
+
extra: dict[str, Any] = {"run_id": run_id, "node_id": node_id, "event": event}
|
|
158
|
+
for key, value in fields.items():
|
|
159
|
+
if key in extra or key in _RECORD_SKIP:
|
|
160
|
+
continue
|
|
161
|
+
extra[key] = value
|
|
162
|
+
logger.info(message, *args, extra=extra)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from readyagents.mcp.builtin import builtin_tools
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"builtin_tools",
|
|
5
|
+
"MCPClient",
|
|
6
|
+
"construct_server",
|
|
7
|
+
"mcp_available",
|
|
8
|
+
"streamable_http_app",
|
|
9
|
+
"compose_http_app",
|
|
10
|
+
"serve_streamable_http",
|
|
11
|
+
"resolve_bearer_token",
|
|
12
|
+
"assert_loopback_host",
|
|
13
|
+
"RunCoordinator",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def __getattr__(name: str):
|
|
18
|
+
if name == "MCPClient":
|
|
19
|
+
from readyagents.mcp.client import MCPClient
|
|
20
|
+
|
|
21
|
+
return MCPClient
|
|
22
|
+
if name in {"construct_server", "mcp_available", "streamable_http_app"}:
|
|
23
|
+
from readyagents.mcp.server import construct_server, mcp_available, streamable_http_app
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
"construct_server": construct_server,
|
|
27
|
+
"mcp_available": mcp_available,
|
|
28
|
+
"streamable_http_app": streamable_http_app,
|
|
29
|
+
}[name]
|
|
30
|
+
if name in {
|
|
31
|
+
"compose_http_app",
|
|
32
|
+
"serve_streamable_http",
|
|
33
|
+
"resolve_bearer_token",
|
|
34
|
+
"assert_loopback_host",
|
|
35
|
+
}:
|
|
36
|
+
from readyagents.mcp import http as mcp_http
|
|
37
|
+
|
|
38
|
+
return getattr(mcp_http, name)
|
|
39
|
+
if name == "RunCoordinator":
|
|
40
|
+
from readyagents.mcp.run_api import RunCoordinator
|
|
41
|
+
|
|
42
|
+
return RunCoordinator
|
|
43
|
+
raise AttributeError(name)
|