dreadnode 1.14.0__tar.gz → 1.15.0__tar.gz

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 (211) hide show
  1. {dreadnode-1.14.0 → dreadnode-1.15.0}/PKG-INFO +6 -5
  2. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/__init__.py +7 -0
  3. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/__init__.py +3 -0
  4. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/agent.py +72 -5
  5. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/events.py +3 -0
  6. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/hooks/__init__.py +3 -0
  7. dreadnode-1.15.0/dreadnode/agent/hooks/backoff.py +134 -0
  8. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/reactions.py +1 -1
  9. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/__init__.py +10 -0
  10. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/base.py +4 -0
  11. dreadnode-1.15.0/dreadnode/airt/__init__.py +29 -0
  12. dreadnode-1.15.0/dreadnode/airt/attack/__init__.py +19 -0
  13. dreadnode-1.15.0/dreadnode/airt/attack/hop_skip_jump.py +89 -0
  14. dreadnode-1.15.0/dreadnode/airt/attack/nes.py +86 -0
  15. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/attack/prompt.py +3 -3
  16. dreadnode-1.15.0/dreadnode/airt/attack/simba.py +78 -0
  17. dreadnode-1.15.0/dreadnode/airt/attack/zoo.py +84 -0
  18. dreadnode-1.15.0/dreadnode/airt/search/__init__.py +6 -0
  19. dreadnode-1.15.0/dreadnode/airt/search/hop_skip_jump.py +242 -0
  20. dreadnode-1.15.0/dreadnode/airt/search/image_utils.py +96 -0
  21. dreadnode-1.15.0/dreadnode/airt/search/nes.py +173 -0
  22. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/search/simba.py +30 -9
  23. dreadnode-1.15.0/dreadnode/airt/search/zoo.py +313 -0
  24. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/target/llm.py +1 -1
  25. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/api/client.py +5 -23
  26. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/agent/cli.py +25 -14
  27. dreadnode-1.15.0/dreadnode/cli/attack/__init__.py +3 -0
  28. dreadnode-1.15.0/dreadnode/cli/attack/cli.py +164 -0
  29. dreadnode-1.15.0/dreadnode/cli/docker.py +387 -0
  30. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/eval/cli.py +25 -21
  31. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/github.py +23 -31
  32. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/main.py +93 -44
  33. dreadnode-1.15.0/dreadnode/cli/platform/cli.py +291 -0
  34. dreadnode-1.15.0/dreadnode/cli/platform/compose.py +212 -0
  35. dreadnode-1.15.0/dreadnode/cli/platform/constants.py +16 -0
  36. dreadnode-1.15.0/dreadnode/cli/platform/download.py +95 -0
  37. {dreadnode-1.14.0/dreadnode/cli/platform/utils → dreadnode-1.15.0/dreadnode/cli/platform}/env_mgmt.py +13 -10
  38. dreadnode-1.15.0/dreadnode/cli/platform/tag.py +40 -0
  39. dreadnode-1.15.0/dreadnode/cli/platform/version.py +216 -0
  40. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/profile/cli.py +16 -14
  41. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/shared.py +6 -3
  42. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/study/cli.py +32 -28
  43. dreadnode-1.15.0/dreadnode/cli/task/__init__.py +3 -0
  44. dreadnode-1.15.0/dreadnode/cli/task/cli.py +164 -0
  45. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/image.py +17 -1
  46. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/discovery.py +21 -7
  47. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/console.py +6 -7
  48. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/dataset.py +1 -1
  49. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/eval.py +156 -42
  50. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/events.py +1 -3
  51. dreadnode-1.15.0/dreadnode/eval/format.py +338 -0
  52. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/result.py +49 -11
  53. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/sample.py +6 -2
  54. dreadnode-1.15.0/dreadnode/format.py +60 -0
  55. dreadnode-1.15.0/dreadnode/logging_.py +80 -0
  56. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/main.py +26 -6
  57. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/meta/config.py +63 -13
  58. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/meta/context.py +14 -9
  59. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/meta/hydrate.py +14 -8
  60. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/meta/introspect.py +153 -51
  61. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/console.py +129 -126
  62. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/events.py +1 -5
  63. dreadnode-1.15.0/dreadnode/optimization/format.py +157 -0
  64. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/result.py +17 -7
  65. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/sampling.py +128 -0
  66. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/boundary.py +34 -17
  67. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/graph.py +16 -4
  68. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/optuna_.py +8 -0
  69. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/random.py +39 -11
  70. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/stop.py +2 -16
  71. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/study.py +238 -80
  72. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/trial.py +66 -8
  73. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/base.py +8 -7
  74. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/image.py +8 -7
  75. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/json.py +4 -2
  76. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/similarity.py +2 -2
  77. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/task.py +122 -93
  78. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/tracing/span.py +6 -1
  79. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/image.py +3 -3
  80. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/user_config.py +2 -2
  81. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/util.py +127 -64
  82. dreadnode-1.15.0/examples/airt/beam_search.ipynb +57 -0
  83. dreadnode-1.15.0/examples/airt/graph_of_attacks_with_pruning.ipynb +179 -0
  84. dreadnode-1.15.0/examples/airt/tap_vs_goat_eval.ipynb +2303 -0
  85. dreadnode-1.15.0/examples/airt/tree_of_attacks_with_pruning.ipynb +181 -0
  86. dreadnode-1.15.0/examples/data_export.ipynb +133 -0
  87. dreadnode-1.15.0/examples/log_artifact.ipynb +182 -0
  88. dreadnode-1.15.0/examples/log_object/audio.ipynb +191 -0
  89. dreadnode-1.15.0/examples/log_object/image.ipynb +290 -0
  90. dreadnode-1.15.0/examples/log_object/object3d.ipynb +109 -0
  91. dreadnode-1.15.0/examples/log_object/table.ipynb +157 -0
  92. dreadnode-1.15.0/examples/log_object/video.ipynb +242 -0
  93. dreadnode-1.15.0/examples/model_training.ipynb +118 -0
  94. dreadnode-1.15.0/examples/rigging.ipynb +95 -0
  95. {dreadnode-1.14.0 → dreadnode-1.15.0}/pyproject.toml +9 -13
  96. dreadnode-1.15.0/tests/cli/test_config.py +79 -0
  97. dreadnode-1.15.0/tests/cli/test_docker.py +161 -0
  98. dreadnode-1.15.0/tests/cli/test_github.py +201 -0
  99. {dreadnode-1.14.0 → dreadnode-1.15.0}/tests/test_meta.py +31 -3
  100. dreadnode-1.14.0/dreadnode/airt/__init__.py +0 -18
  101. dreadnode-1.14.0/dreadnode/airt/attack/__init__.py +0 -6
  102. dreadnode-1.14.0/dreadnode/airt/search/__init__.py +0 -4
  103. dreadnode-1.14.0/dreadnode/airt/search/hop_skip_jump.py +0 -195
  104. dreadnode-1.14.0/dreadnode/airt/search/image_utils.py +0 -46
  105. dreadnode-1.14.0/dreadnode/cli/platform/cli.py +0 -164
  106. dreadnode-1.14.0/dreadnode/cli/platform/configure.py +0 -47
  107. dreadnode-1.14.0/dreadnode/cli/platform/constants.py +0 -12
  108. dreadnode-1.14.0/dreadnode/cli/platform/docker_.py +0 -463
  109. dreadnode-1.14.0/dreadnode/cli/platform/download.py +0 -144
  110. dreadnode-1.14.0/dreadnode/cli/platform/login.py +0 -20
  111. dreadnode-1.14.0/dreadnode/cli/platform/schemas.py +0 -117
  112. dreadnode-1.14.0/dreadnode/cli/platform/start.py +0 -71
  113. dreadnode-1.14.0/dreadnode/cli/platform/status.py +0 -51
  114. dreadnode-1.14.0/dreadnode/cli/platform/stop.py +0 -21
  115. dreadnode-1.14.0/dreadnode/cli/platform/upgrade.py +0 -64
  116. dreadnode-1.14.0/dreadnode/cli/platform/utils/printing.py +0 -43
  117. dreadnode-1.14.0/dreadnode/cli/platform/utils/versions.py +0 -184
  118. dreadnode-1.14.0/dreadnode/eval/format.py +0 -118
  119. dreadnode-1.14.0/dreadnode/logging_.py +0 -69
  120. dreadnode-1.14.0/dreadnode/optimization/format.py +0 -83
  121. dreadnode-1.14.0/dreadnode/tracing/__init__.py +0 -0
  122. {dreadnode-1.14.0 → dreadnode-1.15.0}/.gitignore +0 -0
  123. {dreadnode-1.14.0 → dreadnode-1.15.0}/LICENSE +0 -0
  124. {dreadnode-1.14.0 → dreadnode-1.15.0}/README.md +0 -0
  125. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/__main__.py +0 -0
  126. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/error.py +0 -0
  127. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/format.py +0 -0
  128. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/hooks/base.py +0 -0
  129. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/hooks/summarize.py +0 -0
  130. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/prompts/__init__.py +0 -0
  131. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/prompts/summarize.py +0 -0
  132. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/result.py +0 -0
  133. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/stop.py +0 -0
  134. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/thread.py +0 -0
  135. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/fs.py +0 -0
  136. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/planning.py +0 -0
  137. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/reporting.py +0 -0
  138. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/agent/tools/tasking.py +0 -0
  139. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/attack/base.py +0 -0
  140. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/attack/goat.py +0 -0
  141. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/attack/tap.py +0 -0
  142. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/target/__init__.py +0 -0
  143. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/target/base.py +0 -0
  144. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/airt/target/custom.py +0 -0
  145. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/api/__init__.py +0 -0
  146. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/api/models.py +0 -0
  147. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/api/util.py +0 -0
  148. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/artifact/__init__.py +0 -0
  149. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/artifact/credential_manager.py +0 -0
  150. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/artifact/merger.py +0 -0
  151. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/artifact/storage.py +0 -0
  152. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/artifact/tree_builder.py +0 -0
  153. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/__init__.py +0 -0
  154. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/agent/__init__.py +0 -0
  155. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/api.py +0 -0
  156. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/eval/__init__.py +0 -0
  157. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/platform/__init__.py +0 -0
  158. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/profile/__init__.py +0 -0
  159. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/cli/study/__init__.py +0 -0
  160. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/common_types.py +0 -0
  161. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/constants.py +0 -0
  162. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/convert.py +0 -0
  163. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/__init__.py +0 -0
  164. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/audio.py +0 -0
  165. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/base.py +0 -0
  166. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/object_3d.py +0 -0
  167. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/table.py +0 -0
  168. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/text.py +0 -0
  169. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/data_types/video.py +0 -0
  170. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/error.py +0 -0
  171. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/eval/__init__.py +0 -0
  172. {dreadnode-1.14.0/dreadnode/cli/platform/utils → dreadnode-1.15.0/dreadnode/integrations}/__init__.py +0 -0
  173. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/integrations/transformers.py +0 -0
  174. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/meta/__init__.py +0 -0
  175. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/metric.py +0 -0
  176. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/object.py +0 -0
  177. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/__init__.py +0 -0
  178. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/collectors.py +0 -0
  179. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/__init__.py +0 -0
  180. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/optimization/search/base.py +0 -0
  181. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/py.typed +0 -0
  182. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/__init__.py +0 -0
  183. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/classification.py +0 -0
  184. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/consistency.py +0 -0
  185. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/contains.py +0 -0
  186. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/crucible.py +0 -0
  187. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/format.py +0 -0
  188. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/harm.py +0 -0
  189. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/judge.py +0 -0
  190. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/length.py +0 -0
  191. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/lexical.py +0 -0
  192. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/pii.py +0 -0
  193. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/readability.py +0 -0
  194. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/rigging.py +0 -0
  195. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/sentiment.py +0 -0
  196. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/scorers/util.py +0 -0
  197. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/serialization.py +0 -0
  198. {dreadnode-1.14.0/dreadnode/integrations → dreadnode-1.15.0/dreadnode/tracing}/__init__.py +0 -0
  199. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/tracing/constants.py +0 -0
  200. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/tracing/exporters.py +0 -0
  201. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/__init__.py +0 -0
  202. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/base.py +0 -0
  203. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/cipher.py +0 -0
  204. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/encoding.py +0 -0
  205. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/perturbation.py +0 -0
  206. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/refine.py +0 -0
  207. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/stylistic.py +0 -0
  208. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/substitution.py +0 -0
  209. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/swap.py +0 -0
  210. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/transforms/text.py +0 -0
  211. {dreadnode-1.14.0 → dreadnode-1.15.0}/dreadnode/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dreadnode
3
- Version: 1.14.0
3
+ Version: 1.15.0
4
4
  Summary: Dreadnode SDK
5
5
  Project-URL: Homepage, https://github.com/dreadnode/sdk
6
6
  Project-URL: Repository, https://github.com/dreadnode/sdk
@@ -213,16 +213,17 @@ Requires-Dist: coolname<3.0.0,>=2.2.0
213
213
  Requires-Dist: cyclopts<4.0.0,>=3.22.2
214
214
  Requires-Dist: fsspec[s3]<=2025.3.0,>=2023.1.0
215
215
  Requires-Dist: httpx<1.0.0,>=0.28.0
216
- Requires-Dist: jsonpath>=0.82.2
217
216
  Requires-Dist: logfire<=3.20.0,>=3.5.3
218
217
  Requires-Dist: loguru>=0.7.3
219
218
  Requires-Dist: numpy<=2.2.6
220
219
  Requires-Dist: optuna<5.0.0,>=4.5.0
221
220
  Requires-Dist: pandas<3.0.0,>=2.2.3
222
221
  Requires-Dist: pydantic<3.0.0,>=2.9.2
222
+ Requires-Dist: python-jsonpath>=2.0.1
223
223
  Requires-Dist: python-ulid<4.0.0,>=3.0.0
224
+ Requires-Dist: pyyaml>=6.0.2
224
225
  Requires-Dist: rigging<4.0.0,>=3.2.1
225
- Requires-Dist: universal-pathlib<0.3.0,>=0.2.6
226
+ Requires-Dist: universal-pathlib<0.4.0,>=0.3.3
226
227
  Provides-Extra: all
227
228
  Requires-Dist: confusables<2.0.0,>=1.2.0; extra == 'all'
228
229
  Requires-Dist: datasets<5.0.0,>=4.0.0; extra == 'all'
@@ -230,7 +231,7 @@ Requires-Dist: moviepy<3.0.0,>=2.1.2; extra == 'all'
230
231
  Requires-Dist: nltk<4.0.0,>=3.9.1; extra == 'all'
231
232
  Requires-Dist: pillow<12.0.0,>=11.2.1; extra == 'all'
232
233
  Requires-Dist: presidio-analyzer<3.0.0,>=2.2.359; extra == 'all'
233
- Requires-Dist: pyarrow<20.0.0,>=19.0.1; extra == 'all'
234
+ Requires-Dist: pyarrow<22.1.0,>=22.0.0; extra == 'all'
234
235
  Requires-Dist: rapidfuzz>=3.14.1; extra == 'all'
235
236
  Requires-Dist: scikit-learn<2.0.0,>=1.7.1; extra == 'all'
236
237
  Requires-Dist: sentence-transformers<6.0.0,>=5.1.0; extra == 'all'
@@ -253,7 +254,7 @@ Requires-Dist: textblob<1.0.0,>=0.19.0; extra == 'scoring'
253
254
  Requires-Dist: textstat<1.0.0,>=0.7.10; extra == 'scoring'
254
255
  Provides-Extra: training
255
256
  Requires-Dist: datasets<5.0.0,>=4.0.0; extra == 'training'
256
- Requires-Dist: pyarrow<20.0.0,>=19.0.1; extra == 'training'
257
+ Requires-Dist: pyarrow<22.1.0,>=22.0.0; extra == 'training'
257
258
  Requires-Dist: transformers<5.0.0,>=4.41.0; extra == 'training'
258
259
  Provides-Extra: transforms
259
260
  Requires-Dist: art<7.0.0,>=6.5; extra == 'transforms'
@@ -37,6 +37,7 @@ from dreadnode.version import VERSION
37
37
 
38
38
  if t.TYPE_CHECKING:
39
39
  from dreadnode import agent, airt, eval, optimization, scorers, transforms # noqa: A004
40
+ from dreadnode.agent import Agent, tool, tool_method
40
41
  from dreadnode.data_types import Audio, Image, Table, Video
41
42
 
42
43
  logger.disable("dreadnode")
@@ -75,6 +76,7 @@ __version__ = VERSION
75
76
 
76
77
  __all__ = [
77
78
  "DEFAULT_INSTANCE",
79
+ "Agent",
78
80
  "Audio",
79
81
  "Code",
80
82
  "Config",
@@ -138,6 +140,8 @@ __all__ = [
138
140
  "task",
139
141
  "task_and_run",
140
142
  "task_span",
143
+ "tool",
144
+ "tool_method",
141
145
  "transforms",
142
146
  ]
143
147
 
@@ -147,6 +151,9 @@ __lazy_components__: dict[str, str] = {
147
151
  "Image": "dreadnode.data_types",
148
152
  "Table": "dreadnode.data_types",
149
153
  "Video": "dreadnode.data_types",
154
+ "Agent": "dreadnode.agent",
155
+ "tool": "dreadnode.agent",
156
+ "tool_method": "dreadnode.agent",
150
157
  }
151
158
 
152
159
 
@@ -4,6 +4,7 @@ from dreadnode.agent import error, events, hooks, reactions, result, stop, tools
4
4
  from dreadnode.agent.agent import Agent
5
5
  from dreadnode.agent.result import AgentResult
6
6
  from dreadnode.agent.thread import Thread
7
+ from dreadnode.agent.tools import tool, tool_method
7
8
 
8
9
  Agent.model_rebuild()
9
10
  Thread.model_rebuild()
@@ -20,5 +21,7 @@ __all__ = [
20
21
  "reactions",
21
22
  "result",
22
23
  "stop",
24
+ "tool",
25
+ "tool_method",
23
26
  "tools",
24
27
  ]
@@ -4,8 +4,10 @@ from contextlib import aclosing, asynccontextmanager
4
4
  from copy import deepcopy
5
5
 
6
6
  import rigging as rg
7
- from pydantic import ConfigDict, Field, PrivateAttr, field_validator
8
- from rigging.message import inject_system_content # can't access via rg
7
+ from loguru import logger
8
+ from pydantic import ConfigDict, Field, PrivateAttr, SkipValidation, field_validator
9
+ from rigging.message import inject_system_content
10
+ from ulid import ULID # can't access via rg
9
11
 
10
12
  from dreadnode.agent.error import MaxStepsError
11
13
  from dreadnode.agent.events import (
@@ -83,7 +85,7 @@ class Agent(Model):
83
85
  caching: rg.caching.CacheMode | None = Config(default=None, repr=False)
84
86
  """How to handle cache_control entries on inference messages."""
85
87
 
86
- tools: list[AnyTool | Toolset] = Config(default_factory=list)
88
+ tools: t.Annotated[list[AnyTool | Toolset], SkipValidation] = Config(default_factory=list)
87
89
  """Tools the agent can use."""
88
90
  tool_mode: ToolMode = Config(default="auto", repr=False)
89
91
  """The tool calling mode to use."""
@@ -236,6 +238,8 @@ class Agent(Model):
236
238
  try:
237
239
  messages = rg.caching.apply_cache_mode_to_messages(self.caching, [messages])[0]
238
240
 
241
+ logger.trace(f"Generating with model '{generator.model}'. Messages: {messages!r}")
242
+
239
243
  generated = (await generator.generate_messages([messages], [params]))[0]
240
244
  if isinstance(generated, BaseException):
241
245
  raise generated # noqa: TRY301
@@ -251,6 +255,7 @@ class Agent(Model):
251
255
  )
252
256
 
253
257
  except Exception as error: # noqa: BLE001
258
+ logger.error(f"Error during generation: {error}", exc_info=True)
254
259
  chat = rg.Chat(
255
260
  messages,
256
261
  [],
@@ -275,6 +280,14 @@ class Agent(Model):
275
280
  ) -> t.AsyncGenerator[AgentEvent, None]:
276
281
  events: list[AgentEvent] = []
277
282
  stop_conditions = self.stop_conditions
283
+ session_id = ULID()
284
+
285
+ logger.info(
286
+ f"Starting Agent '{self.name}' ({session_id}): "
287
+ f"model='{self.model}', "
288
+ f"max_steps={self.max_steps}, "
289
+ f"tools={[tool.name for tool in self.all_tools]}"
290
+ )
278
291
 
279
292
  # Event dispatcher
280
293
 
@@ -290,6 +303,11 @@ class Agent(Model):
290
303
  if not applicable_hooks:
291
304
  return
292
305
 
306
+ logger.debug(
307
+ f"Agent '{self.name}' ({session_id}) dispatching '{type(event).__name__}': "
308
+ f"applicable_hooks={[get_callable_name(h, short=True) for h in applicable_hooks]}"
309
+ )
310
+
293
311
  # Run all applicable hooks and collect their reactions
294
312
  hook_reactions: dict[str, Reaction | None] = {}
295
313
  for hook in applicable_hooks:
@@ -308,6 +326,10 @@ class Agent(Model):
308
326
  if reaction is None:
309
327
  continue
310
328
 
329
+ logger.debug(
330
+ f"Agent '{self.name}' ({session_id}) hook '{hook_name}' returned reaction: {reaction!r}"
331
+ )
332
+
311
333
  if not isinstance(reaction, Reaction):
312
334
  warn_at_user_stacklevel(
313
335
  f"Hook '{hook_name}' returned {reaction}, but expected a Reaction.",
@@ -368,6 +390,7 @@ class Agent(Model):
368
390
  "unknown",
369
391
  )
370
392
  reacted_event = Reacted(
393
+ session_id=session_id,
371
394
  agent=self,
372
395
  thread=thread,
373
396
  messages=messages,
@@ -383,6 +406,9 @@ class Agent(Model):
383
406
  return
384
407
 
385
408
  if isinstance(winning_reaction, RetryWithFeedback):
409
+ logger.debug(
410
+ f"Agent '{self.name}' ({session_id}) injecting feedback for retry: '{winning_reaction.feedback}'"
411
+ )
386
412
  messages.append(rg.Message("user", winning_reaction.feedback))
387
413
  raise Retry(messages=messages) from winning_reaction
388
414
 
@@ -395,6 +421,7 @@ class Agent(Model):
395
421
  ) -> t.AsyncGenerator[AgentEvent, None]:
396
422
  async for event in _dispatch(
397
423
  ToolStart(
424
+ session_id=session_id,
398
425
  agent=self,
399
426
  thread=thread,
400
427
  messages=messages,
@@ -404,6 +431,10 @@ class Agent(Model):
404
431
  ):
405
432
  yield event
406
433
 
434
+ logger.debug(
435
+ f"Executing tool '{tool_call.name}' with args: {tool_call.function.arguments}"
436
+ )
437
+
407
438
  message: rg.Message
408
439
  stop = False
409
440
  tool = next((t for t in self.all_tools if t.name == tool_call.name), None)
@@ -416,6 +447,7 @@ class Agent(Model):
416
447
  except Exception as e:
417
448
  async for event in _dispatch(
418
449
  AgentError(
450
+ session_id=session_id,
419
451
  agent=self,
420
452
  thread=thread,
421
453
  messages=messages,
@@ -426,12 +458,14 @@ class Agent(Model):
426
458
  yield event
427
459
  raise
428
460
  else:
461
+ logger.warning(f"Tool '{tool_call.name}' not found.")
429
462
  message = rg.Message.from_model(
430
463
  rg.model.SystemErrorModel(content=f"Tool '{tool_call.name}' not found.")
431
464
  )
432
465
 
433
466
  async for event in _dispatch(
434
467
  ToolEnd(
468
+ session_id=session_id,
435
469
  agent=self,
436
470
  thread=thread,
437
471
  messages=messages,
@@ -447,6 +481,7 @@ class Agent(Model):
447
481
 
448
482
  async for event in _dispatch(
449
483
  AgentStart(
484
+ session_id=session_id,
450
485
  agent=self,
451
486
  thread=thread,
452
487
  messages=messages,
@@ -464,6 +499,7 @@ class Agent(Model):
464
499
  try:
465
500
  async for event in _dispatch(
466
501
  StepStart(
502
+ session_id=session_id,
467
503
  agent=self,
468
504
  thread=thread,
469
505
  messages=messages,
@@ -479,6 +515,7 @@ class Agent(Model):
479
515
  if step_chat.failed and step_chat.error:
480
516
  async for event in _dispatch(
481
517
  AgentError(
518
+ session_id=session_id,
482
519
  agent=self,
483
520
  thread=thread,
484
521
  messages=messages,
@@ -493,6 +530,7 @@ class Agent(Model):
493
530
 
494
531
  async for event in _dispatch(
495
532
  GenerationEnd(
533
+ session_id=session_id,
496
534
  agent=self,
497
535
  thread=thread,
498
536
  messages=messages,
@@ -506,6 +544,7 @@ class Agent(Model):
506
544
  # Check for stop conditions
507
545
 
508
546
  if any(cond(events) for cond in stop_conditions):
547
+ logger.info("A stop condition was met. Ending run.")
509
548
  break
510
549
 
511
550
  # Check if stalled
@@ -514,8 +553,13 @@ class Agent(Model):
514
553
  if not stop_conditions:
515
554
  break
516
555
 
556
+ logger.warning(
557
+ f"Agent '{self.name}' ({session_id}) stalled: No tool calls and no stop conditions met."
558
+ )
559
+
517
560
  async for event in _dispatch(
518
561
  AgentStalled(
562
+ session_id=session_id,
519
563
  agent=self,
520
564
  thread=thread,
521
565
  messages=messages,
@@ -578,7 +622,25 @@ class Agent(Model):
578
622
  thread.messages = messages
579
623
  thread.events.extend(events)
580
624
 
625
+ total_usage = _total_usage_from_events(events)
626
+ log_message = (
627
+ f"Agent '{self.name}' finished: "
628
+ f"reason='{stop_reason}', "
629
+ f"steps={step - 1}, "
630
+ f"total_tokens={total_usage.total_tokens}, "
631
+ f"in_tokens={total_usage.input_tokens}, "
632
+ f"out_tokens={total_usage.output_tokens}"
633
+ )
634
+
635
+ if stop_reason == "finished":
636
+ logger.success(log_message)
637
+ elif stop_reason == "error":
638
+ logger.error(f"{log_message}, error='{error!r}'")
639
+ else:
640
+ logger.warning(log_message)
641
+
581
642
  yield AgentEnd(
643
+ session_id=session_id,
582
644
  agent=self,
583
645
  thread=thread,
584
646
  messages=messages,
@@ -646,9 +708,10 @@ class Agent(Model):
646
708
  "user_input": user_input,
647
709
  "messages": messages,
648
710
  "instructions": self.instructions,
649
- "tools": [t.name for t in self.all_tools],
650
711
  "hooks": [get_callable_name(hook, short=True) for hook in self.hooks],
651
712
  "stop_conditions": [s.name for s in self.stop_conditions],
713
+ "tools": [t.name for t in self.all_tools],
714
+ "tool_schemas": [t.api_definition for t in self.all_tools],
652
715
  }
653
716
  )
654
717
  trace_params.update(
@@ -669,13 +732,17 @@ class Agent(Model):
669
732
  yield event
670
733
  finally:
671
734
  if last_event is not None:
672
- log_outputs(messages=last_event.messages, token_usage=last_event.total_usage)
735
+ # TODO(nick): Don't love having to inject here, but it's the most accurate in
736
+ # in terms of ensuring we don't miss the system component of messages
737
+ final_messages = inject_system_content(last_event.messages, self.get_prompt())
738
+ log_outputs(messages=final_messages, token_usage=last_event.total_usage)
673
739
 
674
740
  if isinstance(last_event, AgentEnd):
675
741
  log_outputs(
676
742
  to="both",
677
743
  steps_taken=min(0, last_event.result.steps - 1),
678
744
  reason=last_event.stop_reason,
745
+ failed=last_event.result.failed,
679
746
  )
680
747
  if last_event.result.error:
681
748
  log_output("error", last_event.result.error, to="both")
@@ -9,6 +9,7 @@ from rich.panel import Panel
9
9
  from rich.rule import Rule
10
10
  from rich.table import Table
11
11
  from rich.text import Text
12
+ from ulid import ULID
12
13
 
13
14
  from dreadnode.agent.format import format_message
14
15
  from dreadnode.agent.reactions import (
@@ -39,6 +40,8 @@ class AgentEvent:
39
40
  )
40
41
  """The timestamp of when the event occurred (UTC)."""
41
42
 
43
+ session_id: ULID = field(repr=False)
44
+ """The unique identifier for the agent run session."""
42
45
  agent: "Agent" = field(repr=False)
43
46
  """The agent associated with this event."""
44
47
  thread: "Thread" = field(repr=False)
@@ -1,3 +1,4 @@
1
+ from dreadnode.agent.hooks.backoff import backoff_on_error, backoff_on_ratelimit
1
2
  from dreadnode.agent.hooks.base import (
2
3
  Hook,
3
4
  retry_with_feedback,
@@ -6,6 +7,8 @@ from dreadnode.agent.hooks.summarize import summarize_when_long
6
7
 
7
8
  __all__ = [
8
9
  "Hook",
10
+ "backoff_on_error",
11
+ "backoff_on_ratelimit",
9
12
  "retry_with_feedback",
10
13
  "summarize_when_long",
11
14
  ]
@@ -0,0 +1,134 @@
1
+ import asyncio
2
+ import random
3
+ import time
4
+ import typing as t
5
+ from dataclasses import dataclass
6
+
7
+ from loguru import logger
8
+
9
+ from dreadnode.agent.events import AgentError, AgentEvent, StepStart
10
+ from dreadnode.agent.reactions import Reaction, Retry
11
+
12
+ if t.TYPE_CHECKING:
13
+ from ulid import ULID
14
+
15
+ from dreadnode.agent.hooks.base import Hook
16
+
17
+
18
+ @dataclass
19
+ class BackoffState:
20
+ tries: int = 0
21
+ start_time: float | None = None
22
+ last_step_seen: int = -1
23
+
24
+ def reset(self, step: int = -1) -> None:
25
+ self.tries = 0
26
+ self.start_time = None
27
+ self.last_step_seen = step
28
+
29
+
30
+ def backoff_on_error(
31
+ exception_types: type[Exception] | t.Iterable[type[Exception]],
32
+ *,
33
+ max_tries: int = 8,
34
+ max_time: float = 300.0,
35
+ base_factor: float = 1.0,
36
+ jitter: bool = True,
37
+ ) -> "Hook":
38
+ """
39
+ Creates a hook that retries with exponential backoff when specific errors occur.
40
+
41
+ It listens for `AgentError` events and, if the error matches, waits for an
42
+ exponentially increasing duration before issuing a `Retry` reaction.
43
+
44
+ Args:
45
+ exception_types: An exception type or iterable of types to catch.
46
+ max_tries: The maximum number of retries before giving up.
47
+ max_time: The maximum total time in seconds to wait before giving up.
48
+ base_factor: The base duration (in seconds) for the backoff calculation.
49
+ jitter: If True, adds a random jitter to the wait time to prevent synchronized retries.
50
+
51
+ Returns:
52
+ An agent hook that implements the backoff logic.
53
+ """
54
+ exceptions = (
55
+ tuple(exception_types) if isinstance(exception_types, t.Iterable) else (exception_types,)
56
+ )
57
+
58
+ session_states: dict[ULID, BackoffState] = {}
59
+
60
+ async def backoff_hook(event: "AgentEvent") -> "Reaction | None":
61
+ state = session_states.setdefault(event.session_id, BackoffState())
62
+
63
+ if isinstance(event, StepStart):
64
+ if event.step > state.last_step_seen:
65
+ state.reset(event.step)
66
+ return None
67
+
68
+ if not isinstance(event, AgentError) or not isinstance(event.error, exceptions):
69
+ return None
70
+
71
+ if state.start_time is None:
72
+ state.start_time = time.monotonic()
73
+
74
+ if state.tries >= max_tries:
75
+ logger.warning(
76
+ f"Backoff aborted for session {event.session_id}: maximum tries ({max_tries}) exceeded."
77
+ )
78
+ return None
79
+
80
+ if (time.monotonic() - state.start_time) >= max_time:
81
+ logger.warning(
82
+ f"Backoff aborted for session {event.session_id}: maximum time ({max_time:.2f}s) exceeded."
83
+ )
84
+ return None
85
+
86
+ state.tries += 1
87
+
88
+ seconds = base_factor * (2 ** (state.tries - 1))
89
+ if jitter:
90
+ seconds += random.uniform(0, base_factor) # noqa: S311 # nosec
91
+
92
+ logger.warning(
93
+ f"Backing off for {seconds:.2f}s (try {state.tries}/{max_tries}) on session {event.session_id} due to error: {event.error}"
94
+ )
95
+
96
+ await asyncio.sleep(seconds)
97
+ return Retry()
98
+
99
+ return backoff_hook
100
+
101
+
102
+ def backoff_on_ratelimit(
103
+ *,
104
+ max_tries: int = 8,
105
+ max_time: float = 300.0,
106
+ base_factor: float = 1.0,
107
+ jitter: bool = True,
108
+ ) -> "Hook":
109
+ """
110
+ A convenient default backoff hook for common, ephemeral LLM errors.
111
+
112
+ This hook retries on `litellm.exceptions.RateLimitError` and `litellm.exceptions.APIError`
113
+ with an exponential backoff strategy for up to 5 minutes.
114
+
115
+ See `backoff_on_error` for more details.
116
+
117
+ Args:
118
+ max_tries: The maximum number of retries before giving up.
119
+ max_time: The maximum total time in seconds to wait before giving up.
120
+ base_factor: The base duration (in seconds) for the backoff calculation.
121
+ jitter: If True, adds a random jitter to the wait time to prevent synchronized retries.
122
+
123
+ Returns:
124
+ An agent hook that implements the backoff logic.
125
+ """
126
+ import litellm.exceptions
127
+
128
+ return backoff_on_error(
129
+ (litellm.exceptions.RateLimitError, litellm.exceptions.APIError),
130
+ max_time=max_time,
131
+ max_tries=max_tries,
132
+ base_factor=base_factor,
133
+ jitter=jitter,
134
+ )
@@ -19,7 +19,7 @@ class Continue(Reaction):
19
19
 
20
20
  @dataclass
21
21
  class Retry(Reaction):
22
- messages: list[rg.Message] | None = Field(None, repr=False)
22
+ messages: list[rg.Message] | None = Field(default=None, repr=False)
23
23
 
24
24
 
25
25
  @dataclass
@@ -3,7 +3,12 @@ import typing as t
3
3
 
4
4
  from dreadnode.agent.tools.base import (
5
5
  AnyTool,
6
+ FunctionCall,
7
+ FunctionDefinition,
6
8
  Tool,
9
+ ToolCall,
10
+ ToolDefinition,
11
+ ToolMode,
7
12
  Toolset,
8
13
  discover_tools_on_obj,
9
14
  tool,
@@ -15,7 +20,12 @@ if t.TYPE_CHECKING:
15
20
 
16
21
  __all__ = [
17
22
  "AnyTool",
23
+ "FunctionCall",
24
+ "FunctionDefinition",
18
25
  "Tool",
26
+ "ToolCall",
27
+ "ToolDefinition",
28
+ "ToolMode",
19
29
  "Toolset",
20
30
  "discover_tools_on_obj",
21
31
  "fs",
@@ -8,6 +8,10 @@ from dreadnode.meta import Component, Config, Model
8
8
 
9
9
  Tool = tools.Tool
10
10
  ToolMode = tools.ToolMode
11
+ ToolCall = tools.ToolCall
12
+ FunctionCall = tools.FunctionCall
13
+ ToolDefinition = tools.ToolDefinition
14
+ FunctionDefinition = tools.FunctionDefinition
11
15
 
12
16
  AnyTool = Tool[t.Any, t.Any]
13
17
 
@@ -0,0 +1,29 @@
1
+ from dreadnode.airt import attack, search
2
+ from dreadnode.airt.attack import (
3
+ Attack,
4
+ goat_attack,
5
+ hop_skip_jump_attack,
6
+ nes_attack,
7
+ prompt_attack,
8
+ simba_attack,
9
+ tap_attack,
10
+ zoo_attack,
11
+ )
12
+ from dreadnode.airt.target import CustomTarget, LLMTarget, Target
13
+
14
+ __all__ = [
15
+ "Attack",
16
+ "CustomTarget",
17
+ "LLMTarget",
18
+ "Target",
19
+ "attack",
20
+ "goat_attack",
21
+ "hop_skip_jump_attack",
22
+ "nes_attack",
23
+ "prompt_attack",
24
+ "search",
25
+ "simba_attack",
26
+ "tap_attack",
27
+ "target",
28
+ "zoo_attack",
29
+ ]
@@ -0,0 +1,19 @@
1
+ from dreadnode.airt.attack.base import Attack
2
+ from dreadnode.airt.attack.goat import goat_attack
3
+ from dreadnode.airt.attack.hop_skip_jump import hop_skip_jump_attack
4
+ from dreadnode.airt.attack.nes import nes_attack
5
+ from dreadnode.airt.attack.prompt import prompt_attack
6
+ from dreadnode.airt.attack.simba import simba_attack
7
+ from dreadnode.airt.attack.tap import tap_attack
8
+ from dreadnode.airt.attack.zoo import zoo_attack
9
+
10
+ __all__ = [
11
+ "Attack",
12
+ "goat_attack",
13
+ "hop_skip_jump_attack",
14
+ "nes_attack",
15
+ "prompt_attack",
16
+ "simba_attack",
17
+ "tap_attack",
18
+ "zoo_attack",
19
+ ]