dreadnode 1.15.1__tar.gz → 1.15.2__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 (192) hide show
  1. {dreadnode-1.15.1 → dreadnode-1.15.2}/PKG-INFO +1 -1
  2. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/agent.py +24 -8
  3. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/hooks/__init__.py +2 -0
  4. dreadnode-1.15.2/dreadnode/agent/hooks/metrics.py +84 -0
  5. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/base.py +13 -8
  6. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/agent/cli.py +0 -2
  7. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/attack/cli.py +0 -2
  8. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/eval/cli.py +0 -2
  9. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/study/cli.py +0 -2
  10. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/task/cli.py +2 -1
  11. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/eval.py +1 -0
  12. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/study.py +9 -1
  13. {dreadnode-1.15.1 → dreadnode-1.15.2}/pyproject.toml +1 -1
  14. {dreadnode-1.15.1 → dreadnode-1.15.2}/.gitignore +0 -0
  15. {dreadnode-1.15.1 → dreadnode-1.15.2}/LICENSE +0 -0
  16. {dreadnode-1.15.1 → dreadnode-1.15.2}/README.md +0 -0
  17. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/__init__.py +0 -0
  18. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/__main__.py +0 -0
  19. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/__init__.py +0 -0
  20. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/error.py +0 -0
  21. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/events.py +0 -0
  22. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/format.py +0 -0
  23. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/hooks/backoff.py +0 -0
  24. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/hooks/base.py +0 -0
  25. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/hooks/summarize.py +0 -0
  26. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/prompts/__init__.py +0 -0
  27. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/prompts/summarize.py +0 -0
  28. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/reactions.py +0 -0
  29. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/result.py +0 -0
  30. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/stop.py +0 -0
  31. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/thread.py +0 -0
  32. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/__init__.py +0 -0
  33. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/execute.py +0 -0
  34. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/fs.py +0 -0
  35. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/memory.py +0 -0
  36. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/planning.py +0 -0
  37. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/reporting.py +0 -0
  38. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/agent/tools/tasking.py +0 -0
  39. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/__init__.py +0 -0
  40. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/__init__.py +0 -0
  41. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/base.py +0 -0
  42. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/goat.py +0 -0
  43. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/hop_skip_jump.py +0 -0
  44. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/nes.py +0 -0
  45. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/prompt.py +0 -0
  46. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/simba.py +0 -0
  47. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/tap.py +0 -0
  48. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/attack/zoo.py +0 -0
  49. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/__init__.py +0 -0
  50. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/hop_skip_jump.py +0 -0
  51. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/image_utils.py +0 -0
  52. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/nes.py +0 -0
  53. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/simba.py +0 -0
  54. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/search/zoo.py +0 -0
  55. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/target/__init__.py +0 -0
  56. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/target/base.py +0 -0
  57. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/target/custom.py +0 -0
  58. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/airt/target/llm.py +0 -0
  59. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/api/__init__.py +0 -0
  60. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/api/client.py +0 -0
  61. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/api/models.py +0 -0
  62. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/api/util.py +0 -0
  63. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/artifact/__init__.py +0 -0
  64. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/artifact/credential_manager.py +0 -0
  65. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/artifact/merger.py +0 -0
  66. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/artifact/storage.py +0 -0
  67. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/artifact/tree_builder.py +0 -0
  68. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/__init__.py +0 -0
  69. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/agent/__init__.py +0 -0
  70. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/api.py +0 -0
  71. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/attack/__init__.py +0 -0
  72. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/docker.py +0 -0
  73. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/eval/__init__.py +0 -0
  74. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/github.py +0 -0
  75. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/main.py +0 -0
  76. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/__init__.py +0 -0
  77. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/cli.py +0 -0
  78. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/compose.py +0 -0
  79. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/constants.py +0 -0
  80. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/download.py +0 -0
  81. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/env_mgmt.py +0 -0
  82. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/tag.py +0 -0
  83. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/platform/version.py +0 -0
  84. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/profile/__init__.py +0 -0
  85. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/profile/cli.py +0 -0
  86. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/shared.py +0 -0
  87. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/study/__init__.py +0 -0
  88. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/cli/task/__init__.py +0 -0
  89. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/common_types.py +0 -0
  90. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/constants.py +0 -0
  91. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/convert.py +0 -0
  92. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/__init__.py +0 -0
  93. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/audio.py +0 -0
  94. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/base.py +0 -0
  95. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/image.py +0 -0
  96. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/object_3d.py +0 -0
  97. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/table.py +0 -0
  98. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/text.py +0 -0
  99. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/data_types/video.py +0 -0
  100. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/discovery.py +0 -0
  101. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/error.py +0 -0
  102. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/__init__.py +0 -0
  103. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/console.py +0 -0
  104. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/dataset.py +0 -0
  105. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/events.py +0 -0
  106. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/format.py +0 -0
  107. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/result.py +0 -0
  108. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/eval/sample.py +0 -0
  109. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/format.py +0 -0
  110. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/integrations/__init__.py +0 -0
  111. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/integrations/transformers.py +0 -0
  112. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/logging_.py +0 -0
  113. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/main.py +0 -0
  114. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/meta/__init__.py +0 -0
  115. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/meta/config.py +0 -0
  116. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/meta/context.py +0 -0
  117. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/meta/hydrate.py +0 -0
  118. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/meta/introspect.py +0 -0
  119. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/metric.py +0 -0
  120. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/object.py +0 -0
  121. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/__init__.py +0 -0
  122. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/collectors.py +0 -0
  123. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/console.py +0 -0
  124. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/events.py +0 -0
  125. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/format.py +0 -0
  126. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/result.py +0 -0
  127. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/sampling.py +0 -0
  128. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/__init__.py +0 -0
  129. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/base.py +0 -0
  130. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/boundary.py +0 -0
  131. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/graph.py +0 -0
  132. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/optuna_.py +0 -0
  133. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/search/random.py +0 -0
  134. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/stop.py +0 -0
  135. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/optimization/trial.py +0 -0
  136. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/py.typed +0 -0
  137. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/__init__.py +0 -0
  138. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/base.py +0 -0
  139. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/classification.py +0 -0
  140. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/consistency.py +0 -0
  141. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/contains.py +0 -0
  142. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/crucible.py +0 -0
  143. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/format.py +0 -0
  144. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/harm.py +0 -0
  145. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/image.py +0 -0
  146. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/json.py +0 -0
  147. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/judge.py +0 -0
  148. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/length.py +0 -0
  149. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/lexical.py +0 -0
  150. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/pii.py +0 -0
  151. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/readability.py +0 -0
  152. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/rigging.py +0 -0
  153. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/sentiment.py +0 -0
  154. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/similarity.py +0 -0
  155. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/scorers/util.py +0 -0
  156. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/serialization.py +0 -0
  157. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/task.py +0 -0
  158. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/tracing/__init__.py +0 -0
  159. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/tracing/constants.py +0 -0
  160. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/tracing/exporters.py +0 -0
  161. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/tracing/span.py +0 -0
  162. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/__init__.py +0 -0
  163. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/base.py +0 -0
  164. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/cipher.py +0 -0
  165. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/encoding.py +0 -0
  166. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/image.py +0 -0
  167. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/perturbation.py +0 -0
  168. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/refine.py +0 -0
  169. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/stylistic.py +0 -0
  170. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/substitution.py +0 -0
  171. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/swap.py +0 -0
  172. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/transforms/text.py +0 -0
  173. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/user_config.py +0 -0
  174. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/util.py +0 -0
  175. {dreadnode-1.15.1 → dreadnode-1.15.2}/dreadnode/version.py +0 -0
  176. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/airt/beam_search.ipynb +0 -0
  177. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/airt/graph_of_attacks_with_pruning.ipynb +0 -0
  178. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/airt/tap_vs_goat_eval.ipynb +0 -0
  179. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/airt/tree_of_attacks_with_pruning.ipynb +0 -0
  180. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/data_export.ipynb +0 -0
  181. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_artifact.ipynb +0 -0
  182. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_object/audio.ipynb +0 -0
  183. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_object/image.ipynb +0 -0
  184. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_object/object3d.ipynb +0 -0
  185. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_object/table.ipynb +0 -0
  186. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/log_object/video.ipynb +0 -0
  187. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/model_training.ipynb +0 -0
  188. {dreadnode-1.15.1 → dreadnode-1.15.2}/examples/rigging.ipynb +0 -0
  189. {dreadnode-1.15.1 → dreadnode-1.15.2}/tests/cli/test_config.py +0 -0
  190. {dreadnode-1.15.1 → dreadnode-1.15.2}/tests/cli/test_docker.py +0 -0
  191. {dreadnode-1.15.1 → dreadnode-1.15.2}/tests/cli/test_github.py +0 -0
  192. {dreadnode-1.15.1 → dreadnode-1.15.2}/tests/test_meta.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dreadnode
3
- Version: 1.15.1
3
+ Version: 1.15.2
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
@@ -2,10 +2,11 @@ import inspect
2
2
  import typing as t
3
3
  from contextlib import aclosing, asynccontextmanager
4
4
  from copy import deepcopy
5
+ from textwrap import dedent
5
6
 
6
7
  import rigging as rg
7
8
  from loguru import logger
8
- from pydantic import ConfigDict, Field, PrivateAttr, SkipValidation, field_validator
9
+ from pydantic import AfterValidator, ConfigDict, Field, PrivateAttr, SkipValidation, field_validator
9
10
  from rigging.message import inject_system_content
10
11
  from ulid import ULID # can't access via rg
11
12
 
@@ -71,14 +72,18 @@ class Agent(Model):
71
72
 
72
73
  name: str
73
74
  """The name of the agent."""
74
- description: str = ""
75
+ description: t.Annotated[str, AfterValidator(dedent)] = ""
75
76
  """A brief description of the agent's purpose."""
76
77
  tags: list[str] = Config(default_factory=lambda: ["agent"])
77
78
  """A list of tags associated with the agent."""
79
+ label: str | None = Config(default=None)
80
+ """Specific label for tracing, otherwise derived from the name."""
78
81
 
79
82
  model: str | None = Config(default=None)
80
83
  """Inference model (rigging generator identifier)."""
81
- instructions: str | None = Config(default=None)
84
+ instructions: t.Annotated[str | None, AfterValidator(lambda x: dedent(x) if x else x)] = Config(
85
+ default=None
86
+ )
82
87
  """The agent's core instructions."""
83
88
  max_steps: int = Config(default=10)
84
89
  """The maximum number of steps (generation + tool calls)."""
@@ -90,15 +95,15 @@ class Agent(Model):
90
95
  tool_mode: ToolMode = Config(default="auto", repr=False)
91
96
  """The tool calling mode to use."""
92
97
 
93
- hooks: list[Hook] = Config(default_factory=list, exclude=True, repr=False)
98
+ hooks: list[Hook] = Field(default_factory=list, exclude=True, repr=False)
94
99
  """Hooks to run at various points in the agent's lifecycle."""
95
- stop_conditions: list[StopCondition] = Config(default_factory=list)
100
+ stop_conditions: list[StopCondition] = Field(default_factory=list)
96
101
  """The logical condition for successfully stopping a run."""
97
102
  thread: Thread = Field(default_factory=Thread, exclude=True, repr=False)
98
103
  """Stateful thread for this agent, for when otherwise not specified during execution."""
99
- scorers: ScorersLike[AgentResult] = Config(default_factory=list)
104
+ scorers: ScorersLike[AgentResult] = Field(default_factory=list)
100
105
  """Scorers to evaluate the agent output."""
101
- assert_scores: list[str] | t.Literal[True] = Config(default_factory=list)
106
+ assert_scores: list[str] | t.Literal[True] = Field(default_factory=list)
102
107
  """Scores to ensure are truthy, otherwise the agent task is marked as failed."""
103
108
 
104
109
  _generator: rg.Generator | None = PrivateAttr(None, init=False)
@@ -716,14 +721,25 @@ class Agent(Model):
716
721
  )
717
722
  trace_params.update(
718
723
  {
724
+ "name": self.name,
719
725
  "model": self.model,
720
726
  "max_steps": self.max_steps,
721
727
  "tool_mode": self.tool_mode,
728
+ "tool_count": len(self.all_tools),
729
+ "instructions_length": len(self.instructions or ""),
730
+ "stop_condition_count": len(self.stop_conditions),
731
+ "message_count": len(messages),
722
732
  }
723
733
  )
724
734
 
725
735
  last_event: AgentEvent | None = None
726
- with task_and_run(name=self.name, tags=self.tags, inputs=trace_inputs, params=trace_params):
736
+ with task_and_run(
737
+ name=self.name,
738
+ tags=self.tags,
739
+ label=self.label,
740
+ inputs=trace_inputs,
741
+ params=trace_params,
742
+ ):
727
743
  try:
728
744
  async with aclosing(self._stream(thread, messages, hooks, commit=commit)) as stream:
729
745
  async for event in stream:
@@ -3,6 +3,7 @@ from dreadnode.agent.hooks.base import (
3
3
  Hook,
4
4
  retry_with_feedback,
5
5
  )
6
+ from dreadnode.agent.hooks.metrics import tool_metrics
6
7
  from dreadnode.agent.hooks.summarize import summarize_when_long
7
8
 
8
9
  __all__ = [
@@ -11,4 +12,5 @@ __all__ = [
11
12
  "backoff_on_ratelimit",
12
13
  "retry_with_feedback",
13
14
  "summarize_when_long",
15
+ "tool_metrics",
14
16
  ]
@@ -0,0 +1,84 @@
1
+ import typing as t
2
+
3
+ from dreadnode.agent.events import AgentEvent, ToolEnd, ToolStart
4
+ from dreadnode.agent.hooks import Hook
5
+ from dreadnode.meta import Config, component
6
+
7
+ if t.TYPE_CHECKING:
8
+ from datetime import datetime
9
+
10
+
11
+ def tool_metrics(*, detailed: bool = False) -> Hook:
12
+ """
13
+ Creates an agent hook to log metrics about tool usage, execution time, and success rates.
14
+
15
+ Args:
16
+ detailed: If True, logs metrics for each specific tool in addition to general stats.
17
+ If False, only logs aggregate statistics across all tools.
18
+
19
+ Returns:
20
+ An async hook function that can be registered with an agent.
21
+ """
22
+ _start_times: dict[str, datetime] = {}
23
+
24
+ @component
25
+ async def tool_metrics(
26
+ event: AgentEvent,
27
+ *,
28
+ detailed: bool = Config(
29
+ default=detailed,
30
+ help="If True, logs metrics for each specific tool in addition to general stats.",
31
+ ),
32
+ ) -> None:
33
+ """The actual hook implementation that processes agent events."""
34
+ from dreadnode import log_metric
35
+
36
+ if isinstance(event, ToolStart):
37
+ log_metric("tool/total_count", 1, step=event.step, mode="count")
38
+ _start_times[event.tool_call.id] = event.timestamp
39
+
40
+ if detailed:
41
+ tool_name = event.tool_call.name
42
+ log_metric(f"tool/count.{tool_name}", 1, step=event.step, mode="count")
43
+
44
+ elif isinstance(event, ToolEnd):
45
+ tool_name = event.tool_call.name
46
+ start_time = _start_times.pop(event.tool_call.id, event.timestamp)
47
+ duration_seconds = (event.timestamp - start_time).total_seconds()
48
+ errored = "error" in event.message.metadata
49
+
50
+ log_metric("tool/total_time", duration_seconds, step=event.step, mode="sum")
51
+ log_metric("tool/success_rate", 0 if errored else 1, step=event.step, mode="avg")
52
+
53
+ if errored:
54
+ log_metric("tool/failed_count", 1, step=event.step, mode="count")
55
+
56
+ if detailed:
57
+ log_metric(
58
+ f"tool/time.{tool_name}",
59
+ duration_seconds,
60
+ step=event.step,
61
+ mode="sum",
62
+ )
63
+ log_metric(
64
+ f"tool/avg_time.{tool_name}",
65
+ duration_seconds,
66
+ step=event.step,
67
+ mode="avg",
68
+ )
69
+ log_metric(
70
+ f"tool/success_rate.{tool_name}",
71
+ 0 if errored else 1,
72
+ step=event.step,
73
+ mode="avg",
74
+ )
75
+
76
+ if errored:
77
+ log_metric(
78
+ f"tool/failed_count.{tool_name}",
79
+ 1,
80
+ step=event.step,
81
+ mode="count",
82
+ )
83
+
84
+ return tool_metrics
@@ -4,7 +4,7 @@ from pydantic import ConfigDict
4
4
  from rigging import tools
5
5
  from rigging.tools.base import ToolMethod as RiggingToolMethod
6
6
 
7
- from dreadnode.meta import Component, Config, Model
7
+ from dreadnode.meta import Component, Model
8
8
 
9
9
  Tool = tools.Tool
10
10
  ToolMode = tools.ToolMode
@@ -103,18 +103,18 @@ def tool_method(
103
103
  description: str | None = None,
104
104
  catch: bool | t.Iterable[type[Exception]] | None = None,
105
105
  truncate: int | None = None,
106
- ) -> t.Callable[[t.Callable[P, R]], RiggingToolMethod[P, R]]: ...
106
+ ) -> t.Callable[[t.Callable[t.Concatenate[t.Any, P], R]], RiggingToolMethod[P, R]]: ...
107
107
 
108
108
 
109
109
  @t.overload
110
110
  def tool_method(
111
- func: t.Callable[P, R],
111
+ func: t.Callable[t.Concatenate[t.Any, P], R],
112
112
  /,
113
113
  ) -> RiggingToolMethod[P, R]: ...
114
114
 
115
115
 
116
116
  def tool_method(
117
- func: t.Callable[P, R] | None = None,
117
+ func: t.Callable[t.Concatenate[t.Any, P], R] | None = None,
118
118
  /,
119
119
  *,
120
120
  variants: list[str] | None = None,
@@ -122,7 +122,10 @@ def tool_method(
122
122
  description: str | None = None,
123
123
  catch: bool | t.Iterable[type[Exception]] | None = None,
124
124
  truncate: int | None = None,
125
- ) -> t.Callable[[t.Callable[P, R]], RiggingToolMethod[P, R]] | RiggingToolMethod[P, R]:
125
+ ) -> (
126
+ t.Callable[[t.Callable[t.Concatenate[t.Any, P], R]], RiggingToolMethod[P, R]]
127
+ | RiggingToolMethod[P, R]
128
+ ):
126
129
  """
127
130
  Marks a method on a Toolset as a tool, adding it to specified variants.
128
131
 
@@ -143,7 +146,9 @@ def tool_method(
143
146
  truncate: The maximum number of characters for the tool's output.
144
147
  """
145
148
 
146
- def make_tool_method(func: t.Callable[P, R]) -> RiggingToolMethod[P, R]:
149
+ def make_tool_method(
150
+ func: t.Callable[t.Concatenate[t.Any, P], R],
151
+ ) -> RiggingToolMethod[P, R]:
147
152
  tool_method_descriptor: RiggingToolMethod[P, R] = tools.tool_method(
148
153
  name=name,
149
154
  description=description,
@@ -168,7 +173,7 @@ class Toolset(Model):
168
173
  - A `get_tools` method for discovering methods decorated with `@dreadnode.tool_method`.
169
174
  """
170
175
 
171
- variant: str = Config("all")
176
+ variant: str | None = None
172
177
  """The variant for filtering tools available in this toolset."""
173
178
 
174
179
  model_config = ConfigDict(arbitrary_types_allowed=True, use_attribute_docstrings=True)
@@ -190,7 +195,7 @@ class Toolset(Model):
190
195
  continue
191
196
 
192
197
  variants = getattr(class_member, TOOL_VARIANTS_ATTR, [])
193
- if variant in variants:
198
+ if not variant or not variants or variant in variants:
194
199
  bound_tool = t.cast("AnyTool", getattr(self, name))
195
200
  tools.append(bound_tool)
196
201
  seen_names.add(name)
@@ -139,8 +139,6 @@ async def run( # noqa: PLR0912, PLR0915
139
139
  agent_cli.__annotations__["config"] = config_annotation
140
140
 
141
141
  help_text = f"Run the '{agent_name}' agent."
142
- if agent_blueprint.__doc__:
143
- help_text += "\n\n" + agent_blueprint.__doc__
144
142
  if agent_blueprint.description:
145
143
  help_text += "\n\n" + agent_blueprint.description
146
144
 
@@ -128,8 +128,6 @@ async def run( # noqa: PLR0912, PLR0915
128
128
  attack_cli.__annotations__["config"] = config_annotation
129
129
 
130
130
  help_text = f"Run the '{attack_name}' attack."
131
- if attack_blueprint.__doc__:
132
- help_text += "\n\n" + attack_blueprint.__doc__
133
131
  if attack_blueprint.description:
134
132
  help_text += "\n\n" + attack_blueprint.description
135
133
 
@@ -129,8 +129,6 @@ async def run( # noqa: PLR0912, PLR0915
129
129
  eval_cli.__annotations__["config"] = config_annotation
130
130
 
131
131
  help_text = f"Run the '{eval_name}' eval."
132
- if eval_blueprint.__doc__:
133
- help_text += "\n\n" + eval_blueprint.__doc__
134
132
  if eval_blueprint.description:
135
133
  help_text += "\n\n" + eval_blueprint.description
136
134
 
@@ -130,8 +130,6 @@ async def run( # noqa: PLR0912, PLR0915
130
130
  study_cli.__annotations__["config"] = config_annotation
131
131
 
132
132
  help_text = f"Run the '{study_name}' study."
133
- if study_blueprint.__doc__:
134
- help_text += "\n\n" + study_blueprint.__doc__
135
133
  if study_blueprint.description:
136
134
  help_text += "\n\n" + study_blueprint.description
137
135
 
@@ -4,6 +4,7 @@ import itertools
4
4
  import typing as t
5
5
  from inspect import isawaitable
6
6
  from pathlib import Path
7
+ from textwrap import dedent
7
8
 
8
9
  import cyclopts
9
10
  import rich
@@ -131,7 +132,7 @@ async def run( # noqa: PLR0912, PLR0915
131
132
 
132
133
  help_text = f"Run the '{task_name}' task."
133
134
  if task_blueprint.__doc__:
134
- help_text += "\n\n" + task_blueprint.__doc__
135
+ help_text += "\n\n" + dedent(task_blueprint.__doc__)
135
136
 
136
137
  task_app = cyclopts.App(
137
138
  name=task_name,
@@ -359,6 +359,7 @@ class Eval(Model, t.Generic[In, Out]):
359
359
  tags=self.tags,
360
360
  inputs=trace_inputs,
361
361
  params={**trace_params, **scenario_params},
362
+ label=self.label,
362
363
  )
363
364
  if self.trace
364
365
  else contextlib.nullcontext()
@@ -58,6 +58,8 @@ class Study(Model, t.Generic[CandidateT, OutputT]):
58
58
  """A brief description of the study's purpose."""
59
59
  tags: list[str] = Config(default_factory=lambda: ["study"])
60
60
  """A list of tags associated with the study for logging."""
61
+ label: str | None = Config(default=None)
62
+ """Specific label for tracing, otherwise derived from the name."""
61
63
 
62
64
  search_strategy: SkipValidation[Search[CandidateT]]
63
65
  """The search strategy to use for suggesting new trials."""
@@ -575,7 +577,13 @@ class Study(Model, t.Generic[CandidateT, OutputT]):
575
577
  log_outputs(to=log_to, **outputs)
576
578
 
577
579
  with (
578
- task_and_run(name=self.name, tags=self.tags, inputs=trace_inputs, params=trace_params),
580
+ task_and_run(
581
+ name=self.name,
582
+ tags=self.tags,
583
+ inputs=trace_inputs,
584
+ params=trace_params,
585
+ label=self.label,
586
+ ),
579
587
  contextlib.ExitStack() as stack,
580
588
  ):
581
589
  stack.callback(log_study, last_event)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dreadnode"
3
- version = "1.15.1"
3
+ version = "1.15.2"
4
4
  description = "Dreadnode SDK"
5
5
  authors = [{ name = "Nick Landers", email = "monoxgas@gmail.com" }]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes