-
Notifications
You must be signed in to change notification settings - Fork 6
evals: replace framework with custom ADK harness #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| dist/tfctl | ||
| .plans/ | ||
| dist/ | ||
| evals/results/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ ASSETS ?= assets | |
| VERSION_FILE ?= version/VERSION | ||
| SKILL_HASHES = skills/tfctl/known_release_hashes | ||
| SKILL_EMBEDDED = skills/tfctl/SKILL.md | ||
| EVAL_ARGS ?= | ||
| EVAL_OUTPUT ?= evals/results/latest.json | ||
| CHANGELOG_FILE = CHANGELOG.md | ||
|
|
||
| ifeq ($(GOARCH), arm64) | ||
|
|
@@ -119,8 +121,24 @@ logotools: | |
| echo "Install figlet https://www.figlet.org/" && exit 1; \ | ||
| } | ||
|
|
||
| .PHONY: eval/test | ||
| eval/test: | ||
| @$(MAKE) -C evals test | ||
|
|
||
| .PHONY: eval/lint | ||
| eval/lint: | ||
| @$(MAKE) -C evals lint | ||
|
|
||
| .PHONY: check | ||
| check: fmt-check go/lint go/test | ||
| check: fmt-check go/lint go/test eval/lint eval/test | ||
|
|
||
| .PHONY: eval | ||
| eval: go/install | ||
| @PATH="$(abspath $(dir $(BIN_PATH))):$$PATH" go -C evals run . $(EVAL_ARGS) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| .PHONY: eval/save | ||
| eval/save: go/install | ||
| @PATH="$(abspath $(dir $(BIN_PATH))):$$PATH" go -C evals run . --output "$(abspath $(EVAL_OUTPUT))" $(EVAL_ARGS) | ||
|
|
||
| # Help (make usage) | ||
| .PHONY: help | ||
|
|
@@ -151,4 +169,10 @@ help: | |
| @echo " requires VERSION argument" | ||
| @echo " cleanup-release Clean up after a release" | ||
| @echo " requires DEV_VERSION argument" | ||
| @echo "" | ||
| @echo "" | ||
| @echo "Evaluations:" | ||
| @echo " eval Run skill evaluations" | ||
| @echo " eval/save Run and save evaluation results" | ||
| @echo " eval/test Test the evaluator module" | ||
| @echo " eval/lint Lint the evaluator module" | ||
| @echo "" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .PHONY: test | ||
| test: | ||
| @go test ./... | ||
|
|
||
| .PHONY: lint | ||
| lint: | ||
| @golangci-lint run |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # tfctl Skill Evaluations | ||
|
|
||
| The evaluation runner sends each YAML task to a simple, custom, Google ADK agent with | ||
| `skills/tfctl/SKILL.md` as its instructions. The agent has one function tool, | ||
| `tfctl(args)`. | ||
|
|
||
| The runner executes ordinary `tfctl` requests with a temporary configuration | ||
| directory. A request that starts with `tfctl api`, `tfctl get`, or `tfctl create` | ||
| is not executed. The runner records that request, stops the agent, and grades | ||
| the recorded isolated request. This prevents a platform request and lets each | ||
| task run independently. If the agent makes no isolated request, the runner | ||
| grades its visible text output instead. The saved output includes model | ||
| reasoning, but task checks do not grade it. | ||
|
|
||
| The evaluator is an independent Go module. Run repository-level Make targets | ||
| from the repository root; they build the current `tfctl` source before running. | ||
| For direct `go -C evals` commands, install `tfctl` on `PATH` first. | ||
|
|
||
| ## Providers | ||
|
|
||
| ADK Go's only OpenAI-shaped model connector | ||
| (`google.golang.org/adk/v2/model/openaimodel`) speaks the newer Responses | ||
| API, which neither a local OpenAI-compatible server nor AWS Bedrock speaks | ||
| natively. Rather than run a translating proxy (e.g. LiteLLM) in front of | ||
| either one, the runner talks to both directly through two small adapters | ||
| in `evals/internal/model`: | ||
|
|
||
| - `openaichat`: calls a Chat Completions endpoint (the format local model | ||
| servers such as llama.cpp, vLLM, and Ollama actually speak). | ||
| - `bedrockconverse`: calls the AWS Bedrock Converse API using the standard | ||
| AWS SDK credential chain. | ||
|
|
||
| Select a provider with `--provider` or `EVAL_PROVIDER`: | ||
|
|
||
| ```sh | ||
| # A local OpenAI-compatible server | ||
| EVAL_PROVIDER=openai EVAL_MODEL=qwen3.8-Q8 make eval | ||
|
|
||
| # AWS Bedrock | ||
| EVAL_PROVIDER=bedrock EVAL_MODEL=us.openai.gpt-5.6-luna make eval/save | ||
| ``` | ||
|
|
||
| For the `openai` provider, `--base-url`/`EVAL_BASE_URL` sets the Chat | ||
| Completions base URL (default `http://127.0.0.1:8000/v1`) and | ||
| `--api-key`/`EVAL_API_KEY` sets an optional API key. `--model`/`EVAL_MODEL` | ||
| is the model name the server expects. | ||
|
|
||
| For the `bedrock` provider, `--model`/`EVAL_MODEL` is a Bedrock model ID or | ||
| cross-region inference profile ID (e.g. `us.anthropic.claude-...`). | ||
| Credentials and region come from the standard AWS SDK default chain: | ||
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`, | ||
| `AWS_REGION`, or an `AWS_PROFILE`. | ||
|
|
||
| `EVAL_PROVIDER` and `EVAL_MODEL` are required when `--provider`/`--model` | ||
| are not set. Do not set `GOOGLE_API_KEY`; the runner does not call Gemini. | ||
|
|
||
| ## Run | ||
|
|
||
| ```sh | ||
| EVAL_PROVIDER=openai EVAL_MODEL=qwen3.8-Q8 make eval | ||
| EVAL_PROVIDER=bedrock EVAL_MODEL=us.openai.gpt-5.6-luna make eval/save | ||
| ``` | ||
|
|
||
| The runner accepts `--provider`, `--model`, `--base-url`, `--api-key`, | ||
| `--output`, `--tags`, `--json`, and `--task`. Flags override the corresponding | ||
| `EVAL_*` environment variables. `--tags` accepts comma-separated tags; `--task` | ||
| accepts a filename glob or substring. | ||
|
|
||
| ## Tasks | ||
|
|
||
| Tasks live in `evals/tasks/` and use this strict schema: | ||
|
|
||
| ```yaml | ||
| task: | | ||
| List all workspaces and show their names. | ||
| tags: [api-pattern, pagination] | ||
| accept: | ||
| - '--all' | ||
| - '(?:/plans/)|(?:/applies/)' | ||
| reject: ['\|\s*jq'] | ||
| turns: 10 | ||
| ``` | ||
|
|
||
| Every `accept` expression must match the isolated invocation, or the visible | ||
| assistant output when there is no isolated invocation. Every `reject` | ||
| expression must not match. Expressions use Go's RE2-compatible syntax and are | ||
| automatically case-insensitive. Use alternation such as | ||
| `(?:first)|(?:second)` when any accepted form is sufficient. Prefer the | ||
| smallest patterns that express required flags, paths, or request data. Invalid | ||
| regular expressions are task validation errors. At least one check is required. | ||
| The stable task ID comes from the filename with its numeric prefix and `.yaml` | ||
|
|
||
| Generated files under `evals/results/` are ignored. Override the output path | ||
| and runner arguments when needed: | ||
|
|
||
| ```sh | ||
| make eval/save EVAL_OUTPUT=evals/results/pagination.json EVAL_ARGS='--tags pagination' | ||
| ``` | ||
|
|
||
| Run evaluator checks independently with `make eval/test` and `make eval/lint`. | ||
|
|
||
| ## CI | ||
|
|
||
| The `Skill Evals` workflow runs against the `bedrock` provider, passes | ||
| runner configuration through `EVAL_*`, and uploads the current JSON result | ||
| even on a failure. AWS credentials (as provisioned by doormat) and the | ||
| Bedrock model ID are supplied as `workflow_dispatch` inputs. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| module github.com/hashicorp/tfctl-cli/evals | ||
|
|
||
| go 1.26.5 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CI installs Go from the root go.mod (1.26.4), while this module requires 1.26.5. I think we either align the Go versions or configure CI to install the eval module’s required version |
||
|
|
||
| require ( | ||
| github.com/aws/aws-sdk-go-v2 v1.44.0 | ||
| github.com/aws/aws-sdk-go-v2/config v1.32.40 | ||
| github.com/aws/aws-sdk-go-v2/credentials v1.19.39 | ||
| github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.58.0 | ||
| github.com/openai/openai-go/v3 v3.49.0 | ||
| google.golang.org/adk/v2 v2.2.0 | ||
| google.golang.org/genai v1.66.0 | ||
| gopkg.in/yaml.v3 v3.0.1 | ||
| ) | ||
|
|
||
| require ( | ||
| cloud.google.com/go v0.123.0 // indirect | ||
| cloud.google.com/go/auth v0.22.0 // indirect | ||
| cloud.google.com/go/compute/metadata v0.9.0 // indirect | ||
| github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect | ||
| github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 // indirect | ||
| github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 // indirect | ||
| github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 // indirect | ||
| github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 // indirect | ||
| github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 // indirect | ||
| github.com/aws/smithy-go v1.28.1 // indirect | ||
| github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||
| github.com/felixge/httpsnoop v1.0.4 // indirect | ||
| github.com/go-logr/logr v1.4.3 // indirect | ||
| github.com/go-logr/stdr v1.2.2 // indirect | ||
| github.com/google/go-cmp v0.7.0 // indirect | ||
| github.com/google/jsonschema-go v0.4.3 // indirect | ||
| github.com/google/s2a-go v0.1.9 // indirect | ||
| github.com/google/safehtml v0.1.0 // indirect | ||
| github.com/google/uuid v1.6.0 // indirect | ||
| github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect | ||
| github.com/googleapis/gax-go/v2 v2.23.0 // indirect | ||
| github.com/gorilla/websocket v1.5.3 // indirect | ||
| github.com/tidwall/gjson v1.19.0 // indirect | ||
| github.com/tidwall/match v1.2.0 // indirect | ||
| github.com/tidwall/pretty v1.2.1 // indirect | ||
| github.com/tidwall/sjson v1.2.5 // indirect | ||
| go.opentelemetry.io/auto/sdk v1.2.1 // indirect | ||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect | ||
| go.opentelemetry.io/otel v1.44.0 // indirect | ||
| go.opentelemetry.io/otel/log v0.20.0 // indirect | ||
| go.opentelemetry.io/otel/metric v1.44.0 // indirect | ||
| go.opentelemetry.io/otel/trace v1.44.0 // indirect | ||
| golang.org/x/crypto v0.54.0 // indirect | ||
| golang.org/x/net v0.57.0 // indirect | ||
| golang.org/x/sys v0.47.0 // indirect | ||
| golang.org/x/text v0.40.0 // indirect | ||
| google.golang.org/api v0.291.0 // indirect | ||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20260724162435-b2f20204f0df // indirect | ||
| google.golang.org/grpc v1.83.0 // indirect | ||
| google.golang.org/protobuf v1.36.11 // indirect | ||
| rsc.io/omap v1.2.0 // indirect | ||
| rsc.io/ordered v1.1.1 // indirect | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These values could be exposed from metadata or logs since they're not coming from GitHub secrets. Can we use GitHub OIDC or repo / env secrets?