10xscale-agentflow-cli 0.3.2.4__tar.gz → 0.3.2.5__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.
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/PKG-INFO +1 -1
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/SOURCES.txt +1 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/PKG-INFO +1 -1
- 10xscale_agentflow_cli-0.3.2.5/agentflow_cli/cli/commands/eval.py +622 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/main.py +20 -4
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/agentflow.json +2 -0
- 10xscale_agentflow_cli-0.3.2.5/agentflow_cli/cli/templates/prod/evals/user_simulator_eval.py +76 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/evals/weather_agents_eval.py +14 -2
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/pyproject.toml +1 -1
- 10xscale_agentflow_cli-0.3.2.4/agentflow_cli/cli/commands/eval.py +0 -273
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/dependency_links.txt +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/entry_points.txt +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/not-zip-safe +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/requires.txt +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/10xscale_agentflow_cli.egg-info/top_level.txt +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/MANIFEST.in +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/README.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow.json +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/api.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/build.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/init.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/skills.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/test.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/commands/version.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/constants.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/core/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/core/config.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/core/output.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/core/validation.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/exceptions.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/logger.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/defaults.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/dev/agentflow.json +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/dev/graph/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/dev/graph/agent.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/.pre-commot-config.yaml +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/auth/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/auth/agent_auth.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/evals/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/agent.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/nodes/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/nodes/main_node.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/nodes/tool_node.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/state.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/thread_name_generator.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/tools/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/tools/weather_tool.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/utils/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/utils/tool_decision.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/validators/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/validators/lifecyle.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/validators/manager.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/graph/validators/validators.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/tests/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/tests/conftest.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/tests/test_agent_eval.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/tests/test_catalog_tools.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/prod/tests/test_graph_nodes.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/SKILL.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/agents-and-tools.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/api-client.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/api-configuration.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/api-settings-and-middleware.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/architecture.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/auth-and-authorization.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/callbacks-and-command.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/checkpointing-and-threads.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/cli-commands.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/client-auth-and-errors.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/client-messages-invoke-stream.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/client-threads-memory-files.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/context-id-background.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/dependency-injection.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/evaluation.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/id-and-thread-name-generators.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/media-and-files.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/memory-and-store.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/prebuilt-agents-and-tools.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/production-runtime.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/providers-and-adapters.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/publishers-and-runtime-protocols.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/rate-limiting.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/remote-tools.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/rest-api-and-errors.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/security-and-validators.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/state-and-messages.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/state-graph.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/stream-emitter.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/streaming.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/testing-and-evaluation.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/agent-skills/references/unit-testing.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/cli/templates/skills/copilot/agentflow.instructions.md +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/auth_backend.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/authorization.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/base_auth.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/jwt_auth.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/auth/permissions.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/graph_config.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/media_settings.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/sentry_config.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/settings.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/setup_logs.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/setup_middleware.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/config/worker_middleware.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/exceptions/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/exceptions/general_exception.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/exceptions/handle_errors.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/exceptions/resources_exceptions.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/exceptions/user_exception.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/base.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/factory.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/memory.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/middleware.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/rate_limit/redis.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/request_limits.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/middleware/security_headers.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/utils/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/core/utils/log_sanitizer.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/loader.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/main.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/a2a.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/a2ui.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/schemas/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/services/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/checkpointer/services/checkpointer_service.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/schemas/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/schemas/graph_schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/services/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/services/graph_service.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/graph/services/multimodal_preprocessor.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/media/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/media/router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/media/schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/ping/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/ping/router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/setup_router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/router.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/schemas/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/schemas/store_schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/services/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/routers/store/services/store_service.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/tasks/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/tasks/user_tasks.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/callable_helper.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/media/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/media/extractor.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/media/pipeline.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/parse_output.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/response_helper.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/schemas/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/schemas/output_schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/schemas/user_schemas.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/snowflake_id_generator.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/swagger_helper.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/utils/thread_name_generator.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/agentflow_cli/src/app/worker.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/graph/__init__.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/graph/react.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/requirements.txt +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/setup.cfg +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/tests/test_multimodal_sprint2_extraction.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/tests/test_sprint4_media_api.py +0 -0
- {10xscale_agentflow_cli-0.3.2.4 → 10xscale_agentflow_cli-0.3.2.5}/tests/test_utils_parse_and_callable.py +0 -0
|
@@ -38,6 +38,7 @@ agentflow_cli/cli/templates/prod/agentflow.json
|
|
|
38
38
|
agentflow_cli/cli/templates/prod/auth/__init__.py
|
|
39
39
|
agentflow_cli/cli/templates/prod/auth/agent_auth.py
|
|
40
40
|
agentflow_cli/cli/templates/prod/evals/__init__.py
|
|
41
|
+
agentflow_cli/cli/templates/prod/evals/user_simulator_eval.py
|
|
41
42
|
agentflow_cli/cli/templates/prod/evals/weather_agents_eval.py
|
|
42
43
|
agentflow_cli/cli/templates/prod/graph/__init__.py
|
|
43
44
|
agentflow_cli/cli/templates/prod/graph/agent.py
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
"""Eval command — discover and run agentflow evaluations, always generating reports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib
|
|
7
|
+
import importlib.util
|
|
8
|
+
import inspect
|
|
9
|
+
import sys
|
|
10
|
+
import typing
|
|
11
|
+
import webbrowser
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from agentflow.qa.evaluation import CriterionConfig, EvalConfig, MatchType
|
|
18
|
+
from agentflow.qa.evaluation.collectors.trajectory_collector import TrajectoryCollector
|
|
19
|
+
from agentflow.qa.evaluation.config.eval_config import ReporterConfig
|
|
20
|
+
from agentflow.qa.evaluation.eval_result import EvalReport as ER, EvalCaseResult
|
|
21
|
+
from agentflow.qa.evaluation.evaluator import AgentEvaluator
|
|
22
|
+
from agentflow.qa.evaluation.reporters.manager import ReporterManager
|
|
23
|
+
|
|
24
|
+
from agentflow_cli.cli.commands import BaseCommand
|
|
25
|
+
from agentflow_cli.cli.core.config import ConfigManager
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from agentflow.qa.evaluation.eval_result import EvalReport
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class _PendingCase:
|
|
34
|
+
case: Any # EvalCase
|
|
35
|
+
evaluator: AgentEvaluator
|
|
36
|
+
file_name: str
|
|
37
|
+
eval_set_id: str
|
|
38
|
+
eval_set_name: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class _PendingSimulation:
|
|
43
|
+
scenario: Any # ConversationScenario
|
|
44
|
+
graph: Any
|
|
45
|
+
simulator: Any # UserSimulator
|
|
46
|
+
file_name: str
|
|
47
|
+
eval_set_id: str
|
|
48
|
+
eval_set_name: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EvalCommand(BaseCommand):
|
|
52
|
+
"""Discover and run agent evaluations; always write HTML + JSON reports."""
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Discovery
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def _discover(self, target: Path) -> list[Path]:
|
|
59
|
+
"""Return eval files under target. If target is a file, return it directly."""
|
|
60
|
+
if target.is_file():
|
|
61
|
+
return [target]
|
|
62
|
+
|
|
63
|
+
seen: dict[Path, None] = {}
|
|
64
|
+
for pattern in ("*_eval.py", "eval_*.py"):
|
|
65
|
+
for p in sorted(target.rglob(pattern)):
|
|
66
|
+
seen[p] = None
|
|
67
|
+
return list(seen)
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# Module loading
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _load_module(self, path: Path) -> Any:
|
|
74
|
+
project_root = str(Path.cwd())
|
|
75
|
+
if project_root not in sys.path:
|
|
76
|
+
sys.path.insert(0, project_root)
|
|
77
|
+
|
|
78
|
+
spec = importlib.util.spec_from_file_location("_agentflow_eval", path)
|
|
79
|
+
mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type]
|
|
80
|
+
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
|
81
|
+
return mod
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
# Agent loading from agentflow.json
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def _load_agent_from_config(self) -> Any:
|
|
88
|
+
config_manager = ConfigManager()
|
|
89
|
+
discovered = config_manager.auto_discover_config()
|
|
90
|
+
if not discovered:
|
|
91
|
+
raise RuntimeError("No agentflow.json found — cannot auto-load agent.")
|
|
92
|
+
config_manager.load_config(str(discovered))
|
|
93
|
+
agent_spec: str = config_manager.get_config_value("agent", default="")
|
|
94
|
+
if not agent_spec or ":" not in agent_spec:
|
|
95
|
+
raise RuntimeError(f"Invalid 'agent' field in agentflow.json: {agent_spec!r}")
|
|
96
|
+
module_path, attr = agent_spec.rsplit(":", 1)
|
|
97
|
+
mod = importlib.import_module(module_path)
|
|
98
|
+
return getattr(mod, attr)
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# Default config
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def _default_config(self) -> EvalConfig:
|
|
105
|
+
return EvalConfig(
|
|
106
|
+
criteria={
|
|
107
|
+
"response_match": CriterionConfig(threshold=0.6, match_type=MatchType.ANY_ORDER),
|
|
108
|
+
"tool_name_match_score": CriterionConfig(
|
|
109
|
+
threshold=0.6, match_type=MatchType.ANY_ORDER, check_args=False
|
|
110
|
+
),
|
|
111
|
+
"node_order": CriterionConfig(threshold=0.6, match_type=MatchType.IN_ORDER),
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _collect_eval_functions(self, mod: Any) -> tuple[list[tuple[str, Any]], Any]:
|
|
116
|
+
"""Pytest-style discovery: functions annotated -> EvalSet are evals, -> EvalConfig is config."""
|
|
117
|
+
from agentflow.qa.evaluation import EvalConfig, EvalSet
|
|
118
|
+
|
|
119
|
+
eval_pairs: list[tuple[str, Any]] = []
|
|
120
|
+
config: Any = None
|
|
121
|
+
|
|
122
|
+
for name, obj in inspect.getmembers(mod, inspect.isfunction):
|
|
123
|
+
if name.startswith("_"):
|
|
124
|
+
continue
|
|
125
|
+
if getattr(obj, "__module__", None) != getattr(mod, "__name__", None):
|
|
126
|
+
continue
|
|
127
|
+
hints: dict[str, Any] = {}
|
|
128
|
+
try:
|
|
129
|
+
hints = typing.get_type_hints(obj)
|
|
130
|
+
except Exception:
|
|
131
|
+
hints = getattr(obj, "__annotations__", {})
|
|
132
|
+
|
|
133
|
+
ret = hints.get("return")
|
|
134
|
+
if ret is None:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
if inspect.isclass(ret) and issubclass(ret, EvalSet):
|
|
139
|
+
eval_pairs.append((name, obj()))
|
|
140
|
+
continue
|
|
141
|
+
if inspect.isclass(ret) and issubclass(ret, EvalConfig) and config is None:
|
|
142
|
+
config = obj()
|
|
143
|
+
except Exception as exc:
|
|
144
|
+
self.logger.warning("Could not call %s(): %s", name, exc)
|
|
145
|
+
|
|
146
|
+
return eval_pairs, config
|
|
147
|
+
|
|
148
|
+
# ------------------------------------------------------------------
|
|
149
|
+
# Flat pool: collect all pending cases from a file
|
|
150
|
+
# ------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
def _collect_from_file(
|
|
153
|
+
self, path: Path, global_config: EvalConfig
|
|
154
|
+
) -> list[_PendingCase | _PendingSimulation]:
|
|
155
|
+
"""Load a module and return pending work for every eval case or simulation scenario.
|
|
156
|
+
|
|
157
|
+
Returns _PendingSimulation items when the file exposes get_scenarios() or SCENARIOS.
|
|
158
|
+
Returns _PendingCase items for the standard get_eval_set() / pytest-style protocols.
|
|
159
|
+
"""
|
|
160
|
+
mod = self._load_module(path)
|
|
161
|
+
file_name = path.name
|
|
162
|
+
|
|
163
|
+
# Simulator protocol — get_scenarios() or SCENARIOS constant
|
|
164
|
+
scenarios = None
|
|
165
|
+
if hasattr(mod, "get_scenarios"):
|
|
166
|
+
try:
|
|
167
|
+
scenarios = mod.get_scenarios()
|
|
168
|
+
except Exception as exc:
|
|
169
|
+
self.logger.warning("Could not call get_scenarios() in %s: %s", file_name, exc)
|
|
170
|
+
elif hasattr(mod, "SCENARIOS"):
|
|
171
|
+
scenarios = mod.SCENARIOS
|
|
172
|
+
|
|
173
|
+
if scenarios is not None:
|
|
174
|
+
return self._collect_simulations(mod, scenarios, file_name)
|
|
175
|
+
|
|
176
|
+
# get_eval_set() protocol
|
|
177
|
+
if hasattr(mod, "get_eval_set"):
|
|
178
|
+
if hasattr(mod, "get_eval_config"):
|
|
179
|
+
file_config = mod.get_eval_config()
|
|
180
|
+
elif hasattr(mod, "EVAL_CONFIG"):
|
|
181
|
+
file_config = mod.EVAL_CONFIG
|
|
182
|
+
else:
|
|
183
|
+
file_config = global_config
|
|
184
|
+
config = global_config if global_config.criteria else file_config
|
|
185
|
+
return self._make_pending(mod, mod.get_eval_set(), config, file_name)
|
|
186
|
+
|
|
187
|
+
# pytest-style discovery
|
|
188
|
+
eval_pairs, discovered_config = self._collect_eval_functions(mod)
|
|
189
|
+
if eval_pairs:
|
|
190
|
+
file_config = discovered_config or (
|
|
191
|
+
mod.get_eval_config()
|
|
192
|
+
if hasattr(mod, "get_eval_config")
|
|
193
|
+
else mod.EVAL_CONFIG
|
|
194
|
+
if hasattr(mod, "EVAL_CONFIG")
|
|
195
|
+
else global_config
|
|
196
|
+
)
|
|
197
|
+
config = global_config if global_config.criteria else file_config
|
|
198
|
+
pending: list[_PendingCase] = []
|
|
199
|
+
for _, es in eval_pairs:
|
|
200
|
+
pending.extend(self._make_pending(mod, es, config, file_name))
|
|
201
|
+
return pending
|
|
202
|
+
|
|
203
|
+
self.output.warning(f"Skipping {file_name} — no eval entry point found.")
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
def _collect_simulations(
|
|
207
|
+
self, mod: Any, scenarios: list[Any], file_name: str
|
|
208
|
+
) -> list[_PendingSimulation]:
|
|
209
|
+
"""Build _PendingSimulation items for each scenario in the file."""
|
|
210
|
+
from agentflow.qa.evaluation import (
|
|
211
|
+
CriterionConfig,
|
|
212
|
+
SimulationGoalsCriterion,
|
|
213
|
+
UserSimulator,
|
|
214
|
+
UserSimulatorConfig,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
graph = getattr(mod, "app", None) or self._load_agent_from_config()
|
|
218
|
+
|
|
219
|
+
# Per-file simulator config via SIMULATOR_CONFIG constant or default
|
|
220
|
+
sim_cfg: UserSimulatorConfig | None = getattr(mod, "SIMULATOR_CONFIG", None)
|
|
221
|
+
goal_threshold: float = 0.7
|
|
222
|
+
if sim_cfg is not None and hasattr(sim_cfg, "goal_threshold"):
|
|
223
|
+
goal_threshold = sim_cfg.goal_threshold # type: ignore[attr-defined]
|
|
224
|
+
|
|
225
|
+
judge = SimulationGoalsCriterion(
|
|
226
|
+
config=CriterionConfig(threshold=goal_threshold, num_samples=1)
|
|
227
|
+
)
|
|
228
|
+
simulator = UserSimulator(
|
|
229
|
+
config=sim_cfg or UserSimulatorConfig(),
|
|
230
|
+
criteria=[judge],
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
eval_set_id = f"{Path(file_name).stem}_simulations"
|
|
234
|
+
eval_set_name = f"{Path(file_name).stem} (user simulator)"
|
|
235
|
+
|
|
236
|
+
return [
|
|
237
|
+
_PendingSimulation(
|
|
238
|
+
scenario=sc,
|
|
239
|
+
graph=graph,
|
|
240
|
+
simulator=simulator,
|
|
241
|
+
file_name=file_name,
|
|
242
|
+
eval_set_id=eval_set_id,
|
|
243
|
+
eval_set_name=eval_set_name,
|
|
244
|
+
)
|
|
245
|
+
for sc in scenarios
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
def _make_pending(
|
|
249
|
+
self, mod: Any, eval_set: Any, config: EvalConfig, file_name: str
|
|
250
|
+
) -> list[_PendingCase]:
|
|
251
|
+
graph = getattr(mod, "app", None) or self._load_agent_from_config()
|
|
252
|
+
collector = TrajectoryCollector(capture_all_events=True)
|
|
253
|
+
evaluator = AgentEvaluator(graph, collector, config=config)
|
|
254
|
+
return [
|
|
255
|
+
_PendingCase(
|
|
256
|
+
case=c,
|
|
257
|
+
evaluator=evaluator,
|
|
258
|
+
file_name=file_name,
|
|
259
|
+
eval_set_id=eval_set.eval_set_id,
|
|
260
|
+
eval_set_name=eval_set.name,
|
|
261
|
+
)
|
|
262
|
+
for c in eval_set.eval_cases
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
# ------------------------------------------------------------------
|
|
266
|
+
# Progress printing
|
|
267
|
+
# ------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
def _print_case_progress(
|
|
270
|
+
self,
|
|
271
|
+
file_name: str,
|
|
272
|
+
case_name: str,
|
|
273
|
+
result: EvalCaseResult,
|
|
274
|
+
index: int,
|
|
275
|
+
total: int,
|
|
276
|
+
) -> None:
|
|
277
|
+
status = "PASSED" if result.passed else ("ERROR" if result.is_error else "FAILED")
|
|
278
|
+
duration = f"{result.duration_seconds:.2f}s"
|
|
279
|
+
label = f"{file_name}::{case_name}"
|
|
280
|
+
status_colored = (
|
|
281
|
+
f"\033[32m{status}\033[0m" if result.passed else f"\033[31m{status}\033[0m"
|
|
282
|
+
)
|
|
283
|
+
print(f"[{index:3d}/{total}] {label} {status_colored} {duration}", flush=True)
|
|
284
|
+
|
|
285
|
+
# ------------------------------------------------------------------
|
|
286
|
+
# Flat pool execution — single asyncio event loop for all cases
|
|
287
|
+
# ------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
async def _run_flat_pool(
|
|
290
|
+
self,
|
|
291
|
+
pending: list[_PendingCase | _PendingSimulation],
|
|
292
|
+
max_concurrency: int,
|
|
293
|
+
parallel: bool,
|
|
294
|
+
) -> list[tuple[str, str, str, EvalCaseResult]]:
|
|
295
|
+
"""Run all cases and simulations under a single event loop.
|
|
296
|
+
|
|
297
|
+
Returns list of (file_name, eval_set_id, eval_set_name, EvalCaseResult).
|
|
298
|
+
"""
|
|
299
|
+
total = len(pending)
|
|
300
|
+
completed = 0
|
|
301
|
+
|
|
302
|
+
async def _run_case(pc: _PendingCase) -> tuple[str, str, str, EvalCaseResult]:
|
|
303
|
+
local_collector = TrajectoryCollector(
|
|
304
|
+
capture_all_events=pc.evaluator.collector.capture_all_events,
|
|
305
|
+
)
|
|
306
|
+
try:
|
|
307
|
+
result = await pc.evaluator._evaluate_case(
|
|
308
|
+
pc.case, collector_override=local_collector
|
|
309
|
+
)
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
result = EvalCaseResult.failure(
|
|
312
|
+
eval_id=pc.case.eval_id,
|
|
313
|
+
error=str(exc),
|
|
314
|
+
name=pc.case.name,
|
|
315
|
+
)
|
|
316
|
+
return (pc.file_name, pc.eval_set_id, pc.eval_set_name, result)
|
|
317
|
+
|
|
318
|
+
async def _run_simulation(ps: _PendingSimulation) -> tuple[str, str, str, EvalCaseResult]:
|
|
319
|
+
from agentflow.qa.evaluation.eval_result import CriterionResult
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
sim_result = await ps.simulator.run(ps.graph, ps.scenario)
|
|
323
|
+
criterion_results = [
|
|
324
|
+
CriterionResult.success(
|
|
325
|
+
criterion=name,
|
|
326
|
+
score=score,
|
|
327
|
+
threshold=ps.simulator.criteria[0].threshold
|
|
328
|
+
if ps.simulator.criteria
|
|
329
|
+
else 0.7,
|
|
330
|
+
details=sim_result.criterion_details.get(name, {}),
|
|
331
|
+
)
|
|
332
|
+
for name, score in sim_result.criterion_scores.items()
|
|
333
|
+
]
|
|
334
|
+
# If no criterion ran (no goals defined), fall back to completion flag
|
|
335
|
+
if not criterion_results:
|
|
336
|
+
score = 1.0 if sim_result.completed else 0.0
|
|
337
|
+
criterion_results = [
|
|
338
|
+
CriterionResult.success(
|
|
339
|
+
criterion="simulation_completed",
|
|
340
|
+
score=score,
|
|
341
|
+
threshold=0.5,
|
|
342
|
+
)
|
|
343
|
+
]
|
|
344
|
+
conversation_text = "\n".join(
|
|
345
|
+
f"{m['role'].upper()}: {m['content']}"
|
|
346
|
+
for m in sim_result.conversation
|
|
347
|
+
)
|
|
348
|
+
result = EvalCaseResult.success(
|
|
349
|
+
eval_id=ps.scenario.scenario_id,
|
|
350
|
+
name=ps.scenario.description or ps.scenario.scenario_id,
|
|
351
|
+
criterion_results=criterion_results,
|
|
352
|
+
actual_response=conversation_text,
|
|
353
|
+
metadata={
|
|
354
|
+
"turns": sim_result.turns,
|
|
355
|
+
"goals_achieved": sim_result.goals_achieved,
|
|
356
|
+
"completed": sim_result.completed,
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
except Exception as exc:
|
|
360
|
+
result = EvalCaseResult.failure(
|
|
361
|
+
eval_id=ps.scenario.scenario_id,
|
|
362
|
+
error=str(exc),
|
|
363
|
+
name=ps.scenario.description or ps.scenario.scenario_id,
|
|
364
|
+
)
|
|
365
|
+
return (ps.file_name, ps.eval_set_id, ps.eval_set_name, result)
|
|
366
|
+
|
|
367
|
+
async def _dispatch(
|
|
368
|
+
item: _PendingCase | _PendingSimulation,
|
|
369
|
+
) -> tuple[str, str, str, EvalCaseResult]:
|
|
370
|
+
if isinstance(item, _PendingSimulation):
|
|
371
|
+
return await _run_simulation(item)
|
|
372
|
+
return await _run_case(item)
|
|
373
|
+
|
|
374
|
+
if not parallel:
|
|
375
|
+
results: list[tuple[str, str, str, EvalCaseResult]] = []
|
|
376
|
+
for item in pending:
|
|
377
|
+
quad = await _dispatch(item)
|
|
378
|
+
completed += 1
|
|
379
|
+
file_name, _, _, result = quad
|
|
380
|
+
self._print_case_progress(
|
|
381
|
+
file_name, result.name or result.eval_id, result, completed, total
|
|
382
|
+
)
|
|
383
|
+
results.append(quad)
|
|
384
|
+
return results
|
|
385
|
+
|
|
386
|
+
semaphore = asyncio.Semaphore(max_concurrency)
|
|
387
|
+
|
|
388
|
+
async def _run_one(
|
|
389
|
+
item: _PendingCase | _PendingSimulation,
|
|
390
|
+
) -> tuple[str, str, str, EvalCaseResult]:
|
|
391
|
+
async with semaphore:
|
|
392
|
+
return await _dispatch(item)
|
|
393
|
+
|
|
394
|
+
output_results: list[tuple[str, str, str, EvalCaseResult]] = []
|
|
395
|
+
tasks = [asyncio.create_task(_run_one(item)) for item in pending]
|
|
396
|
+
for coro in asyncio.as_completed(tasks):
|
|
397
|
+
quad = await coro
|
|
398
|
+
completed += 1
|
|
399
|
+
file_name, _, _, result = quad
|
|
400
|
+
self._print_case_progress(
|
|
401
|
+
file_name, result.name or result.eval_id, result, completed, total
|
|
402
|
+
)
|
|
403
|
+
output_results.append(quad)
|
|
404
|
+
|
|
405
|
+
return output_results
|
|
406
|
+
|
|
407
|
+
# ------------------------------------------------------------------
|
|
408
|
+
# Report merging
|
|
409
|
+
# ------------------------------------------------------------------
|
|
410
|
+
|
|
411
|
+
def _merge_reports(self, reports: list[EvalReport]) -> EvalReport:
|
|
412
|
+
if len(reports) == 1:
|
|
413
|
+
return reports[0]
|
|
414
|
+
|
|
415
|
+
all_results = []
|
|
416
|
+
for r in reports:
|
|
417
|
+
all_results.extend(r.results)
|
|
418
|
+
return ER.create(
|
|
419
|
+
eval_set_id="combined_eval",
|
|
420
|
+
eval_set_name="Combined Evaluation",
|
|
421
|
+
results=all_results,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# ------------------------------------------------------------------
|
|
425
|
+
# Eval directory from agentflow.json
|
|
426
|
+
# ------------------------------------------------------------------
|
|
427
|
+
|
|
428
|
+
def _resolve_eval_dir(self) -> Path:
|
|
429
|
+
config_manager = ConfigManager()
|
|
430
|
+
discovered = config_manager.auto_discover_config()
|
|
431
|
+
directory = "evals"
|
|
432
|
+
if discovered:
|
|
433
|
+
try:
|
|
434
|
+
config_manager.load_config(str(discovered))
|
|
435
|
+
eval_cfg = config_manager.get_evaluation_config()
|
|
436
|
+
directory = eval_cfg.get("directory", "evals")
|
|
437
|
+
except Exception:
|
|
438
|
+
self.logger.warning(
|
|
439
|
+
"Failed to load eval directory from config; using default 'evals/'"
|
|
440
|
+
)
|
|
441
|
+
return Path.cwd() / directory
|
|
442
|
+
|
|
443
|
+
# ------------------------------------------------------------------
|
|
444
|
+
# Main entry point
|
|
445
|
+
# ------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
def execute( # noqa: PLR0912, PLR0915
|
|
448
|
+
self,
|
|
449
|
+
target: str | None = None,
|
|
450
|
+
output_dir: str = "eval_reports",
|
|
451
|
+
no_report: bool = False,
|
|
452
|
+
threshold: float | None = None,
|
|
453
|
+
open_report: bool = False,
|
|
454
|
+
parallel: bool = False,
|
|
455
|
+
max_concurrency: int = 4,
|
|
456
|
+
verbose: bool = False,
|
|
457
|
+
quiet: bool = False,
|
|
458
|
+
**kwargs: Any,
|
|
459
|
+
) -> int:
|
|
460
|
+
# 1. Load global config from agentflow.json
|
|
461
|
+
global_eval_cfg: dict[str, Any] = {}
|
|
462
|
+
config_manager = ConfigManager()
|
|
463
|
+
discovered = config_manager.auto_discover_config()
|
|
464
|
+
if discovered:
|
|
465
|
+
try:
|
|
466
|
+
config_manager.load_config(str(discovered))
|
|
467
|
+
global_eval_cfg = config_manager.get_evaluation_config()
|
|
468
|
+
if output_dir == "eval_reports":
|
|
469
|
+
output_dir = global_eval_cfg.get("output_dir", output_dir)
|
|
470
|
+
if threshold is None:
|
|
471
|
+
threshold = global_eval_cfg.get("threshold")
|
|
472
|
+
except Exception:
|
|
473
|
+
self.logger.warning(
|
|
474
|
+
"Failed to load eval config from agentflow.json; using defaults"
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# 2. Build typed EvalConfig; CLI flags override everything.
|
|
478
|
+
# If agentflow.json has an evaluation section but no criteria key, inject
|
|
479
|
+
# the built-in defaults so at least one criterion always runs.
|
|
480
|
+
try:
|
|
481
|
+
global_config = (
|
|
482
|
+
EvalConfig.model_validate(global_eval_cfg)
|
|
483
|
+
if global_eval_cfg
|
|
484
|
+
else self._default_config()
|
|
485
|
+
)
|
|
486
|
+
except Exception:
|
|
487
|
+
global_config = self._default_config()
|
|
488
|
+
|
|
489
|
+
if not global_config.criteria:
|
|
490
|
+
global_config.criteria = self._default_config().criteria
|
|
491
|
+
|
|
492
|
+
if parallel:
|
|
493
|
+
global_config.parallel = True
|
|
494
|
+
if max_concurrency != 4:
|
|
495
|
+
global_config.max_concurrency = max_concurrency
|
|
496
|
+
|
|
497
|
+
effective_parallel = global_config.parallel
|
|
498
|
+
effective_concurrency = global_config.max_concurrency
|
|
499
|
+
|
|
500
|
+
# 3. Resolve target path
|
|
501
|
+
if target:
|
|
502
|
+
target_path = Path(target)
|
|
503
|
+
if not target_path.exists():
|
|
504
|
+
self.output.error(f"Path not found: {target}")
|
|
505
|
+
return 1
|
|
506
|
+
else:
|
|
507
|
+
target_path = self._resolve_eval_dir()
|
|
508
|
+
if not target_path.exists():
|
|
509
|
+
self.output.error(
|
|
510
|
+
f"Eval directory '{target_path}' not found. "
|
|
511
|
+
"Create an evals/ directory or pass a file/folder path."
|
|
512
|
+
)
|
|
513
|
+
return 1
|
|
514
|
+
|
|
515
|
+
# 4. Discover files
|
|
516
|
+
files = self._discover(target_path)
|
|
517
|
+
if not files:
|
|
518
|
+
self.output.error(f"No eval files found in {target_path}")
|
|
519
|
+
return 1
|
|
520
|
+
|
|
521
|
+
# 5. Collect all pending cases across every file
|
|
522
|
+
pending: list[_PendingCase] = []
|
|
523
|
+
for f in files:
|
|
524
|
+
try:
|
|
525
|
+
cases = self._collect_from_file(f, global_config)
|
|
526
|
+
pending.extend(cases)
|
|
527
|
+
except Exception as exc:
|
|
528
|
+
self.output.error(f"Error loading {f.name}: {exc}")
|
|
529
|
+
self.logger.exception("Failed to load eval file: %s", f)
|
|
530
|
+
|
|
531
|
+
if not pending:
|
|
532
|
+
self.output.error(
|
|
533
|
+
"No eval cases found. Ensure eval files expose get_eval_set() "
|
|
534
|
+
"or functions annotated with -> EvalSet."
|
|
535
|
+
)
|
|
536
|
+
return 1
|
|
537
|
+
|
|
538
|
+
n_files = len({pc.file_name for pc in pending})
|
|
539
|
+
n_sims = sum(1 for pc in pending if isinstance(pc, _PendingSimulation))
|
|
540
|
+
n_cases = len(pending) - n_sims
|
|
541
|
+
parts = []
|
|
542
|
+
if n_cases:
|
|
543
|
+
parts.append(f"{n_cases} eval case(s)")
|
|
544
|
+
if n_sims:
|
|
545
|
+
parts.append(f"{n_sims} simulation scenario(s)")
|
|
546
|
+
self.output.print_banner(
|
|
547
|
+
"Eval",
|
|
548
|
+
f"Found {', '.join(parts)} across {n_files} file(s) in {target_path}",
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# 6. Run all cases under a single asyncio event loop
|
|
552
|
+
quads = asyncio.run(
|
|
553
|
+
self._run_flat_pool(pending, effective_concurrency, effective_parallel)
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
if not quads:
|
|
557
|
+
self.output.error("No results produced.")
|
|
558
|
+
return 1
|
|
559
|
+
|
|
560
|
+
# 7. Group by eval_set_id → one EvalReport per set
|
|
561
|
+
groups: dict[str, tuple[str, list[EvalCaseResult]]] = defaultdict(
|
|
562
|
+
lambda: ("", [])
|
|
563
|
+
)
|
|
564
|
+
for file_name, eval_set_id, eval_set_name, result in quads:
|
|
565
|
+
name, results_list = groups[eval_set_id]
|
|
566
|
+
groups[eval_set_id] = (eval_set_name or name, results_list + [result])
|
|
567
|
+
|
|
568
|
+
reports: list[EvalReport] = []
|
|
569
|
+
for eval_set_id, (eval_set_name, results) in groups.items():
|
|
570
|
+
reports.append(
|
|
571
|
+
ER.create(
|
|
572
|
+
eval_set_id=eval_set_id,
|
|
573
|
+
eval_set_name=eval_set_name,
|
|
574
|
+
results=results,
|
|
575
|
+
config_used=global_config.model_dump(),
|
|
576
|
+
)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# 8. Merge into a single report
|
|
580
|
+
merged = self._merge_reports(reports)
|
|
581
|
+
|
|
582
|
+
# 9. Determine exit code
|
|
583
|
+
if threshold is not None and merged.summary.pass_rate < threshold:
|
|
584
|
+
self.output.error(
|
|
585
|
+
f"Pass rate {merged.summary.pass_rate:.1%} is below threshold {threshold:.1%}"
|
|
586
|
+
)
|
|
587
|
+
return_code = 1
|
|
588
|
+
else:
|
|
589
|
+
return_code = 0 if merged.summary.pass_rate == 1.0 else 1
|
|
590
|
+
|
|
591
|
+
# 10. Generate reports
|
|
592
|
+
if not no_report:
|
|
593
|
+
manager = ReporterManager(
|
|
594
|
+
ReporterConfig(
|
|
595
|
+
output_dir=output_dir,
|
|
596
|
+
html=True,
|
|
597
|
+
json_report=True,
|
|
598
|
+
console=False,
|
|
599
|
+
timestamp_files=True,
|
|
600
|
+
)
|
|
601
|
+
)
|
|
602
|
+
report_result = manager.run_all(merged)
|
|
603
|
+
|
|
604
|
+
if report_result.html_path:
|
|
605
|
+
self.output.success(f"HTML report: {report_result.html_path}")
|
|
606
|
+
if report_result.json_path:
|
|
607
|
+
self.output.info(f"JSON report: {report_result.json_path}", emoji=False)
|
|
608
|
+
if report_result.has_errors:
|
|
609
|
+
for name, err in report_result.errors:
|
|
610
|
+
self.output.warning(f"Reporter error [{name}]: {err}")
|
|
611
|
+
|
|
612
|
+
if open_report and report_result.html_path:
|
|
613
|
+
webbrowser.open(Path(report_result.html_path).as_uri())
|
|
614
|
+
|
|
615
|
+
summary = merged.summary
|
|
616
|
+
self.output.info(
|
|
617
|
+
f"Results: {summary.passed_cases}/{summary.total_cases} passed "
|
|
618
|
+
f"({summary.pass_rate:.1%})",
|
|
619
|
+
emoji=False,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
return return_code
|
|
@@ -430,18 +430,32 @@ def eval_cmd(
|
|
|
430
430
|
"--open",
|
|
431
431
|
help="Open the HTML report in the default browser after the run",
|
|
432
432
|
),
|
|
433
|
+
parallel: bool = typer.Option(
|
|
434
|
+
False,
|
|
435
|
+
"--parallel",
|
|
436
|
+
"-p",
|
|
437
|
+
help="Collect all cases from all files into a flat pool and run them concurrently",
|
|
438
|
+
),
|
|
439
|
+
max_concurrency: int = typer.Option(
|
|
440
|
+
4,
|
|
441
|
+
"--max-concurrency",
|
|
442
|
+
"-c",
|
|
443
|
+
help="Max cases running concurrently when --parallel is set (global semaphore)",
|
|
444
|
+
),
|
|
433
445
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output"),
|
|
434
446
|
quiet: bool = typer.Option(False, "--quiet", "-q", help="Suppress all output except errors"),
|
|
435
447
|
) -> None:
|
|
436
448
|
"""Run agent evaluations.
|
|
437
449
|
|
|
438
450
|
Discovers *_eval.py / eval_*.py files in the target directory (default: evals/).
|
|
439
|
-
|
|
451
|
+
Collects all cases from all files into a flat pool, then runs them under a single
|
|
452
|
+
event loop throttled by --max-concurrency. Always generates HTML + JSON reports
|
|
453
|
+
in eval_reports/ unless --no-report is set.
|
|
440
454
|
|
|
441
455
|
Each eval file must expose one of:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
456
|
+
get_eval_set() + get_eval_config() # CLI loads agent from agentflow.json
|
|
457
|
+
EVAL_CONFIG + get_eval_set() # same, config as a constant
|
|
458
|
+
any function returning EvalSet # auto-discovered, pytest-style
|
|
445
459
|
"""
|
|
446
460
|
setup_cli_logging(verbose=verbose, quiet=quiet)
|
|
447
461
|
|
|
@@ -453,6 +467,8 @@ def eval_cmd(
|
|
|
453
467
|
no_report=no_report,
|
|
454
468
|
threshold=threshold,
|
|
455
469
|
open_report=open_report,
|
|
470
|
+
parallel=parallel,
|
|
471
|
+
max_concurrency=max_concurrency,
|
|
456
472
|
verbose=verbose,
|
|
457
473
|
quiet=quiet,
|
|
458
474
|
)
|