graph-agents-cli 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graph_agents_cli/__init__.py +26 -0
- graph_agents_cli/_api_policy.py +2145 -0
- graph_agents_cli/_approvals.py +400 -0
- graph_agents_cli/_build.py +186 -0
- graph_agents_cli/_build_info.json +7 -0
- graph_agents_cli/_chat_client.py +462 -0
- graph_agents_cli/_click.py +157 -0
- graph_agents_cli/_defaults.py +139 -0
- graph_agents_cli/_experiments.py +64 -0
- graph_agents_cli/_http.py +192 -0
- graph_agents_cli/_output.py +83 -0
- graph_agents_cli/_project.py +462 -0
- graph_agents_cli/_remote.py +220 -0
- graph_agents_cli/_response_schema.py +264 -0
- graph_agents_cli/_runner.py +319 -0
- graph_agents_cli/_skills_check.py +274 -0
- graph_agents_cli/_tools.py +189 -0
- graph_agents_cli/_trust.py +66 -0
- graph_agents_cli/api/__init__.py +15 -0
- graph_agents_cli/api/_changes.py +506 -0
- graph_agents_cli/api/_files.py +658 -0
- graph_agents_cli/api/cmd_api.py +2480 -0
- graph_agents_cli/deploy/__init__.py +15 -0
- graph_agents_cli/deploy/_config.py +171 -0
- graph_agents_cli/deploy/_image.py +128 -0
- graph_agents_cli/deploy/_kube.py +286 -0
- graph_agents_cli/deploy/_modes.py +234 -0
- graph_agents_cli/deploy/_preflight.py +370 -0
- graph_agents_cli/deploy/_values.py +168 -0
- graph_agents_cli/deploy/cmd_deploy.py +1866 -0
- graph_agents_cli/deploy/gitops.py +562 -0
- graph_agents_cli/deploy/local_load.py +273 -0
- graph_agents_cli/dev/__init__.py +13 -0
- graph_agents_cli/dev/cmd_build.py +131 -0
- graph_agents_cli/dev/cmd_install.py +78 -0
- graph_agents_cli/dev/cmd_lint.py +119 -0
- graph_agents_cli/dev/cmd_playground.py +297 -0
- graph_agents_cli/dev/policy_check.py +1287 -0
- graph_agents_cli/eval/__init__.py +22 -0
- graph_agents_cli/eval/_client.py +670 -0
- graph_agents_cli/eval/_common.py +177 -0
- graph_agents_cli/eval/_judge.py +168 -0
- graph_agents_cli/eval/_judge_runner.py +238 -0
- graph_agents_cli/eval/_paths.py +212 -0
- graph_agents_cli/eval/checks.py +581 -0
- graph_agents_cli/eval/cmd_analyze.py +278 -0
- graph_agents_cli/eval/cmd_compare.py +284 -0
- graph_agents_cli/eval/cmd_eval_group.py +80 -0
- graph_agents_cli/eval/cmd_generate.py +558 -0
- graph_agents_cli/eval/cmd_grade.py +466 -0
- graph_agents_cli/eval/cmd_metric.py +156 -0
- graph_agents_cli/eval/cmd_run.py +370 -0
- graph_agents_cli/eval/cmd_submit.py +400 -0
- graph_agents_cli/eval/config.py +435 -0
- graph_agents_cli/eval/dataset.py +350 -0
- graph_agents_cli/eval/gate.py +420 -0
- graph_agents_cli/eval/transcript.py +192 -0
- graph_agents_cli/extension/__init__.py +13 -0
- graph_agents_cli/extension/_compat.py +86 -0
- graph_agents_cli/extension/_loader.py +293 -0
- graph_agents_cli/extension/_manifest.py +135 -0
- graph_agents_cli/extension/_overrides.py +195 -0
- graph_agents_cli/extension/_paths.py +91 -0
- graph_agents_cli/extension/_refs.py +193 -0
- graph_agents_cli/extension/_resolver.py +453 -0
- graph_agents_cli/extension/_schema.py +106 -0
- graph_agents_cli/extension/_spec.py +253 -0
- graph_agents_cli/extension/_sync.py +102 -0
- graph_agents_cli/extension/_trust.py +58 -0
- graph_agents_cli/extension/cmd_extension_add.py +259 -0
- graph_agents_cli/extension/cmd_extension_group.py +57 -0
- graph_agents_cli/extension/cmd_extension_list.py +56 -0
- graph_agents_cli/extension/cmd_extension_remove.py +61 -0
- graph_agents_cli/extension/cmd_extension_update.py +195 -0
- graph_agents_cli/info/__init__.py +13 -0
- graph_agents_cli/info/cmd_info.py +222 -0
- graph_agents_cli/infra/__init__.py +15 -0
- graph_agents_cli/infra/checks.py +1169 -0
- graph_agents_cli/infra/cmd_infra.py +103 -0
- graph_agents_cli/main.py +591 -0
- graph_agents_cli/peer/__init__.py +15 -0
- graph_agents_cli/peer/_generate.py +254 -0
- graph_agents_cli/peer/cmd_peer.py +1151 -0
- graph_agents_cli/run/__init__.py +13 -0
- graph_agents_cli/run/_local_server.py +1157 -0
- graph_agents_cli/run/_signals.py +141 -0
- graph_agents_cli/run/cmd_approvals.py +530 -0
- graph_agents_cli/run/cmd_run.py +1421 -0
- graph_agents_cli/scaffold/__init__.py +19 -0
- graph_agents_cli/scaffold/agents/README.md +24 -0
- graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
- graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
- graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
- graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
- graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
- graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
- graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
- graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
- graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
- graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
- graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
- graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
- graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
- graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
- graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
- graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
- graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
- graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
- graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
- graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
- graph_agents_cli/scaffold/commands/__init__.py +13 -0
- graph_agents_cli/scaffold/commands/create.py +1424 -0
- graph_agents_cli/scaffold/commands/enhance.py +1652 -0
- graph_agents_cli/scaffold/commands/upgrade.py +570 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
- graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
- graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
- graph_agents_cli/scaffold/utils/__init__.py +13 -0
- graph_agents_cli/scaffold/utils/backup.py +212 -0
- graph_agents_cli/scaffold/utils/build_record.py +257 -0
- graph_agents_cli/scaffold/utils/cli_options.py +184 -0
- graph_agents_cli/scaffold/utils/fs.py +83 -0
- graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
- graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
- graph_agents_cli/scaffold/utils/keyedit.py +768 -0
- graph_agents_cli/scaffold/utils/keymerge.py +537 -0
- graph_agents_cli/scaffold/utils/language.py +138 -0
- graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
- graph_agents_cli/scaffold/utils/logging.py +77 -0
- graph_agents_cli/scaffold/utils/manifest.py +292 -0
- graph_agents_cli/scaffold/utils/merge.py +970 -0
- graph_agents_cli/scaffold/utils/merge3.py +216 -0
- graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
- graph_agents_cli/scaffold/utils/remote_template.py +376 -0
- graph_agents_cli/scaffold/utils/template.py +1352 -0
- graph_agents_cli/scaffold/utils/upgrade.py +894 -0
- graph_agents_cli/scaffold/utils/version.py +438 -0
- graph_agents_cli/secrets/__init__.py +15 -0
- graph_agents_cli/secrets/_apply.py +954 -0
- graph_agents_cli/secrets/_required.py +188 -0
- graph_agents_cli/secrets/cmd_secrets.py +211 -0
- graph_agents_cli/setup/__init__.py +13 -0
- graph_agents_cli/setup/_antigravity.py +221 -0
- graph_agents_cli/setup/cmd_auth.py +1030 -0
- graph_agents_cli/setup/cmd_dev_token.py +513 -0
- graph_agents_cli/setup/cmd_setup.py +428 -0
- graph_agents_cli/setup/cmd_update.py +140 -0
- graph_agents_cli/skills/__init__.py +13 -0
- graph_agents_cli/skills/_bundle.py +65 -0
- graph_agents_cli/skills/data/README.md +19 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
- graph_agents_cli/system/__init__.py +15 -0
- graph_agents_cli/system/_apply.py +519 -0
- graph_agents_cli/system/_checks.py +1023 -0
- graph_agents_cli/system/_deploy.py +215 -0
- graph_agents_cli/system/_model.py +363 -0
- graph_agents_cli/system/_system.py +664 -0
- graph_agents_cli/system/_views.py +208 -0
- graph_agents_cli/system/cmd_system.py +423 -0
- graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
- graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
- graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
- graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
- graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
- graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""graph-agents-cli eval generate command: run the agent over a dataset into traces."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import contextlib
|
|
20
|
+
import inspect
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
28
|
+
|
|
29
|
+
import click
|
|
30
|
+
from rich.markup import escape
|
|
31
|
+
|
|
32
|
+
from graph_agents_cli import _http
|
|
33
|
+
from graph_agents_cli._chat_client import redact_credentials
|
|
34
|
+
from graph_agents_cli._output import Console
|
|
35
|
+
from graph_agents_cli._project import find_project_root
|
|
36
|
+
from graph_agents_cli._remote import deprecated_session_token, fold_session_token
|
|
37
|
+
from graph_agents_cli.eval import _paths
|
|
38
|
+
from graph_agents_cli.eval._client import (
|
|
39
|
+
DEFAULT_TIMEOUT,
|
|
40
|
+
STATUS_OK,
|
|
41
|
+
build_case_headers,
|
|
42
|
+
empty_trace,
|
|
43
|
+
run_case,
|
|
44
|
+
)
|
|
45
|
+
from graph_agents_cli.eval._common import (
|
|
46
|
+
EXIT_INCOMPLETE,
|
|
47
|
+
EXIT_OK,
|
|
48
|
+
EvalConfigError,
|
|
49
|
+
project_env,
|
|
50
|
+
project_meta,
|
|
51
|
+
utc_now_iso,
|
|
52
|
+
write_json_file,
|
|
53
|
+
)
|
|
54
|
+
from graph_agents_cli.eval.dataset import Dataset, EvalCase, load_dataset
|
|
55
|
+
from graph_agents_cli.run._signals import shielded, terminate_like_interrupt
|
|
56
|
+
|
|
57
|
+
DEFAULT_CONCURRENCY = 4
|
|
58
|
+
API_KEY_ENV = "GRAPH_AGENTS_CLI_API_KEY"
|
|
59
|
+
# Decisions on role:<name> gates are sent with this credential when set (an
|
|
60
|
+
# approver other than the eval identity); a gate that lists requester is
|
|
61
|
+
# decided as the eval identity, which started the run.
|
|
62
|
+
APPROVER_KEY_ENV = "GRAPH_AGENTS_CLI_APPROVER_API_KEY"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _start_local_server(project_root: Path, meta: dict[str, Any]) -> tuple[str, Callable[[], None]]:
|
|
66
|
+
"""Start (or reuse) the project's local server; returns ``(base_url, teardown)``.
|
|
67
|
+
|
|
68
|
+
Coded against ``run._local_server.ensure_server(project_root, agent_dir, *,
|
|
69
|
+
runtime, ...)`` returning a ``ServerInfo`` with ``base_url`` (or ``port``),
|
|
70
|
+
``started`` and ``pid``. Only the required parameters the function actually
|
|
71
|
+
declares are filled in (``agent_dir``, ``runtime``, ``language``), so a
|
|
72
|
+
signature change on either side does not break the call.
|
|
73
|
+
"""
|
|
74
|
+
from graph_agents_cli.run import _local_server
|
|
75
|
+
|
|
76
|
+
ensure_server = _local_server.ensure_server
|
|
77
|
+
params = inspect.signature(ensure_server).parameters
|
|
78
|
+
args: list[Any] = [project_root]
|
|
79
|
+
kwargs: dict[str, Any] = {}
|
|
80
|
+
for name, param in list(params.items())[1:]:
|
|
81
|
+
if param.default is not inspect.Parameter.empty:
|
|
82
|
+
continue
|
|
83
|
+
value: Any
|
|
84
|
+
if name == "agent_dir":
|
|
85
|
+
value = meta.get("agent_directory") or "app"
|
|
86
|
+
elif name == "runtime":
|
|
87
|
+
value = meta.get("runtime") or "fastapi"
|
|
88
|
+
elif name == "language":
|
|
89
|
+
value = "python"
|
|
90
|
+
else:
|
|
91
|
+
continue
|
|
92
|
+
if param.kind is inspect.Parameter.KEYWORD_ONLY:
|
|
93
|
+
kwargs[name] = value
|
|
94
|
+
else:
|
|
95
|
+
args.append(value)
|
|
96
|
+
info = ensure_server(*args, **kwargs)
|
|
97
|
+
|
|
98
|
+
base_url = getattr(info, "base_url", None)
|
|
99
|
+
if not base_url:
|
|
100
|
+
port = getattr(info, "port", None)
|
|
101
|
+
if port is None:
|
|
102
|
+
raise EvalConfigError("local server start returned no base_url or port")
|
|
103
|
+
base_url = f"http://127.0.0.1:{port}"
|
|
104
|
+
started = bool(getattr(info, "started", True))
|
|
105
|
+
pid = getattr(info, "pid", None)
|
|
106
|
+
# A long session must not look idle to a concurrent `run` (which would
|
|
107
|
+
# replace the server under it): stamp last_activity at a coarse cadence.
|
|
108
|
+
heartbeat = getattr(_local_server, "start_activity_heartbeat", None)
|
|
109
|
+
stop_heartbeat = heartbeat(project_root) if callable(heartbeat) else (lambda: None)
|
|
110
|
+
|
|
111
|
+
def teardown() -> None:
|
|
112
|
+
# A SIGTERM or Ctrl-C now is held until the server is stopped and its
|
|
113
|
+
# record removed, then handled (a stale record would name a dead PID).
|
|
114
|
+
with shielded():
|
|
115
|
+
stop_heartbeat()
|
|
116
|
+
if not started:
|
|
117
|
+
return
|
|
118
|
+
stop = _local_server.stop_server
|
|
119
|
+
try:
|
|
120
|
+
if pid is not None and "pid" in inspect.signature(stop).parameters:
|
|
121
|
+
stop(project_root, pid=pid)
|
|
122
|
+
else:
|
|
123
|
+
stop(project_root)
|
|
124
|
+
except _local_server.ServerStopError as exc:
|
|
125
|
+
# A warning: the traces (or the error that ended the run) stand.
|
|
126
|
+
_local_server.warn_not_stopped(exc)
|
|
127
|
+
|
|
128
|
+
return str(base_url), teardown
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _dispatch(
|
|
132
|
+
cases: list[EvalCase],
|
|
133
|
+
*,
|
|
134
|
+
base_url: str,
|
|
135
|
+
headers: dict[str, str],
|
|
136
|
+
metadata: dict[str, Any],
|
|
137
|
+
concurrency: int,
|
|
138
|
+
timeout: float,
|
|
139
|
+
console: Console,
|
|
140
|
+
decision_headers: dict[str, str] | None = None,
|
|
141
|
+
) -> list[dict[str, Any]]:
|
|
142
|
+
"""Run every case in parallel; input order is preserved in the result."""
|
|
143
|
+
traces: list[dict[str, Any] | None] = [None] * len(cases)
|
|
144
|
+
|
|
145
|
+
def _one(index: int, case: EvalCase) -> tuple[int, dict[str, Any]]:
|
|
146
|
+
try:
|
|
147
|
+
return index, run_case(
|
|
148
|
+
base_url,
|
|
149
|
+
case,
|
|
150
|
+
headers=headers,
|
|
151
|
+
metadata=metadata,
|
|
152
|
+
timeout=timeout,
|
|
153
|
+
decision_headers=decision_headers,
|
|
154
|
+
)
|
|
155
|
+
except Exception as exc: # a worker crash still yields a trace
|
|
156
|
+
trace = empty_trace(case.id)
|
|
157
|
+
trace["status"] = "error"
|
|
158
|
+
trace["error"] = redact_credentials(f"worker crashed: {type(exc).__name__}: {exc}")
|
|
159
|
+
return index, trace
|
|
160
|
+
|
|
161
|
+
pool = ThreadPoolExecutor(max_workers=max(1, concurrency))
|
|
162
|
+
try:
|
|
163
|
+
futures = [pool.submit(_one, i, case) for i, case in enumerate(cases)]
|
|
164
|
+
for future in as_completed(futures):
|
|
165
|
+
index, trace = future.result()
|
|
166
|
+
traces[index] = trace
|
|
167
|
+
status = trace["status"]
|
|
168
|
+
style = "green" if status == STATUS_OK else "red"
|
|
169
|
+
detail = f"{trace['latency_ms']} ms" if status == STATUS_OK else str(trace.get("error"))
|
|
170
|
+
console.print(f"[generate] {trace['case_id']}: [{style}]{status}[/{style}] ({detail})")
|
|
171
|
+
except BaseException:
|
|
172
|
+
# Ctrl-C / SIGTERM: queued cases never start, and the caller's teardown
|
|
173
|
+
# stops the server now instead of after every in-flight call timed out
|
|
174
|
+
# (those calls then fail fast against the stopped server).
|
|
175
|
+
pool.shutdown(wait=False, cancel_futures=True)
|
|
176
|
+
raise
|
|
177
|
+
pool.shutdown(wait=True)
|
|
178
|
+
return [t if t is not None else empty_trace(c.id) for t, c in zip(traces, cases, strict=True)]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_WRITE_METHODS = ("POST", "PUT", "PATCH", "DELETE")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def display_url(url: str) -> str:
|
|
185
|
+
"""``url`` as printed and stored, with any ``user:password@`` replaced by ``***@``."""
|
|
186
|
+
parts = urlsplit(url)
|
|
187
|
+
if "@" not in parts.netloc:
|
|
188
|
+
return url
|
|
189
|
+
return urlunsplit(parts._replace(netloc="***@" + parts.netloc.rsplit("@", 1)[1]))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _policy_write_access(project_root: Path) -> list[str]:
|
|
193
|
+
"""``"api (METHOD, ...)"`` for each API whose policy allows a write method.
|
|
194
|
+
|
|
195
|
+
Read from the project's own ``api-policy.yaml``; the deployed image carries
|
|
196
|
+
the policy it was built with, so this is a hint, not a guarantee. Any
|
|
197
|
+
problem reading it yields no hint (``lint`` reports policy problems).
|
|
198
|
+
"""
|
|
199
|
+
from graph_agents_cli._api_policy import POLICY_FILENAME, load_policy_document
|
|
200
|
+
|
|
201
|
+
path = project_root / POLICY_FILENAME
|
|
202
|
+
if not path.is_file():
|
|
203
|
+
return []
|
|
204
|
+
try:
|
|
205
|
+
document = load_policy_document(path)
|
|
206
|
+
except Exception:
|
|
207
|
+
return []
|
|
208
|
+
found: list[str] = []
|
|
209
|
+
for name, api in (document.get("apis") or {}).items():
|
|
210
|
+
if not isinstance(api, dict):
|
|
211
|
+
continue
|
|
212
|
+
allowed = _expand([str(m).upper() for m in api.get("allowed_methods") or []])
|
|
213
|
+
operations = api.get("allowed_operations")
|
|
214
|
+
if isinstance(operations, list):
|
|
215
|
+
# An allow-list narrows access to its entries (an entry without
|
|
216
|
+
# `methods` takes the API's allowed_methods).
|
|
217
|
+
methods: set[str] = set()
|
|
218
|
+
for entry in operations:
|
|
219
|
+
pinned = entry.get("methods") if isinstance(entry, dict) else None
|
|
220
|
+
if pinned:
|
|
221
|
+
methods |= _expand([str(m).upper() for m in pinned]) & allowed
|
|
222
|
+
else:
|
|
223
|
+
methods |= allowed
|
|
224
|
+
allowed = methods
|
|
225
|
+
writes = [m for m in _WRITE_METHODS if m in allowed]
|
|
226
|
+
if writes:
|
|
227
|
+
found.append(f"{name} ({', '.join(writes)})")
|
|
228
|
+
return found
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _expand(methods: list[str]) -> set[str]:
|
|
232
|
+
return set(_WRITE_METHODS) | set(methods) if "*" in methods else set(methods)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def warn_live_target(
|
|
236
|
+
console: Console, base_url: str, project_root: Path, cases: int | None
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Say, before any case runs, that `--url` runs the agent's tools for real there."""
|
|
239
|
+
writes = _policy_write_access(project_root)
|
|
240
|
+
what = f"{cases} case(s) will run" if cases else "The cases will run"
|
|
241
|
+
console.print(
|
|
242
|
+
f"[bold yellow]Warning:[/bold yellow] [yellow]{what} against the agent at "
|
|
243
|
+
f"{escape(display_url(base_url))} (--url). Every tool call the agent makes runs for "
|
|
244
|
+
"real in that environment, as the identity these requests authenticate as: tools "
|
|
245
|
+
"that create, change or delete data do so there.[/yellow]"
|
|
246
|
+
)
|
|
247
|
+
if writes:
|
|
248
|
+
console.print(
|
|
249
|
+
"[yellow]This project's api-policy.yaml allows write methods: "
|
|
250
|
+
f"{escape('; '.join(writes))}.[/yellow]"
|
|
251
|
+
)
|
|
252
|
+
console.print(
|
|
253
|
+
"[yellow]Point eval at an environment whose data you can reset, with a dedicated test "
|
|
254
|
+
"identity; never at production data.[/yellow]"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Headers that carry the eval identity's credential: an approver's decisions go without them.
|
|
259
|
+
_CREDENTIAL_HEADERS = frozenset({"authorization", "cookie", "x-api-key"})
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def decision_headers_for(headers: dict[str, str], env: dict[str, str]) -> dict[str, str] | None:
|
|
263
|
+
"""The headers decisions on ``role:`` gates go with: the approver's credential when set.
|
|
264
|
+
|
|
265
|
+
Only the approver's bearer credential identifies the decider: the eval
|
|
266
|
+
identity's own credentials (its bearer, cookies, API key header) are left
|
|
267
|
+
out, so a policy that reads them cannot take the decision as the eval
|
|
268
|
+
identity. A gate that lists ``requester`` is decided as the eval identity.
|
|
269
|
+
"""
|
|
270
|
+
key = env.get(APPROVER_KEY_ENV, "")
|
|
271
|
+
if not key:
|
|
272
|
+
return None
|
|
273
|
+
decided = {k: v for k, v in headers.items() if k.lower() not in _CREDENTIAL_HEADERS}
|
|
274
|
+
decided["Authorization"] = f"Bearer {key}"
|
|
275
|
+
return decided
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def approving_cases(cases: list[EvalCase]) -> list[str]:
|
|
279
|
+
"""Ids of the cases whose approvals instructions approve a gated call."""
|
|
280
|
+
return [c.id for c in cases if any(i["decision"] == "approve" for i in c.approvals)]
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _print_incomplete_summary(traces: list[dict[str, Any]], *, remote: bool = False) -> None:
|
|
284
|
+
bad = [t for t in traces if t["status"] != STATUS_OK]
|
|
285
|
+
click.echo("", err=True)
|
|
286
|
+
click.echo(
|
|
287
|
+
f"Generate summary: {len(traces) - len(bad)}/{len(traces)} cases produced a response, "
|
|
288
|
+
f"{len(bad)} did not.",
|
|
289
|
+
err=True,
|
|
290
|
+
)
|
|
291
|
+
for trace in bad:
|
|
292
|
+
click.echo(f" - {trace['case_id']}: {trace['status']}: {trace.get('error')}", err=True)
|
|
293
|
+
if any("HTTP 401 " in str(t.get("error") or "") for t in bad):
|
|
294
|
+
# The same policy-aware hint `run` prints (which credential, and how to send it).
|
|
295
|
+
from graph_agents_cli.run.cmd_run import _auth_hint, _project_auth_policy
|
|
296
|
+
|
|
297
|
+
hint = _auth_hint(_project_auth_policy(remote=remote), remote=remote).strip()
|
|
298
|
+
click.echo(f" {hint}", err=True)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def generate_traces(
|
|
302
|
+
*,
|
|
303
|
+
dataset: str | None = None,
|
|
304
|
+
output: str | None = None,
|
|
305
|
+
url: str | None = None,
|
|
306
|
+
concurrency: int = DEFAULT_CONCURRENCY,
|
|
307
|
+
header: tuple[str, ...] = (),
|
|
308
|
+
cookie: tuple[str, ...] = (),
|
|
309
|
+
session_token: str | None = None,
|
|
310
|
+
app_name: str | None = None,
|
|
311
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
312
|
+
console: Console | None = None,
|
|
313
|
+
) -> int:
|
|
314
|
+
"""Run the dataset and write the trace file; returns 0 (complete) or 2 (incomplete)."""
|
|
315
|
+
console = console or Console()
|
|
316
|
+
project_root = find_project_root()
|
|
317
|
+
if project_root is None:
|
|
318
|
+
raise EvalConfigError(
|
|
319
|
+
"not inside a graph-agents-cli project (no graph-agents-cli-manifest.yaml found)"
|
|
320
|
+
)
|
|
321
|
+
files = _paths.resolve_input_datasets(project_root, dataset)
|
|
322
|
+
if not files:
|
|
323
|
+
raise EvalConfigError(
|
|
324
|
+
f"no dataset found: pass --dataset PATH or add {_paths.DEFAULT_INPUT_DATASET}"
|
|
325
|
+
)
|
|
326
|
+
ds: Dataset = load_dataset(files)
|
|
327
|
+
output_path = _paths.resolve_output_path(
|
|
328
|
+
project_root,
|
|
329
|
+
output,
|
|
330
|
+
default_dir=_paths.default_traces_dir(project_root),
|
|
331
|
+
prefix=_paths.TRACES_FILE_PREFIX,
|
|
332
|
+
)
|
|
333
|
+
meta = project_meta(project_root)
|
|
334
|
+
app_name = app_name or meta["agent_directory"]
|
|
335
|
+
|
|
336
|
+
env: dict[str, str] = dict(os.environ)
|
|
337
|
+
if not url and not env.get(API_KEY_ENV):
|
|
338
|
+
# The local server enforces the shared-bearer policy with the project's API_KEY.
|
|
339
|
+
local_key = project_env(project_root).get("API_KEY")
|
|
340
|
+
if local_key:
|
|
341
|
+
env[API_KEY_ENV] = local_key
|
|
342
|
+
headers = build_case_headers(header, cookie, session_token, env=env)
|
|
343
|
+
decision_headers = decision_headers_for(headers, env)
|
|
344
|
+
|
|
345
|
+
console.print(
|
|
346
|
+
f"Running [cyan]{len(ds.cases)}[/cyan] case(s) from "
|
|
347
|
+
f"[cyan]{', '.join(str(f.relative_to(project_root)) if f.is_relative_to(project_root) else str(f) for f in files)}[/cyan]"
|
|
348
|
+
)
|
|
349
|
+
metadata = {"dataset_hash": ds.hash, "app_name": app_name}
|
|
350
|
+
# With a local server, SIGTERM/SIGHUP unwind like Ctrl-C: the server this run
|
|
351
|
+
# started is stopped (and its pid file removed) even when a CI timeout kills it.
|
|
352
|
+
with terminate_like_interrupt() if not url else contextlib.nullcontext():
|
|
353
|
+
teardown: Callable[[], None] = lambda: None # noqa: E731
|
|
354
|
+
if url:
|
|
355
|
+
base_url = url.rstrip("/")
|
|
356
|
+
# A proxy setting that would fail every case fails the command once, up front
|
|
357
|
+
# (each case would otherwise record it as its own error).
|
|
358
|
+
_http.check_env_proxies(base_url)
|
|
359
|
+
console.print(f"Target: [cyan]{escape(display_url(base_url))}[/cyan]")
|
|
360
|
+
warn_live_target(console, base_url, project_root, len(ds.cases))
|
|
361
|
+
approving = approving_cases(ds.cases)
|
|
362
|
+
if approving:
|
|
363
|
+
console.print(
|
|
364
|
+
f"[yellow]{len(approving)} case(s) approve gated calls "
|
|
365
|
+
f"({escape(', '.join(approving[:5]))}{', ...' if len(approving) > 5 else ''})"
|
|
366
|
+
": those calls are sent there as approved.[/yellow]"
|
|
367
|
+
)
|
|
368
|
+
else:
|
|
369
|
+
console.print("Starting the local server...")
|
|
370
|
+
base_url, teardown = _start_local_server(project_root, meta)
|
|
371
|
+
console.print(f"Local server at [cyan]{base_url}[/cyan]")
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
traces = _dispatch(
|
|
375
|
+
ds.cases,
|
|
376
|
+
base_url=base_url,
|
|
377
|
+
headers=headers,
|
|
378
|
+
metadata=metadata,
|
|
379
|
+
concurrency=concurrency,
|
|
380
|
+
timeout=timeout,
|
|
381
|
+
console=console,
|
|
382
|
+
decision_headers=decision_headers,
|
|
383
|
+
)
|
|
384
|
+
finally:
|
|
385
|
+
teardown()
|
|
386
|
+
|
|
387
|
+
# The model is known only for the project's own local server, which runs the
|
|
388
|
+
# project's settings. The agent at a --url target does not report its model,
|
|
389
|
+
# and the local settings need not be what runs there: its traces name none.
|
|
390
|
+
model = None if url else meta["model"]
|
|
391
|
+
for trace, case in zip(traces, ds.cases, strict=True):
|
|
392
|
+
trace["agent_version"] = meta["agent_version"]
|
|
393
|
+
trace["model"] = model
|
|
394
|
+
trace["case"] = case.raw
|
|
395
|
+
|
|
396
|
+
doc = {
|
|
397
|
+
"dataset_hash": ds.hash,
|
|
398
|
+
"dataset_paths": [
|
|
399
|
+
str(f.relative_to(project_root)) if f.is_relative_to(project_root) else str(f)
|
|
400
|
+
for f in files
|
|
401
|
+
],
|
|
402
|
+
"generated_at": utc_now_iso(),
|
|
403
|
+
"agent_version": meta["agent_version"],
|
|
404
|
+
"model": model,
|
|
405
|
+
# The project's MODEL_PROVIDER: the agent's only for the local server.
|
|
406
|
+
# `eval grade` warns when that agent ran on the fake model, and for a
|
|
407
|
+
# --url target when these settings name it.
|
|
408
|
+
"model_provider": meta["model_provider"],
|
|
409
|
+
"target": "url" if url else "local",
|
|
410
|
+
"base_url": display_url(base_url),
|
|
411
|
+
"app_name": app_name,
|
|
412
|
+
"traces": traces,
|
|
413
|
+
}
|
|
414
|
+
write_json_file(output_path, doc)
|
|
415
|
+
console.print(f"Traces saved to [green]{output_path}[/green]")
|
|
416
|
+
|
|
417
|
+
if any(t["status"] != STATUS_OK for t in traces):
|
|
418
|
+
_print_incomplete_summary(traces, remote=bool(url))
|
|
419
|
+
return EXIT_INCOMPLETE
|
|
420
|
+
return EXIT_OK
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@click.command("generate")
|
|
424
|
+
@click.option(
|
|
425
|
+
"--dataset",
|
|
426
|
+
default=None,
|
|
427
|
+
help=(
|
|
428
|
+
"Dataset file, or a directory of *.json datasets. Defaults to "
|
|
429
|
+
f"{_paths.DEFAULT_INPUT_DATASET}, else every *.json in {_paths.DATASETS_DIR}/."
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
@click.option(
|
|
433
|
+
"--output",
|
|
434
|
+
"-o",
|
|
435
|
+
default=None,
|
|
436
|
+
help=(
|
|
437
|
+
"Traces file, or a directory to write traces_<ts>.json into. Defaults to "
|
|
438
|
+
f"{_paths.ARTIFACTS_DIR}/{_paths.TRACES_SUBDIR}/traces_<ts>.json."
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
@click.option(
|
|
442
|
+
"--url",
|
|
443
|
+
default=None,
|
|
444
|
+
help=(
|
|
445
|
+
"Base URL of a running agent (its POST /chat). Its tools run for real in that "
|
|
446
|
+
"environment, write tools included. When omitted the project's local server is "
|
|
447
|
+
"started for the run and stopped afterwards."
|
|
448
|
+
),
|
|
449
|
+
)
|
|
450
|
+
@click.option(
|
|
451
|
+
"--concurrency",
|
|
452
|
+
type=click.IntRange(min=1),
|
|
453
|
+
default=DEFAULT_CONCURRENCY,
|
|
454
|
+
show_default=True,
|
|
455
|
+
help="Cases run in parallel, each on its own thread.",
|
|
456
|
+
)
|
|
457
|
+
@click.option(
|
|
458
|
+
"--header",
|
|
459
|
+
"-H",
|
|
460
|
+
"header",
|
|
461
|
+
multiple=True,
|
|
462
|
+
help=(
|
|
463
|
+
"Extra HTTP header 'Key: Value' (repeatable). For a bearer credential use "
|
|
464
|
+
"GRAPH_AGENTS_CLI_API_KEY instead (argv is visible to other local users); an "
|
|
465
|
+
"Authorization header overrides it."
|
|
466
|
+
),
|
|
467
|
+
)
|
|
468
|
+
@click.option(
|
|
469
|
+
"--cookie",
|
|
470
|
+
multiple=True,
|
|
471
|
+
help="Cookie 'name=value' (repeatable), for a custom auth policy that reads cookies.",
|
|
472
|
+
)
|
|
473
|
+
@click.option(
|
|
474
|
+
"--session-token",
|
|
475
|
+
default=None,
|
|
476
|
+
hidden=True,
|
|
477
|
+
callback=deprecated_session_token,
|
|
478
|
+
help="Deprecated alias of --header 'X-Session-Token: ...'.",
|
|
479
|
+
)
|
|
480
|
+
@click.option(
|
|
481
|
+
"--app-name",
|
|
482
|
+
default=None,
|
|
483
|
+
help="Agent name recorded in the traces and sent as request metadata. Defaults to agent_directory.",
|
|
484
|
+
)
|
|
485
|
+
@click.option(
|
|
486
|
+
"--timeout",
|
|
487
|
+
type=click.FloatRange(min=1),
|
|
488
|
+
default=DEFAULT_TIMEOUT,
|
|
489
|
+
show_default=True,
|
|
490
|
+
help="Seconds allowed per chat call.",
|
|
491
|
+
)
|
|
492
|
+
def cmd_generate(
|
|
493
|
+
*,
|
|
494
|
+
dataset: str | None,
|
|
495
|
+
output: str | None,
|
|
496
|
+
url: str | None,
|
|
497
|
+
concurrency: int,
|
|
498
|
+
header: tuple[str, ...],
|
|
499
|
+
cookie: tuple[str, ...],
|
|
500
|
+
session_token: str | None,
|
|
501
|
+
app_name: str | None,
|
|
502
|
+
timeout: float,
|
|
503
|
+
) -> None:
|
|
504
|
+
"""Run the agent over the eval dataset and write traces.
|
|
505
|
+
|
|
506
|
+
Each case's user messages are sent to POST /chat (SSE) and the events are
|
|
507
|
+
folded into one trace per case: response, tool calls, usage, latency,
|
|
508
|
+
thread and run ids, and a status of ok, error or missing. The trace file
|
|
509
|
+
records the dataset hash so `eval grade` can account for every planned case.
|
|
510
|
+
|
|
511
|
+
Without --url the local server is started through `run`'s server manager
|
|
512
|
+
and stopped after the run. Credentials follow the project's auth policy,
|
|
513
|
+
locally and with --url. Put a bearer credential in GRAPH_AGENTS_CLI_API_KEY
|
|
514
|
+
(sent as 'Authorization: Bearer <value>'): unlike --header, it stays out
|
|
515
|
+
of the process list and your shell history.
|
|
516
|
+
|
|
517
|
+
\b
|
|
518
|
+
shared-bearer GRAPH_AGENTS_CLI_API_KEY=<API_KEY> (locally: the API_KEY in .env)
|
|
519
|
+
jwt GRAPH_AGENTS_CLI_API_KEY=<token> (locally: auth dev-token)
|
|
520
|
+
custom --header 'Name: value' or --cookie name=value
|
|
521
|
+
|
|
522
|
+
With --url every tool the agent calls runs for real in that environment
|
|
523
|
+
(a warning names the target first): cases that create, change or delete
|
|
524
|
+
data do so there. Use a dedicated test identity and data you can reset.
|
|
525
|
+
|
|
526
|
+
A call the API policy gates (an approval block) pauses the run; it is
|
|
527
|
+
decided as the case's "approvals" instructions say ({"decision":
|
|
528
|
+
"approve"|"reject", "match": {...}}) and the run continues. A gate no
|
|
529
|
+
instruction matches makes the case an error: generate never approves on
|
|
530
|
+
its own, and rejects such a gate (as it does one whose decision was
|
|
531
|
+
refused) so that no approval is left pending; one it may not reject goes
|
|
532
|
+
with the case's thread, which it deletes as the eval identity (deleting a
|
|
533
|
+
thread deletes its approvals). The trace records how (approvals[].cleanup),
|
|
534
|
+
and the case error names a gate it could neither reject nor delete, which
|
|
535
|
+
stays pending until it expires or an approver decides it (approves or
|
|
536
|
+
rejects). A gate that lists requester is decided as the eval identity (it
|
|
537
|
+
started the run); any other with GRAPH_AGENTS_CLI_APPROVER_API_KEY as a
|
|
538
|
+
bearer credential when it is set (a principal holding the gate's role).
|
|
539
|
+
|
|
540
|
+
\b
|
|
541
|
+
Exit codes:
|
|
542
|
+
0 every case produced a trace with a response
|
|
543
|
+
2 at least one case is error or missing (summary on stderr)
|
|
544
|
+
3 configuration error (no dataset, malformed case, no project)
|
|
545
|
+
"""
|
|
546
|
+
code = generate_traces(
|
|
547
|
+
dataset=dataset,
|
|
548
|
+
output=output,
|
|
549
|
+
url=url,
|
|
550
|
+
concurrency=concurrency,
|
|
551
|
+
header=fold_session_token(header, session_token),
|
|
552
|
+
cookie=cookie,
|
|
553
|
+
session_token=None,
|
|
554
|
+
app_name=app_name,
|
|
555
|
+
timeout=timeout,
|
|
556
|
+
)
|
|
557
|
+
if code:
|
|
558
|
+
sys.exit(code)
|