mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
refactor(runner): rename openrouter package to llmclient
This commit is contained in:
1 parent
8eabaaa842
commit
3ca0e9a5b9
3 files changed
+23
-22
No files matched your search
@@ -13,7 +13,7 @@ import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/priyanshujain/sanderling/internal/openrouter"
|
||||
"github.com/priyanshujain/sanderling/internal/llmclient"
|
||||
"github.com/priyanshujain/sanderling/internal/trace"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
@@ -36,14 +36,14 @@ const (
|
||||
// candidates the system already enumerated; it never invents actions.
|
||||
const llmSystemPrompt = "You are exploring this app to surface bugs. Choose the most useful next action from the numbered candidates. Avoid repeating recent actions; prefer progress into new screens. Return only your ranked choices."
|
||||
|
||||
// llmSource selects each step's action with an OpenRouter model instead of the
|
||||
// seeded random pick. It replaces ONLY the pick: the candidate list, the input
|
||||
// llmSource selects each step's action with an OpenAI-compatible vision model
|
||||
// instead of the seeded random pick. It replaces ONLY the pick: the candidate list, the input
|
||||
// values, and action execution are all reused unchanged. The spec's JS setup
|
||||
// still runs first each tick (setup precedence), and the LLM drives once setup
|
||||
// yields nothing.
|
||||
type llmSource struct {
|
||||
verifier *verifier.Verifier
|
||||
client *openrouter.Client
|
||||
client *llmclient.Client
|
||||
model string
|
||||
logger *slog.Logger
|
||||
history *actionHistory
|
||||
@@ -126,17 +126,17 @@ func (s *llmSource) selectViaLLM(ctx context.Context) (verifier.Action, string,
|
||||
// buildRequest assembles the one-shot multimodal request: a system frame, the
|
||||
// numbered candidate list plus recent-action memory, and the downscaled
|
||||
// screenshot. The strict json_schema response format pins the ranked output.
|
||||
func (s *llmSource) buildRequest(candidates []verifier.ActionCandidate) openrouter.Request {
|
||||
userParts := []openrouter.ContentPart{openrouter.TextPart(s.userPrompt(candidates))}
|
||||
func (s *llmSource) buildRequest(candidates []verifier.ActionCandidate) llmclient.Request {
|
||||
userParts := []llmclient.ContentPart{llmclient.TextPart(s.userPrompt(candidates))}
|
||||
if screenshot := s.verifier.Screenshot(); len(screenshot) > 0 {
|
||||
if dataURL, ok := screenshotDataURL(screenshot, llmMaxImageEdge); ok {
|
||||
userParts = append(userParts, openrouter.ImagePart(dataURL))
|
||||
userParts = append(userParts, llmclient.ImagePart(dataURL))
|
||||
}
|
||||
}
|
||||
return openrouter.Request{
|
||||
return llmclient.Request{
|
||||
Model: s.model,
|
||||
Messages: []openrouter.Message{
|
||||
{Role: "system", Content: []openrouter.ContentPart{openrouter.TextPart(llmSystemPrompt)}},
|
||||
Messages: []llmclient.Message{
|
||||
{Role: "system", Content: []llmclient.ContentPart{llmclient.TextPart(llmSystemPrompt)}},
|
||||
{Role: "user", Content: userParts},
|
||||
},
|
||||
ResponseFormat: rankedResponseFormat(),
|
||||
@@ -166,10 +166,10 @@ func (s *llmSource) userPrompt(candidates []verifier.ActionCandidate) string {
|
||||
|
||||
// rankedResponseFormat is the strict structured-output schema: a short
|
||||
// reasoning string and a ranked list of candidate indices.
|
||||
func rankedResponseFormat() *openrouter.ResponseFormat {
|
||||
return &openrouter.ResponseFormat{
|
||||
func rankedResponseFormat() *llmclient.ResponseFormat {
|
||||
return &llmclient.ResponseFormat{
|
||||
Type: "json_schema",
|
||||
JSONSchema: openrouter.JSONSchema{
|
||||
JSONSchema: llmclient.JSONSchema{
|
||||
Name: "ranked_actions",
|
||||
Strict: true,
|
||||
Schema: map[string]any{
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/priyanshujain/sanderling/internal/hierarchy"
|
||||
"github.com/priyanshujain/sanderling/internal/openrouter"
|
||||
"github.com/priyanshujain/sanderling/internal/llmclient"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
|
||||
@@ -157,8 +157,8 @@ func newFakeOpenRouter(t *testing.T) *fakeOpenRouter {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
_ = json.Unmarshal(body, &fake.lastRequest)
|
||||
content, _ := json.Marshal(map[string]any{"reasoning": fake.reasoning, "ranked": fake.ranked})
|
||||
response, _ := json.Marshal(openrouter.Response{
|
||||
Choices: []openrouter.Choice{{Message: openrouter.ResponseMessage{Content: string(content)}}},
|
||||
response, _ := json.Marshal(llmclient.Response{
|
||||
Choices: []llmclient.Choice{{Message: llmclient.ResponseMessage{Content: string(content)}}},
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(response)
|
||||
@@ -171,9 +171,9 @@ func newLLMSource(t *testing.T, fake *fakeOpenRouter) (*llmSource, *verifier.Ver
|
||||
t.Helper()
|
||||
t.Setenv("OPENROUTER_API_KEY", "test-key")
|
||||
t.Setenv("OPENROUTER_BASE_URL", fake.server.URL)
|
||||
client, err := openrouter.New()
|
||||
client, err := llmclient.New()
|
||||
if err != nil {
|
||||
t.Fatalf("openrouter.New: %v", err)
|
||||
t.Fatalf("llmclient.New: %v", err)
|
||||
}
|
||||
|
||||
verifierInstance, err := verifier.New()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/priyanshujain/sanderling/internal/driver"
|
||||
"github.com/priyanshujain/sanderling/internal/openrouter"
|
||||
"github.com/priyanshujain/sanderling/internal/llmclient"
|
||||
"github.com/priyanshujain/sanderling/internal/verifier"
|
||||
)
|
||||
|
||||
@@ -65,15 +65,16 @@ func (s webSource) ExtractorOverrides(ctx context.Context) (map[int]json.RawMess
|
||||
// pickSources selects the runtime's action and extractor sources ONCE at setup
|
||||
// from the driver's capabilities and the spec, so the step loop never
|
||||
// type-asserts. When the spec selected the LLM action backend (actions =
|
||||
// llm({...})) it constructs the OpenRouter client and returns an llmSource for
|
||||
// selection while extractor overrides still come from the goja path.
|
||||
// llm({...})) it constructs the chat-completions client and returns an
|
||||
// llmSource for selection while extractor overrides still come from the goja
|
||||
// path.
|
||||
func pickSources(options Options) (ActionSource, ExtractorSource, error) {
|
||||
if web, ok := options.Driver.(driver.WebDriver); ok {
|
||||
source := webSource{web: web}
|
||||
return source, source, nil
|
||||
}
|
||||
if config, ok := options.Verifier.LLMConfig(); ok {
|
||||
client, err := openrouter.New()
|
||||
client, err := llmclient.New()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("llm action backend: %w", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user