From 2bc498aa73b849d7a1d8bb3fc89b8f1666757004 Mon Sep 17 00:00:00 2001 From: OpenWaygate <27287694+OpenWaygate@users.noreply.github.com> Date: Sat, 29 Aug 2026 16:44:06 +0800 Subject: [PATCH] :sparkles: Support OpenAI(compatible) LLM providers #130 --- cmd/agent/BUILD.bazel | 1 + cmd/agent/agent.go | 95 +++++++++++++++++++++++++++---------------- go.mod | 5 +++ go.sum | 15 +++++++ 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/cmd/agent/BUILD.bazel b/cmd/agent/BUILD.bazel index cfa50ca..9d33743 100644 --- a/cmd/agent/BUILD.bazel +++ b/cmd/agent/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "@org_golang_google_adk_v2//memory", "@org_golang_google_adk_v2//model", "@org_golang_google_adk_v2//model/gemini", + "@org_golang_google_adk_v2//model/openaimodel", "@org_golang_google_adk_v2//session", "@org_golang_google_adk_v2//tool", "@org_golang_google_adk_v2//tool/geminitool", diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index f423a53..992f37a 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -22,6 +22,7 @@ import ( "google.golang.org/adk/v2/memory" "google.golang.org/adk/v2/model" "google.golang.org/adk/v2/model/gemini" + "google.golang.org/adk/v2/model/openaimodel" "google.golang.org/adk/v2/session" "google.golang.org/adk/v2/tool" "google.golang.org/adk/v2/tool/geminitool" @@ -34,25 +35,38 @@ import ( const ( short = "Start an agent to automate YouTube workflows" long = "Start an agent to automate YouTube workflows." - example = `# console mode -yutu agent --args "console" --model "google:gemini-3.7-flash" --api-key "YOUR_KEY" -# web mode -yutu agent --args "web api a2a webui" --model "google:gemini-3.7-flash" --api-key "YOUR_KEY"` + example = `# Gemini (default) +yutu agent --provider google --model gemini-3.7-flash --api-key "YOUR_KEY" +# OpenAI +yutu agent --provider openai --model gpt-5.6-terra +# OpenAI-compatible endpoint (DeepSeek, Ollama, vLLM, etc.) +yutu agent --provider openai-compatible --model deepseek-v4-flash --base-url "https://api.deepseek.com/" +# Custom Gemini endpoint +yutu agent --provider google --model gemini-3.7-flash --base-url "https://custom-endpoint.example.com/"` argsUsage = "Launcher arguments as a single string" - modelUsage = "Model in provider:modelName format" + providerUsage = "LLM provider (google, openai, openai-compatible)" + modelUsage = "Model name" apiKeyUsage = "API key for the model provider" + baseURLUsage = "Base URL for the model provider's API endpoint" instructionUsage = "Override the built-in agent instruction" + agentDescription = "YouTube growth strategist and workflow assistant — retrieve, create, update, and delete YouTube content." - agentDescription = "YouTube growth strategist and workflow assistant — retrieves, creates, updates, and deletes YouTube content." - errInvalidModelSpec = "invalid model spec %q: expected provider:modelName, e.g. google:gemini-3.7-flash" - errUnsupportedProvider = "unsupported provider %q: only \"google\" is supported" + errUnsupportedProvider = "unsupported provider %q: supported providers are google, openai, openai-compatible" + errModelConfig = "model configuration error" + errMCPConnect = "failed to connect to MCP server" + errMCPToolSet = "failed to create MCP tool set" + errSkillToolset = "failed to create skill toolset" + errBuildAgent = "failed to build agent" + errLaunchAgent = "failed to launch agent" ) var ( - launcherArgs string - modelSpec string - apiKey string - instructionOverride string + launcherArgs string + provider string + modelName string + apiKey string + baseURL string + inst string //go:embed INSTRUCTION.md instruction string @@ -81,27 +95,32 @@ func init() { cmd.RootCmd.AddCommand(agentCmd) agentCmd.Flags().StringVarP(&launcherArgs, "args", "a", "console", argsUsage) agentCmd.Flags().StringVarP( - &modelSpec, "model", "m", "google:gemini-3.7-flash", modelUsage, + &provider, "provider", "p", "google", providerUsage, + ) + agentCmd.Flags().StringVarP( + &modelName, "model", "m", "gemini-3.7-flash", modelUsage, ) agentCmd.Flags().StringVar(&apiKey, "api-key", "", apiKeyUsage) - agentCmd.Flags().StringVarP( - &instructionOverride, "instruction", "i", "", instructionUsage, - ) + agentCmd.Flags().StringVar(&baseURL, "base-url", "", baseURLUsage) + agentCmd.Flags().StringVarP(&inst, "instruction", "i", "", instructionUsage) } -func newModel(ctx context.Context, spec string) (model.LLM, error) { - parts := strings.SplitN(spec, ":", 2) - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return nil, fmt.Errorf(errInvalidModelSpec, spec) - } - - provider, modelName := parts[0], parts[1] +func newModel(ctx context.Context) (model.LLM, error) { switch provider { case "google": - return gemini.NewModel( - ctx, modelName, &genai.ClientConfig{ + cfg := &genai.ClientConfig{ + APIKey: apiKey, + Backend: genai.BackendGeminiAPI, + } + if baseURL != "" { + cfg.HTTPOptions.BaseURL = baseURL + } + return gemini.NewModel(ctx, modelName, cfg) + case "openai", "openai-compatible": + return openaimodel.NewModel( + ctx, modelName, &openaimodel.ClientConfig{ APIKey: apiKey, - Backend: genai.BackendGeminiAPI, + BaseURL: baseURL, }, ) default: @@ -121,33 +140,37 @@ func newSkillToolset(ctx context.Context) (tool.Toolset, error) { func buildAgent( m model.LLM, mcpToolSet, skillToolset tool.Toolset, ) (agent.Agent, error) { + var tools []tool.Tool + if provider == "google" { + tools = append(tools, geminitool.GoogleSearch{}) + } return llmagent.New( llmagent.Config{ Name: "Miffy", Model: m, Description: agentDescription, Instruction: instruction, - Tools: []tool.Tool{geminitool.GoogleSearch{}}, + Tools: tools, Toolsets: []tool.Toolset{mcpToolSet, skillToolset}, }, ) } func launch(ctx context.Context, writer io.Writer, args []string) { - if instructionOverride != "" { - instruction = instructionOverride + if inst != "" { + instruction = inst } - m, err := newModel(ctx, modelSpec) + m, err := newModel(ctx) if err != nil { - slog.ErrorContext(ctx, "model configuration error", "error", err) + slog.ErrorContext(ctx, errModelConfig, "error", err) os.Exit(1) } clientTransport, serverTransport := mcp.NewInMemoryTransports() _, err = cmd.Server.Connect(ctx, serverTransport, nil) if err != nil { - slog.ErrorContext(ctx, "failed to connect to MCP server", "error", err) + slog.ErrorContext(ctx, errMCPConnect, "error", err) os.Exit(1) } @@ -157,19 +180,19 @@ func launch(ctx context.Context, writer io.Writer, args []string) { }, ) if err != nil { - slog.ErrorContext(ctx, "failed to create MCP tool set", "error", err) + slog.ErrorContext(ctx, errMCPToolSet, "error", err) os.Exit(1) } skillToolset, err := newSkillToolset(ctx) if err != nil { - slog.ErrorContext(ctx, "failed to create skill toolset", "error", err) + slog.ErrorContext(ctx, errSkillToolset, "error", err) os.Exit(1) } miffy, err := buildAgent(m, mcpToolSet, skillToolset) if err != nil { - slog.ErrorContext(ctx, "failed to build agent", "error", err) + slog.ErrorContext(ctx, errBuildAgent, "error", err) os.Exit(1) } @@ -183,7 +206,7 @@ func launch(ctx context.Context, writer io.Writer, args []string) { } l := full.NewLauncher() if err := l.Execute(ctx, config, args); err != nil { - slog.ErrorContext(ctx, "failed to launch agent", "error", err) + slog.ErrorContext(ctx, errLaunchAgent, "error", err) _, _ = fmt.Fprintln(writer, l.CommandLineSyntax()) os.Exit(1) } diff --git a/go.mod b/go.mod index faeb567..7184bc1 100644 --- a/go.mod +++ b/go.mod @@ -40,11 +40,16 @@ require ( github.com/mattn/go-runewidth v0.0.28 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/openai/openai-go/v3 v3.49.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/segmentio/encoding v0.5.4 // indirect github.com/tc-hib/go-winres v0.3.3 // indirect github.com/tc-hib/winres v0.2.1 // 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 github.com/urfave/cli/v2 v2.25.7 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect diff --git a/go.sum b/go.sum index e33d5f4..aa8bdab 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,10 @@ github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6 github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/openai/openai-go/v3 v3.49.0 h1:/BObwLKdgHJ3AbJ7AXK8BRDfjuTAV+eTmEQFbG86wnQ= +github.com/openai/openai-go/v3 v3.49.0/go.mod h1:nCqcbgPr2jYDV8wiCWPA2tRL56mWXdb6QavBmBwzSis= +github.com/openai/openai-go/v3 v3.54.0 h1:qeNvIpFB/wzX7pI8USN1eruvzCS+XqOZ8mx73lGF6Lg= +github.com/openai/openai-go/v3 v3.54.0/go.mod h1:ufI1+K+t0ijRB3gk8eztiw1crcDpsBuxRQL4sbLIrts= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -98,6 +102,17 @@ github.com/tc-hib/go-winres v0.3.3 h1:DQ50qlvDVhqrDOY0svTxZFZWfKWtZtfnXXHPCw6lqh github.com/tc-hib/go-winres v0.3.3/go.mod h1:5NGzOtuvjSqnpIEi2o1h48MKZzP9olvrf+PeY2t1uoA= github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA= github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=