package oci // 真实 OCI GenAI effort 探针(重建版)。 // // 前身 genai_cache_integration_test.go 承载缓存/亲和/服务端工具等历轮调研模式, // 调研结论均已归档(.trellis/tasks/archive/2026-07/*/research/),该文件已删除; // 本文件重建共享基础设施,保留 effort 支持矩阵探测,并新增 responses 端点探测 // (multi-agent 模型仅允许走 Responses 面)。 // // 运行(默认跳过,不触网): // // OCI_GENAI_CACHE_PROBE=effort-matrix OCI_CACHE_DB= DATA_KEY=<主密钥> \ // OCI_CACHE_RUN_ID=<唯一标识> go test ./internal/oci/ -run TestRealGenAiEffortMatrix -v // // 可选:OCI_CACHE_CONFIG_ID(默认 6)、OCI_CACHE_REGION(默认 us-chicago-1)、 // OCI_EFFORT_CASES 自定义用例("model=effort" 逗号分隔;effort 后缀 "@responses" // 表示走 /actions/v1/responses 端点,如 "xai.grok-4.20-multi-agent=low@responses")。 // // 纪律:数据库只读打开;OCI_GO_SDK_DEBUG 必须为空;日志不落原始请求体与凭据, // 错误消息经 OCID 遮掩后截断。 import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "regexp" "strings" "testing" "time" "github.com/glebarez/sqlite" "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/generativeaiinference" "gorm.io/gorm" "gorm.io/gorm/logger" projectcrypto "oci-portal/internal/crypto" "oci-portal/internal/model" ) const ( effortProbeRegion = "us-chicago-1" effortProbeLimit = int64(4 << 20) ) var effortRunIDPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,64}$`) type effortProbeCase struct { model string effort string responses bool // true 走 /actions/v1/responses,否则 typed /actions/chat stream bool // 仅 responses 面:请求 SSE 流,观测事件类型序列 } type effortProbeResources struct { cred Credentials region string modelOCID map[string]string inference generativeaiinference.GenerativeAiInferenceClient } type effortProbeResult struct { status int requestRef string duration time.Duration errorCode string errorMessage string completionTokens int reasoningTokens int answerLen int outputTypes []string } // effortMatrixCases 默认批为 grok-4.3 五档基线;全模型矩阵结论见任务归档, // 复测用 OCI_EFFORT_CASES 自定义。 func effortMatrixCases() []effortProbeCase { if raw := os.Getenv("OCI_EFFORT_CASES"); raw != "" { return parseEffortCases(raw) } return []effortProbeCase{ {model: "xai.grok-4.3", effort: "none", responses: true}, {model: "xai.grok-4.3", effort: "low", responses: true}, {model: "xai.grok-4.3", effort: "high", responses: true}, } } // parseEffortCases 解析 "model=effort[@responses|@responses-stream]" 逗号分隔用例。 func parseEffortCases(raw string) []effortProbeCase { var out []effortProbeCase for _, item := range strings.Split(raw, ",") { parts := strings.SplitN(strings.TrimSpace(item), "=", 2) if len(parts) != 2 || parts[0] == "" || parts[1] == "" { continue } effort, streaming := strings.CutSuffix(parts[1], "@responses-stream") if streaming { out = append(out, effortProbeCase{model: parts[0], effort: effort, responses: true, stream: true}) continue } effort, _ = strings.CutSuffix(effort, "@responses") // 后缀兼容保留;所有用例均走 responses 面 out = append(out, effortProbeCase{model: parts[0], effort: effort, responses: true}) } return out } func TestRealGenAiEffortMatrix(t *testing.T) { requireEffortProbeMode(t, "effort-matrix") ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute) defer cancel() resources := newEffortProbeResources(t, ctx) for _, tc := range effortMatrixCases() { runEffortCase(t, ctx, resources, tc) } } func requireEffortProbeMode(t *testing.T, wanted string) { t.Helper() if os.Getenv("OCI_GENAI_CACHE_PROBE") != wanted { t.Skipf("set OCI_GENAI_CACHE_PROBE=%s to run", wanted) } if os.Getenv("OCI_GO_SDK_DEBUG") != "" { t.Fatal("OCI_GO_SDK_DEBUG must be unset to protect signed requests") } if !effortRunIDPattern.MatchString(os.Getenv("OCI_CACHE_RUN_ID")) { t.Fatal("set a unique OCI_CACHE_RUN_ID (1-64 safe characters)") } } func runEffortCase(t *testing.T, ctx context.Context, resources effortProbeResources, tc effortProbeCase) { t.Helper() ocid, ok := resources.modelOCID[tc.model] if !ok { t.Logf("model=%s effort=%s resolve-failed: model not in on-demand catalog", tc.model, tc.effort) return } _ = ocid body, path, err := effortProbeBody(tc) if err != nil { t.Fatalf("build effort body: %v", err) } result := executeEffortProbe(ctx, resources, path, body) logEffortResult(t, tc, result) } // effortProbeBody 构造 OpenAI Responses 直通请求体(typed chat 面已随网关剔除, // 探针仅保留 responses 端点;历史 typed 结论见任务归档)。 func effortProbeBody(tc effortProbeCase) ([]byte, string, error) { question := "How many positive divisors does 360 have? Answer with the number only." body := map[string]interface{}{"model": tc.model, "input": question, "max_output_tokens": 2048, "store": false} if tc.effort != "" { body["reasoning"] = map[string]string{"effort": tc.effort} } if tc.stream { body["stream"] = true } payload, err := json.Marshal(body) return payload, "/actions/v1/responses", err } // executeEffortProbe 以 IAM 签名裸 POST 指定路径;responses 面按生产实现补 compartment 头。 func executeEffortProbe(ctx context.Context, resources effortProbeResources, path string, body []byte) effortProbeResult { client := resources.inference.BaseClient client.Configuration.CircuitBreaker = nil common.UpdateEndpointTemplateForOptions(&client) common.SetMissingTemplateParams(&client) request, err := http.NewRequestWithContext(ctx, http.MethodPost, path, strings.NewReader(string(body))) if err != nil { return effortProbeResult{errorCode: "BuildRequest", errorMessage: err.Error()} } request.Header.Set("Content-Type", "application/json") if path != "/actions/chat" { request.Header.Set("CompartmentId", resources.cred.TenancyOCID) request.Header.Set("opc-compartment-id", resources.cred.TenancyOCID) } return effortCallProbe(ctx, client, request) } func effortCallProbe(ctx context.Context, client common.BaseClient, request *http.Request) effortProbeResult { started := time.Now() response, err := client.Call(ctx, request) result := effortProbeResult{duration: time.Since(started)} if response != nil { result.status = response.StatusCode result.requestRef = shortEffortRef(response.Header.Get("opc-request-id")) defer response.Body.Close() } if err != nil { result.errorCode, result.errorMessage = effortProbeError(err) return result } payload, readErr := io.ReadAll(io.LimitReader(response.Body, effortProbeLimit)) if readErr != nil { result.errorCode, result.errorMessage = "ReadResponse", readErr.Error() return result } parseEffortPayload(&result, payload) return result } func effortProbeError(err error) (string, string) { if serviceErr, ok := common.IsServiceError(err); ok { return fmt.Sprintf("%d", serviceErr.GetHTTPStatusCode()), sanitizeEffortText(serviceErr.GetMessage()) } return "CallError", sanitizeEffortText(err.Error()) } // parseEffortPayload 兼容三种 usage 形态:typed 驼峰(chatResponse.usage.completionTokens)、 // chat 兼容面下划线(usage.completion_tokens)、responses 面(usage.output_tokens)。 func parseEffortPayload(result *effortProbeResult, payload []byte) { if text := strings.TrimSpace(string(payload)); strings.HasPrefix(text, "event:") || strings.HasPrefix(text, "data:") { parseEffortSSE(result, text) return } var root map[string]interface{} if json.Unmarshal(payload, &root) != nil { return } result.answerLen = len(parseEffortAnswer(root)) result.outputTypes = parseEffortOutputTypes(root) if typed, ok := root["chatResponse"].(map[string]interface{}); ok { root = typed } usage, _ := root["usage"].(map[string]interface{}) for _, key := range []string{"completionTokens", "completion_tokens", "output_tokens"} { if v, ok := effortInt(usage, key); ok { result.completionTokens = v break } } for _, detailsKey := range []string{"completionTokensDetails", "completion_tokens_details", "output_tokens_details"} { details, _ := usage[detailsKey].(map[string]interface{}) for _, key := range []string{"reasoningTokens", "reasoning_tokens"} { if v, ok := effortInt(details, key); ok { result.reasoningTokens = v } } } } // parseEffortAnswer 提取正文文本:typed 取 chatResponse.choices[].message.content[].text, // responses 取 output[] 里 message 项的 content[].text。仅在内存中量长度,不落日志。 func parseEffortAnswer(root map[string]interface{}) string { var sb strings.Builder if typed, ok := root["chatResponse"].(map[string]interface{}); ok { choices, _ := typed["choices"].([]interface{}) for _, c := range choices { cm, _ := c.(map[string]interface{}) msg, _ := cm["message"].(map[string]interface{}) collectEffortText(&sb, msg["content"]) } return sb.String() } output, _ := root["output"].([]interface{}) for _, item := range output { im, _ := item.(map[string]interface{}) if im["type"] == "message" { collectEffortText(&sb, im["content"]) } } return sb.String() } func collectEffortText(sb *strings.Builder, content interface{}) { parts, _ := content.([]interface{}) for _, p := range parts { pm, _ := p.(map[string]interface{}) if text, ok := pm["text"].(string); ok { sb.WriteString(text) } } } // parseEffortSSE 汇总 SSE 流:事件类型去重序列进 outputTypes,正文增量长度进 answerLen, // 末尾 completed 事件里的 usage 进 tokens 字段。 func parseEffortSSE(result *effortProbeResult, text string) { seen := map[string]bool{} for _, line := range strings.Split(text, "\n") { if name, ok := strings.CutPrefix(line, "event: "); ok { if name = strings.TrimSpace(name); !seen[name] { seen[name] = true result.outputTypes = append(result.outputTypes, name) } continue } data, ok := strings.CutPrefix(line, "data: ") if !ok { continue } var ev map[string]interface{} if json.Unmarshal([]byte(data), &ev) != nil { continue } if name, ok := ev["type"].(string); ok && !seen[name] { // 纯 data: 行流(无 event: 行)从事件体取类型 seen[name] = true result.outputTypes = append(result.outputTypes, name) } if delta, ok := ev["delta"].(string); ok && strings.Contains(fmt.Sprint(ev["type"]), "output_text") { result.answerLen += len(delta) } if resp, ok := ev["response"].(map[string]interface{}); ok { usage, _ := resp["usage"].(map[string]interface{}) if v, ok := effortInt(usage, "output_tokens"); ok { result.completionTokens = v } } } } // parseEffortOutputTypes 收集 responses 面 output 项类型序列(multi-agent 行为观测)。 func parseEffortOutputTypes(root map[string]interface{}) []string { output, _ := root["output"].([]interface{}) var types []string for _, item := range output { im, _ := item.(map[string]interface{}) if s, ok := im["type"].(string); ok { types = append(types, s) } } return types } func effortInt(root map[string]interface{}, key string) (int, bool) { if root == nil { return 0, false } if value, ok := root[key].(float64); ok { return int(value), true } return 0, false } func logEffortResult(t *testing.T, tc effortProbeCase, result effortProbeResult) { t.Helper() endpoint := "typed-chat" if tc.responses { endpoint = "compat-responses" } t.Logf("endpoint=%s model=%s effort=%s status=%d completion=%d reasoning=%d answer_len=%d output_types=%v request=%s duration_ms=%d error=%s message=%q", endpoint, tc.model, tc.effort, result.status, result.completionTokens, result.reasoningTokens, result.answerLen, result.outputTypes, result.requestRef, result.duration.Milliseconds(), result.errorCode, result.errorMessage) } // sanitizeEffortText 遮掩 OCID 并截断,避免错误消息携带租户可定位信息。 func sanitizeEffortText(text string) string { text = regexp.MustCompile(`ocid1\.[a-z0-9._-]+`).ReplaceAllString(text, "ocid1.***") if len(text) > 300 { text = text[:300] + "..." } return text } func shortEffortRef(ref string) string { if len(ref) > 12 { return ref[:12] } return ref } func effortEnvOr(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback } // newEffortProbeResources 装配凭据、区域、inference 客户端与按需模型目录。 func newEffortProbeResources(t *testing.T, ctx context.Context) effortProbeResources { t.Helper() cred := effortProbeCredentials(t) region := effortEnvOr("OCI_CACHE_REGION", effortProbeRegion) cred.Region = region real := &RealClient{} ic, err := real.genAiInferenceClient(cred, region) if err != nil { t.Fatalf("new inference client: %v", err) } models, err := real.ListGenAiModels(ctx, cred, region) if err != nil { t.Fatalf("list models: %s", sanitizeEffortText(err.Error())) } index := make(map[string]string, len(models)) for _, m := range models { index[m.Name] = m.Ocid } return effortProbeResources{cred: cred, region: region, modelOCID: index, inference: ic} } // effortProbeCredentials 优先从面板数据库(只读)取渠道凭据,否则退回本地 ini 测试凭据。 func effortProbeCredentials(t *testing.T) Credentials { t.Helper() if dbPath := os.Getenv("OCI_CACHE_DB"); dbPath != "" { return effortDBCredentials(t, dbPath) } return loadTestCredentials(t, effortEnvOr("OCI_TEST_KEY", "试用期")) } func effortDBCredentials(t *testing.T, dbPath string) Credentials { t.Helper() dsn := fmt.Sprintf("file:%s?mode=ro", dbPath) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) if err != nil { t.Fatalf("open read-only probe database: %v", err) } cipher, err := projectcrypto.NewCipher(os.Getenv("DATA_KEY")) if err != nil { t.Fatalf("create data cipher: %v", err) } var config model.OciConfig if err := db.First(&config, effortEnvOr("OCI_CACHE_CONFIG_ID", "6")).Error; err != nil { t.Fatalf("load OCI config: %v", err) } return decryptEffortCredentials(t, db, cipher, config) } func decryptEffortCredentials(t *testing.T, db *gorm.DB, cipher *projectcrypto.Cipher, config model.OciConfig) Credentials { t.Helper() privateKey, err := cipher.DecryptString(config.PrivateKeyEnc) if err != nil { t.Fatalf("decrypt OCI config %d private key: %v", config.ID, err) } passphrase := "" if config.PassphraseEnc != "" { if passphrase, err = cipher.DecryptString(config.PassphraseEnc); err != nil { t.Fatalf("decrypt OCI config %d passphrase: %v", config.ID, err) } } return Credentials{TenancyOCID: config.TenancyOCID, UserOCID: config.UserOCID, Fingerprint: config.Fingerprint, Region: config.Region, PrivateKey: privateKey, Passphrase: passphrase, Proxy: loadEffortProxy(t, db, cipher, config.ProxyID)} } func loadEffortProxy(t *testing.T, db *gorm.DB, cipher *projectcrypto.Cipher, proxyID *uint) *ProxySpec { t.Helper() if proxyID == nil { return nil } var proxy model.Proxy if err := db.First(&proxy, *proxyID).Error; err != nil { t.Fatalf("load proxy %d: %v", *proxyID, err) } password := "" if proxy.PasswordEnc != "" { decrypted, err := cipher.DecryptString(proxy.PasswordEnc) if err != nil { t.Fatalf("decrypt proxy %d password: %v", *proxyID, err) } password = decrypted } return &ProxySpec{Type: proxy.Type, Host: proxy.Host, Port: proxy.Port, Username: proxy.Username, Password: password} } // TestParseEffortCases 断言自定义用例解析:端点后缀、空项与畸形项跳过。 func TestParseEffortCases(t *testing.T) { got := parseEffortCases("a=low, b=high@responses ,bad,=x,c=") want := []effortProbeCase{{model: "a", effort: "low", responses: true}, {model: "b", effort: "high", responses: true}} if len(got) != len(want) { t.Fatalf("parseEffortCases = %+v, want %+v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("case %d = %+v, want %+v", i, got[i], want[i]) } } } // TestParseEffortPayload 断言三种 usage 形态与 output 类型序列的解析。 func TestParseEffortPayload(t *testing.T) { cases := []struct { name string payload string completion int reasoning int types int }{ {"typed 驼峰", `{"chatResponse":{"choices":[{"message":{"content":[{"type":"TEXT","text":"24"}]}}],"usage":{"completionTokens":5,"completionTokensDetails":{"reasoningTokens":9}}}}`, 5, 9, 0}, {"responses 面", `{"output":[{"type":"reasoning"},{"type":"message","content":[{"type":"output_text","text":"24"}]}],"usage":{"output_tokens":7,"output_tokens_details":{"reasoning_tokens":3}}}`, 7, 3, 2}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { var result effortProbeResult parseEffortPayload(&result, []byte(tc.payload)) if result.completionTokens != tc.completion || result.reasoningTokens != tc.reasoning || len(result.outputTypes) != tc.types { t.Fatalf("parse = %+v, want completion=%d reasoning=%d types=%d", result, tc.completion, tc.reasoning, tc.types) } if result.answerLen != 2 { t.Fatalf("answerLen = %d, want 2", result.answerLen) } }) } }