{"repo": "agenthands/helix", "files": {"test/oracle/llm/client.go": {"9dd32f9689bf4716486b52d2f5efcb6c387b9318": {"t": 1785745650.9018784, "n": 4964, "h": "de092c634ebc5082", "c": "//go:build llm || llmjudge\n\npackage llm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n\toai \"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\nconst (\n\t// EnvAPIKey is the environment variable for the Anthropic API key.\n\tEnvAPIKey = \"ANTHROPIC_API_KEY\"\n\t// EnvDeepSeekKey is the environment variable for the DeepSeek API key.\n\tEnvDeepSeekKey = \"DEEPSEEK_API_KEY\"\n\t// EnvTestModel overrides the subject model for behavioral tests.\n\tEnvTestModel = \"HELIX_TEST_MODEL\"\n\t// EnvJudgeModel overrides the judge model for scoring.\n\tEnvJudgeModel = \"HELIX_JUDGE_MODEL\"\n\t// EnvInlineJudge enables inline judge mode when set to \"1\".\n\tEnvInlineJudge = \"HELIX_INLINE_JUDGE\"\n\t// EnvLLMProvider selects the LLM provider: \"anthropic\" (default) or \"deepseek\".\n\tEnvLLMProvider = \"HELIX_LLM_PROVIDER\"\n\t// DefaultSubjectModel is the cheapest Claude model for behavioral tests (D-11).\n\tDefaultSubjectModel = \"claude-haiku-4-5-20251001\"\n\t// DefaultDeepSeekModel is the default DeepSeek model.\n\tDefaultDeepSeekModel = \"deepseek-chat\"\n)\n\n// Provider returns the active LLM provider name.\nfunc Provider() string {\n\tif p := os.Getenv(EnvLLMProvider); p != \"\" {\n\t\treturn p\n\t}\n\t// Auto-detect: if DeepSeek key is set but Anthropic isn't, use DeepSeek.\n\tif os.Getenv(EnvAPIKey) == \"\" && os.Getenv(EnvDeepSeekKey) != \"\" {\n\t\treturn \"deepseek\"\n\t}\n\treturn \"anthropic\"\n}\n\n// SkipWithoutAPIKey skips the test if no LLM API key is available (D-18).\n// Never logs the key value to avoid information disclosure (T-21-01).\nfunc SkipWithoutAPIKey(t *testing.T) {\n\tt.Helper()\n\tp := Provider()\n\tswitch p {\n\tcase \"deepseek\":\n\t\tif os.Getenv(EnvDeepSeekKey) == \"\" {\n\t\t\tt.Skip(\"DEEPSEEK_API_KEY not set\")\n\t\t}\n\tdefault:\n\t\tif os.Getenv(EnvAPIKey) == \"\" {\n\t\t\tt.Skip(\"ANTHROPIC_API_KEY not set\")\n\t\t}\n\t}\n}\n\n// NewClient creates an Anthropic client that reads ANTHROPIC_API_KEY from env.\n// Only used when provider is \"anthropic\".\nfunc NewClient() anthropic.Client {\n\treturn anthropic.NewClient()\n}\n\n// SubjectModel returns the model ID for behavioral tests.\n// Uses HELIX_TEST_MODEL if set, otherwise defaults per provider (D-10/D-11).\nfunc SubjectModel() string {\n\tif m := os.Getenv(EnvTestModel); m != \"\" {\n\t\treturn m\n\t}\n\tif Provider() == \"deepseek\" {\n\t\treturn DefaultDeepSeekModel\n\t}\n\treturn DefaultSubjectModel\n}\n\n// JudgeModel returns the model ID for judge scoring and whether the run is self-judged.\n// Uses HELIX_JUDGE_MODEL if set, otherwise falls back to SubjectModel (D-10/D-12).\nfunc JudgeModel() (model string, selfJudged bool) {\n\tif m := os.Getenv(EnvJudgeModel); m != \"\" {\n\t\treturn m, false\n\t}\n\treturn SubjectModel(), true\n}\n\n// AskSingleTurn sends a single-turn prompt and returns the response text,\n// stop reason, and any error. Routes to the active provider.\nfunc AskSingleTurn(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (responseText string, stopReason string, err error) {\n\tif Provider() == \"deepseek\" {\n\t\treturn askDeepSeek(ctx, model, system, userPrompt)\n\t}\n\treturn askAnthropic(ctx, client, model, system, userPrompt)\n}\n\n// askAnthropic sends via Anthropic API. Uses MaxTokens: 1024 (D-01).\nfunc askAnthropic(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (string, string, error) {\n\tparams := anthropic.MessageNewParams{\n\t\tModel: model,\n\t\tMaxTokens: 1024,\n\t\tMessages: []anthropic.MessageParam{\n\t\t\tanthropic.NewUserMessage(anthropic.NewTextBlock(userPrompt)),\n\t\t},\n\t}\n\tif system != \"\" {\n\t\tparams.System = []anthropic.TextBlockParam{\n\t\t\t{Text: system},\n\t\t}\n\t}\n\n\tmessage, err := client.Messages.New(ctx, params)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"anthropic API call failed: %w\", err)\n\t}\n\n\tfor _, block := range message.Content {\n\t\tif v, ok := block.AsAny().(anthropic.TextBlock); ok {\n\t\t\treturn v.Text, string(message.StopReason), nil\n\t\t}\n\t}\n\n\treturn \"\", string(message.StopReason), fmt.Errorf(\"no text content in response\")\n}\n\n// askDeepSeek sends via DeepSeek's OpenAI-compatible API.\nfunc askDeepSeek(ctx context.Context, model, system, userPrompt string) (string, string, error) {\n\tclient := oai.NewClient(\n\t\toption.WithAPIKey(os.Getenv(EnvDeepSeekKey)),\n\t\toption.WithBaseURL(\"https://api.deepseek.com\"),\n\t)\n\n\tvar messages []oai.ChatCompletionMessageParamUnion\n\tif system != \"\" {\n\t\tmessages = append(messages, oai.SystemMessage(system))\n\t}\n\tmessages = append(messages, oai.UserMessage(userPrompt))\n\n\tresp, err := client.Chat.Completions.New(ctx, oai.ChatCompletionNewParams{\n\t\tModel: model,\n\t\tMessages: messages,\n\t})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"deepseek API call failed: %w\", err)\n\t}\n\n\tif len(resp.Choices) == 0 {\n\t\treturn \"\", \"\", fmt.Errorf(\"no choices in deepseek response\")\n\t}\n\n\tchoice := resp.Choices[0]\n\treturn choice.Message.Content, string(choice.FinishReason), nil\n}\n\n// InterCallDelay sleeps 250ms between API calls to avoid rate limits (D-19).\nfunc InterCallDelay() {\n\ttime.Sleep(250 * time.Millisecond)\n}\n"}, "6501092f14b36d1e467bb71a2ae21db2a850acd3": {"t": 1785755391.6387677, "n": 4964, "h": "de092c634ebc5082", "c": "//go:build llm || llmjudge\n\npackage llm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n\toai \"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\nconst (\n\t// EnvAPIKey is the environment variable for the Anthropic API key.\n\tEnvAPIKey = \"ANTHROPIC_API_KEY\"\n\t// EnvDeepSeekKey is the environment variable for the DeepSeek API key.\n\tEnvDeepSeekKey = \"DEEPSEEK_API_KEY\"\n\t// EnvTestModel overrides the subject model for behavioral tests.\n\tEnvTestModel = \"HELIX_TEST_MODEL\"\n\t// EnvJudgeModel overrides the judge model for scoring.\n\tEnvJudgeModel = \"HELIX_JUDGE_MODEL\"\n\t// EnvInlineJudge enables inline judge mode when set to \"1\".\n\tEnvInlineJudge = \"HELIX_INLINE_JUDGE\"\n\t// EnvLLMProvider selects the LLM provider: \"anthropic\" (default) or \"deepseek\".\n\tEnvLLMProvider = \"HELIX_LLM_PROVIDER\"\n\t// DefaultSubjectModel is the cheapest Claude model for behavioral tests (D-11).\n\tDefaultSubjectModel = \"claude-haiku-4-5-20251001\"\n\t// DefaultDeepSeekModel is the default DeepSeek model.\n\tDefaultDeepSeekModel = \"deepseek-chat\"\n)\n\n// Provider returns the active LLM provider name.\nfunc Provider() string {\n\tif p := os.Getenv(EnvLLMProvider); p != \"\" {\n\t\treturn p\n\t}\n\t// Auto-detect: if DeepSeek key is set but Anthropic isn't, use DeepSeek.\n\tif os.Getenv(EnvAPIKey) == \"\" && os.Getenv(EnvDeepSeekKey) != \"\" {\n\t\treturn \"deepseek\"\n\t}\n\treturn \"anthropic\"\n}\n\n// SkipWithoutAPIKey skips the test if no LLM API key is available (D-18).\n// Never logs the key value to avoid information disclosure (T-21-01).\nfunc SkipWithoutAPIKey(t *testing.T) {\n\tt.Helper()\n\tp := Provider()\n\tswitch p {\n\tcase \"deepseek\":\n\t\tif os.Getenv(EnvDeepSeekKey) == \"\" {\n\t\t\tt.Skip(\"DEEPSEEK_API_KEY not set\")\n\t\t}\n\tdefault:\n\t\tif os.Getenv(EnvAPIKey) == \"\" {\n\t\t\tt.Skip(\"ANTHROPIC_API_KEY not set\")\n\t\t}\n\t}\n}\n\n// NewClient creates an Anthropic client that reads ANTHROPIC_API_KEY from env.\n// Only used when provider is \"anthropic\".\nfunc NewClient() anthropic.Client {\n\treturn anthropic.NewClient()\n}\n\n// SubjectModel returns the model ID for behavioral tests.\n// Uses HELIX_TEST_MODEL if set, otherwise defaults per provider (D-10/D-11).\nfunc SubjectModel() string {\n\tif m := os.Getenv(EnvTestModel); m != \"\" {\n\t\treturn m\n\t}\n\tif Provider() == \"deepseek\" {\n\t\treturn DefaultDeepSeekModel\n\t}\n\treturn DefaultSubjectModel\n}\n\n// JudgeModel returns the model ID for judge scoring and whether the run is self-judged.\n// Uses HELIX_JUDGE_MODEL if set, otherwise falls back to SubjectModel (D-10/D-12).\nfunc JudgeModel() (model string, selfJudged bool) {\n\tif m := os.Getenv(EnvJudgeModel); m != \"\" {\n\t\treturn m, false\n\t}\n\treturn SubjectModel(), true\n}\n\n// AskSingleTurn sends a single-turn prompt and returns the response text,\n// stop reason, and any error. Routes to the active provider.\nfunc AskSingleTurn(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (responseText string, stopReason string, err error) {\n\tif Provider() == \"deepseek\" {\n\t\treturn askDeepSeek(ctx, model, system, userPrompt)\n\t}\n\treturn askAnthropic(ctx, client, model, system, userPrompt)\n}\n\n// askAnthropic sends via Anthropic API. Uses MaxTokens: 1024 (D-01).\nfunc askAnthropic(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (string, string, error) {\n\tparams := anthropic.MessageNewParams{\n\t\tModel: model,\n\t\tMaxTokens: 1024,\n\t\tMessages: []anthropic.MessageParam{\n\t\t\tanthropic.NewUserMessage(anthropic.NewTextBlock(userPrompt)),\n\t\t},\n\t}\n\tif system != \"\" {\n\t\tparams.System = []anthropic.TextBlockParam{\n\t\t\t{Text: system},\n\t\t}\n\t}\n\n\tmessage, err := client.Messages.New(ctx, params)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"anthropic API call failed: %w\", err)\n\t}\n\n\tfor _, block := range message.Content {\n\t\tif v, ok := block.AsAny().(anthropic.TextBlock); ok {\n\t\t\treturn v.Text, string(message.StopReason), nil\n\t\t}\n\t}\n\n\treturn \"\", string(message.StopReason), fmt.Errorf(\"no text content in response\")\n}\n\n// askDeepSeek sends via DeepSeek's OpenAI-compatible API.\nfunc askDeepSeek(ctx context.Context, model, system, userPrompt string) (string, string, error) {\n\tclient := oai.NewClient(\n\t\toption.WithAPIKey(os.Getenv(EnvDeepSeekKey)),\n\t\toption.WithBaseURL(\"https://api.deepseek.com\"),\n\t)\n\n\tvar messages []oai.ChatCompletionMessageParamUnion\n\tif system != \"\" {\n\t\tmessages = append(messages, oai.SystemMessage(system))\n\t}\n\tmessages = append(messages, oai.UserMessage(userPrompt))\n\n\tresp, err := client.Chat.Completions.New(ctx, oai.ChatCompletionNewParams{\n\t\tModel: model,\n\t\tMessages: messages,\n\t})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"deepseek API call failed: %w\", err)\n\t}\n\n\tif len(resp.Choices) == 0 {\n\t\treturn \"\", \"\", fmt.Errorf(\"no choices in deepseek response\")\n\t}\n\n\tchoice := resp.Choices[0]\n\treturn choice.Message.Content, string(choice.FinishReason), nil\n}\n\n// InterCallDelay sleeps 250ms between API calls to avoid rate limits (D-19).\nfunc InterCallDelay() {\n\ttime.Sleep(250 * time.Millisecond)\n}\n"}, "95c0025c346d6e20b1e0b99cfab075f6db5cc991": {"t": 1785755392.8431807, "n": 4970, "h": "7b138b582c2c0009", "c": "//go:build llm || llmjudge\n\npackage llm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n\toai \"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\nconst (\n\t// EnvAPIKey is the environment variable for the Anthropic API key.\n\tEnvAPIKey = \"ANTHROPIC_API_KEY\"\n\t// EnvDeepSeekKey is the environment variable for the DeepSeek API key.\n\tEnvDeepSeekKey = \"DEEPSEEK_API_KEY\"\n\t// EnvTestModel overrides the subject model for behavioral tests.\n\tEnvTestModel = \"SERENA_TEST_MODEL\"\n\t// EnvJudgeModel overrides the judge model for scoring.\n\tEnvJudgeModel = \"SERENA_JUDGE_MODEL\"\n\t// EnvInlineJudge enables inline judge mode when set to \"1\".\n\tEnvInlineJudge = \"SERENA_INLINE_JUDGE\"\n\t// EnvLLMProvider selects the LLM provider: \"anthropic\" (default) or \"deepseek\".\n\tEnvLLMProvider = \"SERENA_LLM_PROVIDER\"\n\t// DefaultSubjectModel is the cheapest Claude model for behavioral tests (D-11).\n\tDefaultSubjectModel = \"claude-haiku-4-5-20251001\"\n\t// DefaultDeepSeekModel is the default DeepSeek model.\n\tDefaultDeepSeekModel = \"deepseek-chat\"\n)\n\n// Provider returns the active LLM provider name.\nfunc Provider() string {\n\tif p := os.Getenv(EnvLLMProvider); p != \"\" {\n\t\treturn p\n\t}\n\t// Auto-detect: if DeepSeek key is set but Anthropic isn't, use DeepSeek.\n\tif os.Getenv(EnvAPIKey) == \"\" && os.Getenv(EnvDeepSeekKey) != \"\" {\n\t\treturn \"deepseek\"\n\t}\n\treturn \"anthropic\"\n}\n\n// SkipWithoutAPIKey skips the test if no LLM API key is available (D-18).\n// Never logs the key value to avoid information disclosure (T-21-01).\nfunc SkipWithoutAPIKey(t *testing.T) {\n\tt.Helper()\n\tp := Provider()\n\tswitch p {\n\tcase \"deepseek\":\n\t\tif os.Getenv(EnvDeepSeekKey) == \"\" {\n\t\t\tt.Skip(\"DEEPSEEK_API_KEY not set\")\n\t\t}\n\tdefault:\n\t\tif os.Getenv(EnvAPIKey) == \"\" {\n\t\t\tt.Skip(\"ANTHROPIC_API_KEY not set\")\n\t\t}\n\t}\n}\n\n// NewClient creates an Anthropic client that reads ANTHROPIC_API_KEY from env.\n// Only used when provider is \"anthropic\".\nfunc NewClient() anthropic.Client {\n\treturn anthropic.NewClient()\n}\n\n// SubjectModel returns the model ID for behavioral tests.\n// Uses SERENA_TEST_MODEL if set, otherwise defaults per provider (D-10/D-11).\nfunc SubjectModel() string {\n\tif m := os.Getenv(EnvTestModel); m != \"\" {\n\t\treturn m\n\t}\n\tif Provider() == \"deepseek\" {\n\t\treturn DefaultDeepSeekModel\n\t}\n\treturn DefaultSubjectModel\n}\n\n// JudgeModel returns the model ID for judge scoring and whether the run is self-judged.\n// Uses SERENA_JUDGE_MODEL if set, otherwise falls back to SubjectModel (D-10/D-12).\nfunc JudgeModel() (model string, selfJudged bool) {\n\tif m := os.Getenv(EnvJudgeModel); m != \"\" {\n\t\treturn m, false\n\t}\n\treturn SubjectModel(), true\n}\n\n// AskSingleTurn sends a single-turn prompt and returns the response text,\n// stop reason, and any error. Routes to the active provider.\nfunc AskSingleTurn(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (responseText string, stopReason string, err error) {\n\tif Provider() == \"deepseek\" {\n\t\treturn askDeepSeek(ctx, model, system, userPrompt)\n\t}\n\treturn askAnthropic(ctx, client, model, system, userPrompt)\n}\n\n// askAnthropic sends via Anthropic API. Uses MaxTokens: 1024 (D-01).\nfunc askAnthropic(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (string, string, error) {\n\tparams := anthropic.MessageNewParams{\n\t\tModel: model,\n\t\tMaxTokens: 1024,\n\t\tMessages: []anthropic.MessageParam{\n\t\t\tanthropic.NewUserMessage(anthropic.NewTextBlock(userPrompt)),\n\t\t},\n\t}\n\tif system != \"\" {\n\t\tparams.System = []anthropic.TextBlockParam{\n\t\t\t{Text: system},\n\t\t}\n\t}\n\n\tmessage, err := client.Messages.New(ctx, params)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"anthropic API call failed: %w\", err)\n\t}\n\n\tfor _, block := range message.Content {\n\t\tif v, ok := block.AsAny().(anthropic.TextBlock); ok {\n\t\t\treturn v.Text, string(message.StopReason), nil\n\t\t}\n\t}\n\n\treturn \"\", string(message.StopReason), fmt.Errorf(\"no text content in response\")\n}\n\n// askDeepSeek sends via DeepSeek's OpenAI-compatible API.\nfunc askDeepSeek(ctx context.Context, model, system, userPrompt string) (string, string, error) {\n\tclient := oai.NewClient(\n\t\toption.WithAPIKey(os.Getenv(EnvDeepSeekKey)),\n\t\toption.WithBaseURL(\"https://api.deepseek.com\"),\n\t)\n\n\tvar messages []oai.ChatCompletionMessageParamUnion\n\tif system != \"\" {\n\t\tmessages = append(messages, oai.SystemMessage(system))\n\t}\n\tmessages = append(messages, oai.UserMessage(userPrompt))\n\n\tresp, err := client.Chat.Completions.New(ctx, oai.ChatCompletionNewParams{\n\t\tModel: model,\n\t\tMessages: messages,\n\t})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"deepseek API call failed: %w\", err)\n\t}\n\n\tif len(resp.Choices) == 0 {\n\t\treturn \"\", \"\", fmt.Errorf(\"no choices in deepseek response\")\n\t}\n\n\tchoice := resp.Choices[0]\n\treturn choice.Message.Content, string(choice.FinishReason), nil\n}\n\n// InterCallDelay sleeps 250ms between API calls to avoid rate limits (D-19).\nfunc InterCallDelay() {\n\ttime.Sleep(250 * time.Millisecond)\n}\n"}, "49b599317bdb461e4c8a0fbd4fd7eb0a45d5bd5f": {"t": 1785755393.9997447, "n": 4968, "h": "688776e9b5332fcc", "c": "//go:build llm || llmjudge\n\npackage llm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n\toai \"github.com/openai/openai-go\"\n\t\"github.com/openai/openai-go/option\"\n)\n\nconst (\n\t// EnvAPIKey is the environment variable for the Anthropic API key.\n\tEnvAPIKey = \"ANTHROPIC_API_KEY\"\n\t// EnvDeepSeekKey is the environment variable for the DeepSeek API key.\n\tEnvDeepSeekKey = \"DEEPSEEK_API_KEY\"\n\t// EnvTestModel overrides the subject model for behavioral tests.\n\tEnvTestModel = \"SERENA_TEST_MODEL\"\n\t// EnvJudgeModel overrides the judge model for scoring.\n\tEnvJudgeModel = \"SERENA_JUDGE_MODEL\"\n\t// EnvInlineJudge enables inline judge mode when set to \"1\".\n\tEnvInlineJudge = \"SERENA_INLINE_JUDGE\"\n\t// EnvLLMProvider selects the LLM provider: \"anthropic\" (default) or \"deepseek\".\n\tEnvLLMProvider = \"SERENA_LLM_PROVIDER\"\n\t// DefaultSubjectModel is the cheapest Claude model for behavioral tests (D-11).\n\tDefaultSubjectModel = \"claude-haiku-4-20250414\"\n\t// DefaultDeepSeekModel is the default DeepSeek model.\n\tDefaultDeepSeekModel = \"deepseek-chat\"\n)\n\n// Provider returns the active LLM provider name.\nfunc Provider() string {\n\tif p := os.Getenv(EnvLLMProvider); p != \"\" {\n\t\treturn p\n\t}\n\t// Auto-detect: if DeepSeek key is set but Anthropic isn't, use DeepSeek.\n\tif os.Getenv(EnvAPIKey) == \"\" && os.Getenv(EnvDeepSeekKey) != \"\" {\n\t\treturn \"deepseek\"\n\t}\n\treturn \"anthropic\"\n}\n\n// SkipWithoutAPIKey skips the test if no LLM API key is available (D-18).\n// Never logs the key value to avoid information disclosure (T-21-01).\nfunc SkipWithoutAPIKey(t *testing.T) {\n\tt.Helper()\n\tp := Provider()\n\tswitch p {\n\tcase \"deepseek\":\n\t\tif os.Getenv(EnvDeepSeekKey) == \"\" {\n\t\t\tt.Skip(\"DEEPSEEK_API_KEY not set\")\n\t\t}\n\tdefault:\n\t\tif os.Getenv(EnvAPIKey) == \"\" {\n\t\t\tt.Skip(\"ANTHROPIC_API_KEY not set\")\n\t\t}\n\t}\n}\n\n// NewClient creates an Anthropic client that reads ANTHROPIC_API_KEY from env.\n// Only used when provider is \"anthropic\".\nfunc NewClient() anthropic.Client {\n\treturn anthropic.NewClient()\n}\n\n// SubjectModel returns the model ID for behavioral tests.\n// Uses SERENA_TEST_MODEL if set, otherwise defaults per provider (D-10/D-11).\nfunc SubjectModel() string {\n\tif m := os.Getenv(EnvTestModel); m != \"\" {\n\t\treturn m\n\t}\n\tif Provider() == \"deepseek\" {\n\t\treturn DefaultDeepSeekModel\n\t}\n\treturn DefaultSubjectModel\n}\n\n// JudgeModel returns the model ID for judge scoring and whether the run is self-judged.\n// Uses SERENA_JUDGE_MODEL if set, otherwise falls back to SubjectModel (D-10/D-12).\nfunc JudgeModel() (model string, selfJudged bool) {\n\tif m := os.Getenv(EnvJudgeModel); m != \"\" {\n\t\treturn m, false\n\t}\n\treturn SubjectModel(), true\n}\n\n// AskSingleTurn sends a single-turn prompt and returns the response text,\n// stop reason, and any error. Routes to the active provider.\nfunc AskSingleTurn(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (responseText string, stopReason string, err error) {\n\tif Provider() == \"deepseek\" {\n\t\treturn askDeepSeek(ctx, model, system, userPrompt)\n\t}\n\treturn askAnthropic(ctx, client, model, system, userPrompt)\n}\n\n// askAnthropic sends via Anthropic API. Uses MaxTokens: 1024 (D-01).\nfunc askAnthropic(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (string, string, error) {\n\tparams := anthropic.MessageNewParams{\n\t\tModel: model,\n\t\tMaxTokens: 1024,\n\t\tMessages: []anthropic.MessageParam{\n\t\t\tanthropic.NewUserMessage(anthropic.NewTextBlock(userPrompt)),\n\t\t},\n\t}\n\tif system != \"\" {\n\t\tparams.System = []anthropic.TextBlockParam{\n\t\t\t{Text: system},\n\t\t}\n\t}\n\n\tmessage, err := client.Messages.New(ctx, params)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"anthropic API call failed: %w\", err)\n\t}\n\n\tfor _, block := range message.Content {\n\t\tif v, ok := block.AsAny().(anthropic.TextBlock); ok {\n\t\t\treturn v.Text, string(message.StopReason), nil\n\t\t}\n\t}\n\n\treturn \"\", string(message.StopReason), fmt.Errorf(\"no text content in response\")\n}\n\n// askDeepSeek sends via DeepSeek's OpenAI-compatible API.\nfunc askDeepSeek(ctx context.Context, model, system, userPrompt string) (string, string, error) {\n\tclient := oai.NewClient(\n\t\toption.WithAPIKey(os.Getenv(EnvDeepSeekKey)),\n\t\toption.WithBaseURL(\"https://api.deepseek.com\"),\n\t)\n\n\tvar messages []oai.ChatCompletionMessageParamUnion\n\tif system != \"\" {\n\t\tmessages = append(messages, oai.SystemMessage(system))\n\t}\n\tmessages = append(messages, oai.UserMessage(userPrompt))\n\n\tresp, err := client.Chat.Completions.New(ctx, oai.ChatCompletionNewParams{\n\t\tModel: model,\n\t\tMessages: messages,\n\t})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"deepseek API call failed: %w\", err)\n\t}\n\n\tif len(resp.Choices) == 0 {\n\t\treturn \"\", \"\", fmt.Errorf(\"no choices in deepseek response\")\n\t}\n\n\tchoice := resp.Choices[0]\n\treturn choice.Message.Content, string(choice.FinishReason), nil\n}\n\n// InterCallDelay sleeps 250ms between API calls to avoid rate limits (D-19).\nfunc InterCallDelay() {\n\ttime.Sleep(250 * time.Millisecond)\n}\n"}, "c4a7271ffc2a563b769dcf3bd46aa21c43ef17a2": {"t": 1785755396.3486178, "n": 2830, "h": "5621b7dd4980a2a1", "c": "//go:build llm || llmjudge\n\npackage llm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/anthropics/anthropic-sdk-go\"\n)\n\nconst (\n\t// EnvAPIKey is the environment variable for the Anthropic API key.\n\tEnvAPIKey = \"ANTHROPIC_API_KEY\"\n\t// EnvTestModel overrides the subject model for behavioral tests.\n\tEnvTestModel = \"SERENA_TEST_MODEL\"\n\t// EnvJudgeModel overrides the judge model for scoring.\n\tEnvJudgeModel = \"SERENA_JUDGE_MODEL\"\n\t// EnvInlineJudge enables inline judge mode when set to \"1\".\n\tEnvInlineJudge = \"SERENA_INLINE_JUDGE\"\n\t// DefaultSubjectModel is the cheapest Claude model for behavioral tests (D-11).\n\tDefaultSubjectModel = \"claude-haiku-4-20250414\"\n)\n\n// SkipWithoutAPIKey skips the test if ANTHROPIC_API_KEY is not set (D-18).\n// Never logs the key value to avoid information disclosure (T-21-01).\nfunc SkipWithoutAPIKey(t *testing.T) {\n\tt.Helper()\n\tif os.Getenv(EnvAPIKey) == \"\" {\n\t\tt.Skip(\"ANTHROPIC_API_KEY not set\")\n\t}\n}\n\n// NewClient creates an Anthropic client that reads ANTHROPIC_API_KEY from env.\nfunc NewClient() anthropic.Client {\n\treturn anthropic.NewClient()\n}\n\n// SubjectModel returns the model ID for behavioral tests.\n// Uses SERENA_TEST_MODEL if set, otherwise DefaultSubjectModel (D-10/D-11).\nfunc SubjectModel() string {\n\tif m := os.Getenv(EnvTestModel); m != \"\" {\n\t\treturn m\n\t}\n\treturn DefaultSubjectModel\n}\n\n// JudgeModel returns the model ID for judge scoring and whether the run is self-judged.\n// Uses SERENA_JUDGE_MODEL if set, otherwise falls back to SubjectModel (D-10/D-12).\nfunc JudgeModel() (model string, selfJudged bool) {\n\tif m := os.Getenv(EnvJudgeModel); m != \"\" {\n\t\treturn m, false\n\t}\n\treturn SubjectModel(), true\n}\n\n// AskSingleTurn sends a single-turn prompt and returns the response text,\n// stop reason, and any error. Uses MaxTokens: 1024 (D-01).\nfunc AskSingleTurn(ctx context.Context, client anthropic.Client, model, system, userPrompt string) (responseText string, stopReason string, err error) {\n\tparams := anthropic.MessageNewParams{\n\t\tModel: model,\n\t\tMaxTokens: 1024,\n\t\tMessages: []anthropic.MessageParam{\n\t\t\tanthropic.NewUserMessage(anthropic.NewTextBlock(userPrompt)),\n\t\t},\n\t}\n\tif system != \"\" {\n\t\tparams.System = []anthropic.TextBlockParam{\n\t\t\t{Text: system},\n\t\t}\n\t}\n\n\tmessage, err := client.Messages.New(ctx, params)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"anthropic API call failed: %w\", err)\n\t}\n\n\t// Extract text from first TextBlock in response content.\n\tfor _, block := range message.Content {\n\t\tif v, ok := block.AsAny().(anthropic.TextBlock); ok {\n\t\t\treturn v.Text, string(message.StopReason), nil\n\t\t}\n\t}\n\n\treturn \"\", string(message.StopReason), fmt.Errorf(\"no text content in response\")\n}\n\n// InterCallDelay sleeps 250ms between API calls to avoid rate limits (D-19).\nfunc InterCallDelay() {\n\ttime.Sleep(250 * time.Millisecond)\n}\n"}}}}