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.
Files changed (51) hide show
  1. readyagents/__init__.py +38 -0
  2. readyagents/__main__.py +4 -0
  3. readyagents/audit.py +67 -0
  4. readyagents/cli.py +1050 -0
  5. readyagents/config.py +264 -0
  6. readyagents/errors.py +129 -0
  7. readyagents/llm/__init__.py +11 -0
  8. readyagents/llm/anthropic_provider.py +72 -0
  9. readyagents/llm/base.py +57 -0
  10. readyagents/llm/cache.py +86 -0
  11. readyagents/llm/openai_compat.py +12 -0
  12. readyagents/llm/openai_provider.py +70 -0
  13. readyagents/llm/registry.py +112 -0
  14. readyagents/llm/resilience.py +179 -0
  15. readyagents/llm/tool_calls.py +286 -0
  16. readyagents/logging.py +162 -0
  17. readyagents/mcp/__init__.py +43 -0
  18. readyagents/mcp/builtin.py +674 -0
  19. readyagents/mcp/client.py +253 -0
  20. readyagents/mcp/http.py +585 -0
  21. readyagents/mcp/run_api.py +1077 -0
  22. readyagents/mcp/server.py +246 -0
  23. readyagents/notify.py +63 -0
  24. readyagents/packs/__init__.py +26 -0
  25. readyagents/packs/loader.py +157 -0
  26. readyagents/packs/protocol.py +55 -0
  27. readyagents/policy.py +127 -0
  28. readyagents/py.typed +1 -0
  29. readyagents/report.py +88 -0
  30. readyagents/scaffold.py +410 -0
  31. readyagents/secrets.py +120 -0
  32. readyagents/testing/__init__.py +17 -0
  33. readyagents/testing/eval.py +219 -0
  34. readyagents/testing/helpers.py +128 -0
  35. readyagents/testing/recorded.py +68 -0
  36. readyagents/tools/__init__.py +67 -0
  37. readyagents/workflow/__init__.py +3 -0
  38. readyagents/workflow/cancellation.py +88 -0
  39. readyagents/workflow/conditions.py +279 -0
  40. readyagents/workflow/engine.py +354 -0
  41. readyagents/workflow/nodes.py +944 -0
  42. readyagents/workflow/runner.py +375 -0
  43. readyagents/workflow/schema.py +287 -0
  44. readyagents/workflow/state.py +472 -0
  45. readyagents/workflow/structured.py +103 -0
  46. readyagents/workflow/templates.py +125 -0
  47. readyagentsdev-0.8.2.dist-info/METADATA +215 -0
  48. readyagentsdev-0.8.2.dist-info/RECORD +51 -0
  49. readyagentsdev-0.8.2.dist-info/WHEEL +4 -0
  50. readyagentsdev-0.8.2.dist-info/entry_points.txt +2 -0
  51. 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)