feat: per-run model selection, usage in diagnosis results and a Claude Messages API provider

캠페인마다 진단 모델을 고를 수 있게 한다. 하네스는 runs.model 열을 추가해 POST /runs의
model을 저장하고, 활성 런의 model을 /diagnose 요청에 실어 보낸다. 에이전트는 요청의
model을 우선 사용하고(없으면 OPENAI_MODEL 기본값), 모델 id가 claude-로 시작하면
internal/anthropic(Claude Messages API 어댑터)로 라우팅한다. 어댑터는 Responses API
항목(message/function_call/function_call_output)을 system·tools(input_schema)·tool_use/
tool_result로 변환하고 previous_response_id 체인을 클라이언트 쪽에서 재현하며, Claude 5.5가
강제 tool_choice를 거부하므로 강제 호출은 auto + 지시문으로 바꾼다. 키는
ANTHROPIC_KEY_FILE(/run/secrets/anthropic-api-key)에서 읽고 비어 있으면 명확한 오류를 낸다.
Result에 usage(입력·출력·합계 토큰)를 추가해 하네스 diagnosis 이벤트에 비용 근거를 남긴다.
This commit is contained in:
seon committed 2026-10-14 23:21:46 +09:00
1 parent fa9c08c778
commit 53ff243183
8 files changed
+492 -12

No files matched your search

+6
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/selfheal-lab/selfheal-lab-apps/ai/internal/agent"
"github.com/selfheal-lab/selfheal-lab-apps/ai/internal/anthropic"
"github.com/selfheal-lab/selfheal-lab-apps/ai/internal/diag"
"github.com/selfheal-lab/selfheal-lab-apps/ai/internal/harness"
"github.com/selfheal-lab/selfheal-lab-apps/ai/internal/jev"
@@ -95,6 +96,10 @@ func main() {
HitlURL: env("HITL_URL", "http://hitl:8081"),
}
ag := agent.New(acfg, oai, tr, hc, store, enums, pv)
// Claude Messages API for claude-* model ids chosen per campaign; the key may be registered later (empty = diagnoses with
// a claude-* model fail with "anthropic api key is empty", recorded in the harness diagnosis event).
anth := anthropic.New(env("ANTHROPIC_BASE_URL", ""), keyReader(env("ANTHROPIC_KEY_FILE", "/run/secrets/anthropic-api-key")))
ag.SetAnthropic(anth)
jevMode := env("JEV_MODE", "off") // off | shadow | enforce (shadow records Jev answers next to the diagnosis; enforce also applies the two review rules)
var jc *jev.Client
if jevMode == "shadow" || jevMode == "enforce" {
@@ -130,6 +135,7 @@ func main() {
out := map[string]any{"model": acfg.Model, "prompt_version": pv, "labels": enums.Labels, "actions": enums.Actions}
key, kerr := keyReader(env("OPENAI_KEY_FILE", "/run/secrets/openai-api-key"))()
out["openai_key_present"] = kerr == nil && key != ""
out["anthropic_key_present"] = anth.KeyPresent()
out["jev_mode"] = jevMode
if jc != nil {
out["jev_key_present"] = jc.KeyPresent()