hackagent 0.4.0__tar.gz → 0.4.1__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.
- {hackagent-0.4.0 → hackagent-0.4.1}/PKG-INFO +1 -3
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/agent.py +29 -20
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/registry.py +4 -39
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/attack.py +32 -10
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/completions.py +19 -2
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/evaluation.py +180 -39
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/evaluators.py +196 -109
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/generate.py +221 -101
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/base.py +1 -1
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/baseline/attack.py +31 -10
- hackagent-0.4.1/hackagent/attacks/techniques/baseline/evaluation.py +371 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/baseline/generation.py +26 -25
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/pair/attack.py +232 -27
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/app.py +1 -32
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/base.py +29 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/views/agents.py +4 -1
- hackagent-0.4.1/hackagent/cli/tui/views/results.py +2048 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/utils.py +1 -24
- hackagent-0.4.1/hackagent/router/adapters/__init__.py +37 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/adapters/litellm_adapter.py +96 -61
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/adapters/openai_adapter.py +166 -78
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/router.py +35 -7
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/utils.py +3 -40
- {hackagent-0.4.0 → hackagent-0.4.1}/pyproject.toml +3 -5
- hackagent-0.4.0/hackagent/attacks/techniques/baseline/evaluation.py +0 -244
- hackagent-0.4.0/hackagent/cli/tui/views/results.py +0 -1362
- hackagent-0.4.0/hackagent/router/adapters/__init__.py +0 -20
- {hackagent-0.4.0 → hackagent-0.4.1}/.gitignore +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/LICENSE +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/README.md +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/__init__.py +0 -0
- {hackagent-0.4.0/hackagent/api/user → hackagent-0.4.1/hackagent/api/agent}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/agent/agent_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/run → hackagent-0.4.1/hackagent/api/apilogs}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/apilogs/apilogs_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/apilogs/apilogs_retrieve.py +0 -0
- {hackagent-0.4.0/hackagent/api/result → hackagent-0.4.1/hackagent/api/attack}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/attack/attack_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/prompt → hackagent-0.4.1/hackagent/api/checkout}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/checkout/checkout_create.py +0 -0
- {hackagent-0.4.0/hackagent/api/organization → hackagent-0.4.1/hackagent/api/generate}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/generate/generate_create.py +0 -0
- {hackagent-0.4.0/hackagent/api/key → hackagent-0.4.1/hackagent/api/judge}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/judge/judge_create.py +0 -0
- {hackagent-0.4.0/hackagent/api/judge → hackagent-0.4.1/hackagent/api/key}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/key/key_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/key/key_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/key/key_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/key/key_retrieve.py +0 -0
- {hackagent-0.4.0/hackagent/api/generate → hackagent-0.4.1/hackagent/api/organization}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_me_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/organization/organization_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/checkout → hackagent-0.4.1/hackagent/api/prompt}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/prompt/prompt_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/attack → hackagent-0.4.1/hackagent/api/result}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_trace_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/result/result_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/apilogs → hackagent-0.4.1/hackagent/api/run}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_result_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_run_tests_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/run/run_update.py +0 -0
- {hackagent-0.4.0/hackagent/api/agent → hackagent-0.4.1/hackagent/api/user}/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_create.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_destroy.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_me_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_me_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_partial_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_retrieve.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/api/user/user_update.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/base.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/objectives/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/objectives/base.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/objectives/harmful_behavior.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/objectives/jailbreak.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/objectives/policy_violation.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/orchestrator.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/evaluators.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/metrics.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/progress.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/templates.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/shared/utils.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/advprefix/utils.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/baseline/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/baseline/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/pair/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/attacks/techniques/pair/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/commands/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/commands/agent.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/commands/attack.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/commands/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/commands/results.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/main.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/actions_logger.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/logger.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/views/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/views/attacks.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/views/config.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/views/dashboard.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/widgets/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/widgets/actions.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/cli/tui/widgets/logs.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/client.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/errors.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/logger.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/agent.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/agent_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/api_token_log.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/attack.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/attack_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/checkout_session_request_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/checkout_session_response.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/choice.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/choice_message.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/evaluation_status_enum.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/generate_error_response.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/generate_request_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/generate_success_response.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/generic_error_response.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/message_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/organization.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/organization_minimal.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/organization_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_agent_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_api_token_log_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_attack_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_organization_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_prompt_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_result_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_run_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_user_api_key_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/paginated_user_profile_list.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_agent_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_attack_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_organization_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_prompt_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_result_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_run_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/patched_user_profile_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/prompt.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/prompt_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/result.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/result_list_evaluation_status.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/result_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/run.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/run_list_status.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/run_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/status_enum.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/step_type_enum.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/trace.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/trace_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/usage.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/user_api_key.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/user_api_key_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/user_profile.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/user_profile_minimal.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/models/user_profile_request.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/adapters/base.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/adapters/google_adk.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/tracking/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/tracking/context.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/tracking/decorators.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/tracking/tracker.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/router/types.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/types.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/vulnerabilities/__init__.py +0 -0
- {hackagent-0.4.0 → hackagent-0.4.1}/hackagent/vulnerabilities/prompts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hackagent
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: HackAgent is an open-source security toolkit to detect vulnerabilities of your AI Agents.
|
|
5
5
|
Author-email: AI Security Lab <ais@ai4i.it>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,9 +17,7 @@ Requires-Python: >=3.10
|
|
|
17
17
|
Requires-Dist: click>=8.1.0
|
|
18
18
|
Requires-Dist: litellm>=1.69.2
|
|
19
19
|
Requires-Dist: openai>=1.0.0
|
|
20
|
-
Requires-Dist: pandas>=2.2.3
|
|
21
20
|
Requires-Dist: pydantic>=2.0
|
|
22
|
-
Requires-Dist: python-dotenv>=1.1.0
|
|
23
21
|
Requires-Dist: pyyaml>=6.0.0
|
|
24
22
|
Requires-Dist: requests>=2.31.0
|
|
25
23
|
Requires-Dist: rich>=14.0.0
|
|
@@ -13,20 +13,19 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
|
-
from typing import Any, Dict, Optional, Union
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
|
17
17
|
|
|
18
18
|
from hackagent import utils
|
|
19
|
-
from hackagent.attacks.registry import (
|
|
20
|
-
AdvPrefixOrchestrator,
|
|
21
|
-
BaselineOrchestrator,
|
|
22
|
-
PAIROrchestrator,
|
|
23
|
-
)
|
|
24
19
|
from hackagent.client import AuthenticatedClient
|
|
25
20
|
from hackagent.errors import HackAgentError
|
|
26
21
|
from hackagent.router import AgentRouter
|
|
27
22
|
from hackagent.router.types import AgentTypeEnum
|
|
28
23
|
from hackagent.vulnerabilities.prompts import DEFAULT_PROMPTS
|
|
29
24
|
|
|
25
|
+
# Lazy import for attack orchestrators to avoid ~0.5s startup delay
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
pass
|
|
28
|
+
|
|
30
29
|
logger = logging.getLogger(__name__)
|
|
31
30
|
|
|
32
31
|
|
|
@@ -66,7 +65,6 @@ class HackAgent:
|
|
|
66
65
|
api_key: Optional[str] = None,
|
|
67
66
|
raise_on_unexpected_status: bool = False,
|
|
68
67
|
timeout: Optional[float] = None,
|
|
69
|
-
env_file_path: Optional[str] = None,
|
|
70
68
|
metadata: Optional[Dict[str, Any]] = None,
|
|
71
69
|
adapter_operational_config: Optional[Dict[str, Any]] = None,
|
|
72
70
|
):
|
|
@@ -94,22 +92,18 @@ class HackAgent:
|
|
|
94
92
|
base_url: The base URL for the HackAgent API service.
|
|
95
93
|
api_key: The API key for authenticating with the HackAgent API.
|
|
96
94
|
If omitted, the client will attempt to retrieve it from the
|
|
97
|
-
|
|
98
|
-
parameter can specify a .env file to load this variable from.
|
|
95
|
+
config file (~/.config/hackagent/config.json).
|
|
99
96
|
raise_on_unexpected_status: If set to `True`, the API client will
|
|
100
97
|
raise an exception for any HTTP status codes that are not typically
|
|
101
98
|
expected for a successful operation. Defaults to `False`.
|
|
102
99
|
timeout: The timeout duration in seconds for API requests made by the
|
|
103
100
|
authenticated client. Defaults to `None` (which might mean a
|
|
104
101
|
default timeout from the underlying HTTP library is used).
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
file if not already present in the environment.
|
|
102
|
+
metadata: Optional dictionary containing agent-specific metadata.
|
|
103
|
+
adapter_operational_config: Optional configuration for the agent adapter.
|
|
108
104
|
"""
|
|
109
105
|
|
|
110
|
-
resolved_auth_token = utils.resolve_api_token(
|
|
111
|
-
direct_api_key_param=api_key, env_file_path=env_file_path
|
|
112
|
-
)
|
|
106
|
+
resolved_auth_token = utils.resolve_api_token(direct_api_key_param=api_key)
|
|
113
107
|
|
|
114
108
|
# Use default base_url if not provided
|
|
115
109
|
if base_url is None:
|
|
@@ -136,11 +130,26 @@ class HackAgent:
|
|
|
136
130
|
adapter_operational_config=adapter_operational_config,
|
|
137
131
|
)
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
133
|
+
# Attack strategies are lazy-loaded to improve startup time
|
|
134
|
+
self._attack_strategies: Optional[Dict[str, Any]] = None
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def attack_strategies(self) -> Dict[str, Any]:
|
|
138
|
+
"""Lazy-loaded attack strategies dictionary."""
|
|
139
|
+
if self._attack_strategies is None:
|
|
140
|
+
# Import here to avoid circular imports and improve startup time
|
|
141
|
+
from hackagent.attacks.registry import (
|
|
142
|
+
AdvPrefixOrchestrator,
|
|
143
|
+
BaselineOrchestrator,
|
|
144
|
+
PAIROrchestrator,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
self._attack_strategies = {
|
|
148
|
+
"advprefix": AdvPrefixOrchestrator(hack_agent=self),
|
|
149
|
+
"baseline": BaselineOrchestrator(hack_agent=self),
|
|
150
|
+
"pair": PAIROrchestrator(hack_agent=self),
|
|
151
|
+
}
|
|
152
|
+
return self._attack_strategies
|
|
144
153
|
|
|
145
154
|
def hack(
|
|
146
155
|
self,
|
|
@@ -21,7 +21,6 @@ using a factory function to eliminate boilerplate code.
|
|
|
21
21
|
The factory dynamically creates orchestrator classes that configure:
|
|
22
22
|
- attack_type: String identifier for the attack
|
|
23
23
|
- attack_impl_class: BaseAttack subclass implementing the algorithm
|
|
24
|
-
- Custom methods: Optional specialized behavior (e.g., PAIR's attacker setup)
|
|
25
24
|
|
|
26
25
|
To add a new attack:
|
|
27
26
|
1. Implement BaseAttack subclass in techniques/your_attack/
|
|
@@ -29,14 +28,13 @@ To add a new attack:
|
|
|
29
28
|
3. Add to ATTACK_REGISTRY dict
|
|
30
29
|
"""
|
|
31
30
|
|
|
32
|
-
from typing import
|
|
31
|
+
from typing import Callable, Optional, Type
|
|
33
32
|
|
|
34
33
|
from hackagent.attacks.orchestrator import AttackOrchestrator
|
|
35
34
|
from hackagent.attacks.techniques.advprefix import AdvPrefixAttack
|
|
36
35
|
from hackagent.attacks.techniques.base import BaseAttack
|
|
37
36
|
from hackagent.attacks.techniques.pair import PAIRAttack
|
|
38
37
|
from hackagent.attacks.techniques.baseline import BaselineAttack
|
|
39
|
-
from hackagent.router.types import AgentTypeEnum
|
|
40
38
|
|
|
41
39
|
|
|
42
40
|
def create_orchestrator(
|
|
@@ -70,50 +68,17 @@ def create_orchestrator(
|
|
|
70
68
|
"__doc__": f"{attack_name}: {attack_impl_class.__doc__ or 'Attack technique orchestrator'}",
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
# Add custom method if provided
|
|
71
|
+
# Add custom method if provided
|
|
74
72
|
if custom_setup:
|
|
75
73
|
class_attrs["_get_attack_impl_kwargs"] = custom_setup
|
|
76
74
|
|
|
77
75
|
return type(f"{attack_name}Orchestrator", (AttackOrchestrator,), class_attrs)
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
self,
|
|
82
|
-
attack_config: Dict[str, Any],
|
|
83
|
-
run_config_override: Optional[Dict[str, Any]],
|
|
84
|
-
run_id: str,
|
|
85
|
-
) -> Dict[str, Any]:
|
|
86
|
-
"""
|
|
87
|
-
PAIR-specific setup: creates attacker router for adversarial prompt generation.
|
|
88
|
-
|
|
89
|
-
PAIR uses a separate LLM as an "attacker" to generate adversarial prompts
|
|
90
|
-
that are then tested against the target agent.
|
|
91
|
-
"""
|
|
92
|
-
kwargs = AttackOrchestrator._get_attack_impl_kwargs(
|
|
93
|
-
self, attack_config, run_config_override, run_id
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
attacker_config = attack_config.get("attacker", {})
|
|
97
|
-
|
|
98
|
-
from hackagent.router import AgentRouter
|
|
99
|
-
|
|
100
|
-
kwargs["attacker_router"] = AgentRouter(
|
|
101
|
-
client=self.client,
|
|
102
|
-
name=attacker_config.get("identifier", "hackagent-attacker"),
|
|
103
|
-
agent_type=AgentTypeEnum.OPENAI_SDK,
|
|
104
|
-
endpoint=attacker_config.get("endpoint", "https://api.openai.com/v1"),
|
|
105
|
-
metadata=attacker_config,
|
|
106
|
-
adapter_operational_config=attacker_config,
|
|
107
|
-
overwrite_metadata=True,
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
return kwargs
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# Create orchestrators using factory (1 line per attack instead of 6-50 lines)
|
|
78
|
+
# Create orchestrators using factory (1 line per attack)
|
|
114
79
|
AdvPrefixOrchestrator = create_orchestrator("AdvPrefix", AdvPrefixAttack)
|
|
115
80
|
BaselineOrchestrator = create_orchestrator("Baseline", BaselineAttack)
|
|
116
|
-
PAIROrchestrator = create_orchestrator("PAIR", PAIRAttack
|
|
81
|
+
PAIROrchestrator = create_orchestrator("PAIR", PAIRAttack)
|
|
117
82
|
|
|
118
83
|
|
|
119
84
|
# Registry of all available attacks
|
|
@@ -34,16 +34,38 @@ from .config import DEFAULT_PREFIX_GENERATION_CONFIG
|
|
|
34
34
|
from .evaluation import EvaluationPipeline
|
|
35
35
|
from .generate import PrefixGenerationPipeline
|
|
36
36
|
|
|
37
|
-
# TUI logging support
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
37
|
+
# TUI logging support - lazy loaded to avoid circular imports
|
|
38
|
+
# The actual import happens inside with_tui_logging wrapper
|
|
39
|
+
_with_tui_logging = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _get_tui_logging_decorator():
|
|
43
|
+
"""Lazily import the TUI logging decorator to avoid circular imports."""
|
|
44
|
+
global _with_tui_logging
|
|
45
|
+
if _with_tui_logging is not None:
|
|
46
|
+
return _with_tui_logging
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from hackagent.cli.tui.logger import with_tui_logging
|
|
50
|
+
|
|
51
|
+
_with_tui_logging = with_tui_logging
|
|
52
|
+
except ImportError:
|
|
53
|
+
# Fallback decorator that does nothing if TUI is not available
|
|
54
|
+
def with_tui_logging(*args, **kwargs):
|
|
55
|
+
def decorator(func):
|
|
56
|
+
return func
|
|
57
|
+
|
|
58
|
+
return decorator
|
|
59
|
+
|
|
60
|
+
_with_tui_logging = with_tui_logging
|
|
61
|
+
|
|
62
|
+
return _with_tui_logging
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def with_tui_logging(*args, **kwargs):
|
|
66
|
+
"""Wrapper that lazily loads the actual TUI logging decorator."""
|
|
67
|
+
decorator = _get_tui_logging_decorator()
|
|
68
|
+
return decorator(*args, **kwargs)
|
|
47
69
|
|
|
48
70
|
|
|
49
71
|
# Helper function for deep merging dictionaries
|
|
@@ -221,17 +221,26 @@ def _get_completion_via_router(
|
|
|
221
221
|
"adapter_specific_events": None,
|
|
222
222
|
"error_message": None,
|
|
223
223
|
"log_message": None, # For per-prefix logging by the main loop
|
|
224
|
+
"result_id": None, # ID for updating evaluation status later
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
# Use route_with_tracking if we have run_id and client for real-time result creation
|
|
227
228
|
if run_id and client:
|
|
228
|
-
logger_instance.
|
|
229
|
-
|
|
229
|
+
logger_instance.debug(f"Calling route_with_tracking with run_id={run_id}")
|
|
230
|
+
tracking_result = agent_router.route_with_tracking(
|
|
230
231
|
registration_key=agent_reg_key,
|
|
231
232
|
request_data=request_data,
|
|
232
233
|
run_id=run_id,
|
|
233
234
|
client=client,
|
|
234
235
|
)
|
|
236
|
+
# route_with_tracking returns {"response": ..., "result_id": ...}
|
|
237
|
+
response = tracking_result.get("response", tracking_result)
|
|
238
|
+
# Capture result_id for later evaluation updates
|
|
239
|
+
result_dict["result_id"] = tracking_result.get("result_id")
|
|
240
|
+
if result_dict["result_id"]:
|
|
241
|
+
logger_instance.debug(
|
|
242
|
+
f"Captured result_id={result_dict['result_id']} for evaluation tracking"
|
|
243
|
+
)
|
|
235
244
|
else:
|
|
236
245
|
logger_instance.warning(
|
|
237
246
|
f"⚠️ Using fallback route_request (run_id={run_id}, client={client is not None})"
|
|
@@ -440,6 +449,14 @@ def execute(
|
|
|
440
449
|
"adapter_specific_events"
|
|
441
450
|
)
|
|
442
451
|
result["error_message"] = completion_result.get("error_message")
|
|
452
|
+
# Pass through result_id for evaluation status updates
|
|
453
|
+
result["result_id"] = completion_result.get("result_id")
|
|
443
454
|
results.append(result)
|
|
444
455
|
|
|
456
|
+
# Debug: verify result_ids are being passed through
|
|
457
|
+
result_ids_in_output = [r.get("result_id") for r in results if r.get("result_id")]
|
|
458
|
+
logger.info(
|
|
459
|
+
f"📊 Completions execute returning {len(results)} results with {len(result_ids_in_output)} result_ids"
|
|
460
|
+
)
|
|
461
|
+
|
|
445
462
|
return results
|
|
@@ -37,15 +37,16 @@ import math
|
|
|
37
37
|
from collections import defaultdict
|
|
38
38
|
from dataclasses import fields
|
|
39
39
|
from typing import Any, Dict, List, Optional
|
|
40
|
+
from uuid import UUID
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
from hackagent.api.result import result_partial_update
|
|
43
43
|
from hackagent.attacks.techniques.advprefix.evaluators import (
|
|
44
44
|
HarmBenchEvaluator,
|
|
45
45
|
JailbreakBenchEvaluator,
|
|
46
46
|
NuancedEvaluator,
|
|
47
47
|
)
|
|
48
48
|
from hackagent.client import AuthenticatedClient
|
|
49
|
+
from hackagent.models import EvaluationStatusEnum, PatchedResultRequest
|
|
49
50
|
from hackagent.router.types import AgentTypeEnum
|
|
50
51
|
|
|
51
52
|
from .config import EvaluationPipelineConfig, EvaluatorConfig
|
|
@@ -135,6 +136,14 @@ class EvaluationPipeline:
|
|
|
135
136
|
logger: Logger for tracking execution
|
|
136
137
|
client: Authenticated client for API access
|
|
137
138
|
"""
|
|
139
|
+
# Extract tracking context BEFORE converting to dataclass (which filters unknown fields)
|
|
140
|
+
self._run_id: Optional[str] = (
|
|
141
|
+
config.get("_run_id") if isinstance(config, dict) else None
|
|
142
|
+
)
|
|
143
|
+
self._tracking_client = (
|
|
144
|
+
config.get("_client") if isinstance(config, dict) else None
|
|
145
|
+
)
|
|
146
|
+
|
|
138
147
|
self.config = (
|
|
139
148
|
EvaluationPipelineConfig.from_dict(config)
|
|
140
149
|
if isinstance(config, dict)
|
|
@@ -181,6 +190,19 @@ class EvaluationPipeline:
|
|
|
181
190
|
Returns:
|
|
182
191
|
List of selected prefix dictionaries ready for final output
|
|
183
192
|
"""
|
|
193
|
+
# Debug: Log input data keys
|
|
194
|
+
if input_data:
|
|
195
|
+
sample = input_data[0]
|
|
196
|
+
self.logger.info(
|
|
197
|
+
f"📋 Evaluation input: {len(input_data)} rows, sample keys: {list(sample.keys())}"
|
|
198
|
+
)
|
|
199
|
+
result_ids_in_input = [
|
|
200
|
+
r.get("result_id") for r in input_data if r.get("result_id")
|
|
201
|
+
]
|
|
202
|
+
self.logger.info(
|
|
203
|
+
f"📋 Evaluation input has {len(result_ids_in_input)} result_ids"
|
|
204
|
+
)
|
|
205
|
+
|
|
184
206
|
self._statistics["input_count"] = len(input_data)
|
|
185
207
|
|
|
186
208
|
# Judge Evaluation
|
|
@@ -194,6 +216,9 @@ class EvaluationPipeline:
|
|
|
194
216
|
self.logger.warning("No data after evaluation")
|
|
195
217
|
return []
|
|
196
218
|
|
|
219
|
+
# Sync evaluation results to server
|
|
220
|
+
self._sync_evaluation_to_server(evaluated_data)
|
|
221
|
+
|
|
197
222
|
# Aggregation
|
|
198
223
|
self.logger.info(
|
|
199
224
|
f"Aggregation: Aggregating {len(evaluated_data)} evaluation results"
|
|
@@ -219,6 +244,113 @@ class EvaluationPipeline:
|
|
|
219
244
|
"""Return execution statistics for monitoring and debugging."""
|
|
220
245
|
return self._statistics.copy()
|
|
221
246
|
|
|
247
|
+
def _sync_evaluation_to_server(self, evaluated_data: List[Dict]) -> int:
|
|
248
|
+
"""
|
|
249
|
+
Sync evaluation results to the server by updating each result's status.
|
|
250
|
+
|
|
251
|
+
This method iterates through evaluated data and updates the server-side
|
|
252
|
+
Result records with the evaluation outcome (SUCCESSFUL_JAILBREAK or
|
|
253
|
+
FAILED_JAILBREAK) based on judge evaluation scores.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
evaluated_data: List of dicts with evaluation results including result_id key
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Number of results successfully updated
|
|
260
|
+
"""
|
|
261
|
+
self.logger.info(f"🔄 Starting evaluation sync for {len(evaluated_data)} rows")
|
|
262
|
+
|
|
263
|
+
client = self._tracking_client
|
|
264
|
+
if not client:
|
|
265
|
+
self.logger.warning(
|
|
266
|
+
"❌ No tracking client available - cannot sync evaluation to server"
|
|
267
|
+
)
|
|
268
|
+
return 0
|
|
269
|
+
|
|
270
|
+
# Check if any row has result_id
|
|
271
|
+
result_ids_found = [
|
|
272
|
+
row.get("result_id") for row in evaluated_data if row.get("result_id")
|
|
273
|
+
]
|
|
274
|
+
self.logger.info(
|
|
275
|
+
f"📋 Found {len(result_ids_found)} rows with result_id out of {len(evaluated_data)} total"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if not result_ids_found:
|
|
279
|
+
self.logger.warning("❌ No result_id in data - cannot sync to server")
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
updated_count = 0
|
|
283
|
+
total_with_ids = 0
|
|
284
|
+
|
|
285
|
+
for row in evaluated_data:
|
|
286
|
+
result_id = row.get("result_id")
|
|
287
|
+
if not result_id:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
total_with_ids += 1
|
|
291
|
+
|
|
292
|
+
# Determine success based on available evaluation scores
|
|
293
|
+
# Priority: JailbreakBench > HarmBench > Nuanced
|
|
294
|
+
# eval_jb/eval_hb/eval_nj: 1 = success (jailbreak), 0 = fail
|
|
295
|
+
success = False
|
|
296
|
+
eval_notes_parts = []
|
|
297
|
+
|
|
298
|
+
if "eval_jb" in row:
|
|
299
|
+
success = success or (row["eval_jb"] == 1)
|
|
300
|
+
eval_notes_parts.append(f"JailbreakBench: {row['eval_jb']}")
|
|
301
|
+
if "explanation_jb" in row:
|
|
302
|
+
eval_notes_parts.append(f"JB explanation: {row['explanation_jb']}")
|
|
303
|
+
|
|
304
|
+
if "eval_hb" in row:
|
|
305
|
+
success = success or (row["eval_hb"] == 1)
|
|
306
|
+
eval_notes_parts.append(f"HarmBench: {row['eval_hb']}")
|
|
307
|
+
if "explanation_hb" in row:
|
|
308
|
+
eval_notes_parts.append(f"HB explanation: {row['explanation_hb']}")
|
|
309
|
+
|
|
310
|
+
if "eval_nj" in row:
|
|
311
|
+
success = success or (row["eval_nj"] == 1)
|
|
312
|
+
eval_notes_parts.append(f"Nuanced: {row['eval_nj']}")
|
|
313
|
+
if "explanation_nj" in row:
|
|
314
|
+
eval_notes_parts.append(f"NJ explanation: {row['explanation_nj']}")
|
|
315
|
+
|
|
316
|
+
evaluation_notes = " | ".join(eval_notes_parts) if eval_notes_parts else ""
|
|
317
|
+
|
|
318
|
+
# Update the result status on the server
|
|
319
|
+
try:
|
|
320
|
+
if success:
|
|
321
|
+
eval_status = EvaluationStatusEnum.SUCCESSFUL_JAILBREAK
|
|
322
|
+
else:
|
|
323
|
+
eval_status = EvaluationStatusEnum.FAILED_JAILBREAK
|
|
324
|
+
|
|
325
|
+
result_request = PatchedResultRequest(
|
|
326
|
+
evaluation_status=eval_status,
|
|
327
|
+
evaluation_notes=evaluation_notes,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
response = result_partial_update.sync_detailed(
|
|
331
|
+
client=client,
|
|
332
|
+
id=UUID(result_id) if isinstance(result_id, str) else result_id,
|
|
333
|
+
body=result_request,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if response.status_code < 300:
|
|
337
|
+
self.logger.info(
|
|
338
|
+
f"✅ Updated result {result_id} to {eval_status.value}"
|
|
339
|
+
)
|
|
340
|
+
updated_count += 1
|
|
341
|
+
else:
|
|
342
|
+
self.logger.warning(
|
|
343
|
+
f"❌ Failed to update result {result_id}: status={response.status_code}, content={getattr(response, 'content', 'N/A')}"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
self.logger.error(f"Exception updating result {result_id}: {e}")
|
|
348
|
+
|
|
349
|
+
self.logger.info(
|
|
350
|
+
f"Synced {updated_count}/{total_with_ids} evaluation results to server"
|
|
351
|
+
)
|
|
352
|
+
return updated_count
|
|
353
|
+
|
|
222
354
|
# ========================================================================
|
|
223
355
|
# JUDGE EVALUATION METHODS
|
|
224
356
|
# ========================================================================
|
|
@@ -238,8 +370,8 @@ class EvaluationPipeline:
|
|
|
238
370
|
self.logger.warning("No judges configured, skipping evaluation")
|
|
239
371
|
return input_data
|
|
240
372
|
|
|
241
|
-
#
|
|
242
|
-
|
|
373
|
+
# Keep as list of dicts for evaluators
|
|
374
|
+
original_data = [row.copy() for row in input_data]
|
|
243
375
|
|
|
244
376
|
# Base config for evaluators
|
|
245
377
|
evaluator_base_config_dict = {
|
|
@@ -251,7 +383,7 @@ class EvaluationPipeline:
|
|
|
251
383
|
"organization_id": self.config.organization_id,
|
|
252
384
|
}
|
|
253
385
|
|
|
254
|
-
|
|
386
|
+
judge_results = {}
|
|
255
387
|
judges_to_run = self._prepare_judge_configs(
|
|
256
388
|
judge_configs_list, evaluator_base_config_dict
|
|
257
389
|
)
|
|
@@ -262,22 +394,22 @@ class EvaluationPipeline:
|
|
|
262
394
|
|
|
263
395
|
# Execute judges sequentially
|
|
264
396
|
for judge_type_str, subprocess_config in judges_to_run:
|
|
265
|
-
|
|
397
|
+
evaluated_data = self._run_single_evaluator(
|
|
266
398
|
judge_type=judge_type_str,
|
|
267
399
|
config=subprocess_config,
|
|
268
|
-
|
|
400
|
+
data=[row.copy() for row in original_data],
|
|
269
401
|
)
|
|
270
402
|
|
|
271
|
-
if
|
|
272
|
-
|
|
403
|
+
if evaluated_data is not None:
|
|
404
|
+
judge_results[judge_type_str] = evaluated_data
|
|
273
405
|
self._statistics["successful_judges"].append(judge_type_str)
|
|
274
406
|
else:
|
|
275
407
|
self._statistics["failed_judges"].append(judge_type_str)
|
|
276
408
|
|
|
277
409
|
# Merge results
|
|
278
|
-
|
|
410
|
+
final_data = self._merge_evaluation_results(original_data, judge_results)
|
|
279
411
|
|
|
280
|
-
return
|
|
412
|
+
return final_data
|
|
281
413
|
|
|
282
414
|
def _prepare_judge_configs(
|
|
283
415
|
self, judge_configs_list: List[Dict], base_config: Dict[str, Any]
|
|
@@ -322,8 +454,9 @@ class EvaluationPipeline:
|
|
|
322
454
|
judge_config_item.get("agent_name")
|
|
323
455
|
or f"judge-{judge_type_str}-{judge_identifier.replace('/', '-')[:20]}"
|
|
324
456
|
)
|
|
457
|
+
# Default to OPENAI_SDK to avoid Pydantic serialization warnings from LiteLLM
|
|
325
458
|
subprocess_config["agent_type"] = judge_config_item.get(
|
|
326
|
-
"agent_type", "
|
|
459
|
+
"agent_type", "OPENAI_SDK"
|
|
327
460
|
)
|
|
328
461
|
subprocess_config["model_id"] = judge_identifier
|
|
329
462
|
subprocess_config["agent_endpoint"] = judge_config_item.get("endpoint")
|
|
@@ -354,8 +487,8 @@ class EvaluationPipeline:
|
|
|
354
487
|
self,
|
|
355
488
|
judge_type: str,
|
|
356
489
|
config: Dict[str, Any],
|
|
357
|
-
|
|
358
|
-
) -> Optional[
|
|
490
|
+
data: List[Dict],
|
|
491
|
+
) -> Optional[List[Dict]]:
|
|
359
492
|
"""Execute a single evaluator process."""
|
|
360
493
|
evaluator_class = EVALUATOR_MAP.get(judge_type)
|
|
361
494
|
if not evaluator_class:
|
|
@@ -383,21 +516,31 @@ class EvaluationPipeline:
|
|
|
383
516
|
return None
|
|
384
517
|
|
|
385
518
|
evaluator_config = EvaluatorConfig(**filtered_config)
|
|
386
|
-
|
|
387
|
-
|
|
519
|
+
# Pass tracking context to the evaluator
|
|
520
|
+
evaluator = evaluator_class(
|
|
521
|
+
client=self.client,
|
|
522
|
+
config=evaluator_config,
|
|
523
|
+
run_id=self._run_id,
|
|
524
|
+
tracking_client=self._tracking_client,
|
|
525
|
+
)
|
|
526
|
+
evaluated_data = evaluator.evaluate(data)
|
|
388
527
|
|
|
389
528
|
# Return only merge keys + judge-specific columns
|
|
390
529
|
eval_cols = JUDGE_COLUMN_MAP.get(judge_type, [])
|
|
391
|
-
if not
|
|
530
|
+
if not evaluated_data:
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
if not all(key in evaluated_data[0] for key in MERGE_KEYS):
|
|
392
534
|
self.logger.error(
|
|
393
535
|
f"Evaluation result missing merge keys for {judge_type}"
|
|
394
536
|
)
|
|
395
537
|
return None
|
|
396
538
|
|
|
397
|
-
cols_to_return = MERGE_KEYS + [
|
|
398
|
-
|
|
539
|
+
cols_to_return = set(MERGE_KEYS + [col for col in eval_cols])
|
|
540
|
+
return [
|
|
541
|
+
{k: v for k, v in row.items() if k in cols_to_return}
|
|
542
|
+
for row in evaluated_data
|
|
399
543
|
]
|
|
400
|
-
return evaluated_df[cols_to_return]
|
|
401
544
|
|
|
402
545
|
except Exception as e:
|
|
403
546
|
self.logger.error(
|
|
@@ -408,30 +551,28 @@ class EvaluationPipeline:
|
|
|
408
551
|
del evaluator
|
|
409
552
|
|
|
410
553
|
def _merge_evaluation_results(
|
|
411
|
-
self,
|
|
412
|
-
) ->
|
|
554
|
+
self, original_data: List[Dict], judge_results: Dict[str, List[Dict]]
|
|
555
|
+
) -> List[Dict]:
|
|
413
556
|
"""Merge evaluation results from multiple judges."""
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
for judge_type, judge_df in judge_results.items():
|
|
557
|
+
# Build lookup dictionaries keyed by merge keys
|
|
558
|
+
for judge_type, judge_data in judge_results.items():
|
|
417
559
|
eval_cols = JUDGE_COLUMN_MAP.get(judge_type, [])
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if not judge_cols_present:
|
|
421
|
-
self.logger.warning(f"No evaluation columns found for {judge_type}")
|
|
560
|
+
if not judge_data:
|
|
422
561
|
continue
|
|
423
562
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
563
|
+
# Build lookup by merge keys
|
|
564
|
+
lookup = {}
|
|
565
|
+
for row in judge_data:
|
|
566
|
+
key = tuple(row.get(k) for k in MERGE_KEYS)
|
|
567
|
+
lookup[key] = {col: row.get(col) for col in eval_cols if col in row}
|
|
568
|
+
|
|
569
|
+
# Merge into original data
|
|
570
|
+
for row in original_data:
|
|
571
|
+
key = tuple(row.get(k) for k in MERGE_KEYS)
|
|
572
|
+
if key in lookup:
|
|
573
|
+
row.update(lookup[key])
|
|
433
574
|
|
|
434
|
-
return
|
|
575
|
+
return original_data
|
|
435
576
|
|
|
436
577
|
# ========================================================================
|
|
437
578
|
# AGGREGATION METHODS
|