Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add database tables and API routes for agentic chat feature#17570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
johnstcn merged 18 commits intomainfromcj/rebase/chat
May 2, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
18 commits
Select commitHold shift + click to select a range
f762a76
Initial chat schema
kylecarbsApr 9, 2025
dd506e6
And we have chat!
kylecarbsApr 9, 2025
d848a94
Add more MCP stuff
kylecarbsApr 10, 2025
1134736
Add tools
kylecarbsApr 10, 2025
6a583e0
Add tool invocation stories stuff
kylecarbsApr 10, 2025
725d738
Add more tools
kylecarbsApr 16, 2025
9068d8b
Improve chat
kylecarbsApr 19, 2025
10b534c
chore: make gen, fmt, lint
johnstcnApr 25, 2025
3faa0b4
site: remove chat-related changes for separate PR
johnstcnApr 28, 2025
7a5c2d2
coderd/rbac: update chat rbac
johnstcnApr 29, 2025
d3f1cf4
coderd/database: only generate ID at database side, order messages by…
johnstcnApr 30, 2025
d9aab39
fix(coderd/database): fix down migration and add fixture
johnstcnApr 30, 2025
8071f9c
chore: add experiment agentic-chat, add tests for chat API routes
johnstcnMay 1, 2025
40c3d5e
chore: update kylecarbs/aisdk-go to v0.0.8
johnstcnMay 2, 2025
3cf8f60
fix(coderd/ai): support overriding base URL for OpenAI provider, requ…
johnstcnMay 2, 2025
ccefabe
fix(coderd): avoid serializing null message content in postChatMessages
johnstcnMay 2, 2025
05fa48d
fix(coderd): chat: avoid panic if no messages are accumulated
johnstcnMay 2, 2025
8481ad5
fix(coderd): chat: do not pass tools to model for generating chat title
johnstcnMay 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletionscli/server.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,6 +61,7 @@ import (
"github.com/coder/serpent"
"github.com/coder/wgtunnel/tunnelsdk"

"github.com/coder/coder/v2/coderd/ai"
"github.com/coder/coder/v2/coderd/entitlements"
"github.com/coder/coder/v2/coderd/notifications/reports"
"github.com/coder/coder/v2/coderd/runtimeconfig"
Expand DownExpand Up@@ -610,6 +611,22 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
)
}

aiProviders,err:=ReadAIProvidersFromEnv(os.Environ())
iferr!=nil {
returnxerrors.Errorf("read ai providers from env: %w",err)
}
vals.AI.Value.Providers=append(vals.AI.Value.Providers,aiProviders...)
for_,provider:=rangeaiProviders {
logger.Debug(
ctx,"loaded ai provider",
slog.F("type",provider.Type),
)
}
languageModels,err:=ai.ModelsFromConfig(ctx,vals.AI.Value.Providers)
iferr!=nil {
returnxerrors.Errorf("create language models: %w",err)
}

realIPConfig,err:=httpmw.ParseRealIPConfig(vals.ProxyTrustedHeaders,vals.ProxyTrustedOrigins)
iferr!=nil {
returnxerrors.Errorf("parse real ip config: %w",err)
Expand DownExpand Up@@ -640,6 +657,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
CacheDir:cacheDir,
GoogleTokenValidator:googleTokenValidator,
ExternalAuthConfigs:externalAuthConfigs,
LanguageModels:languageModels,
RealIPConfig:realIPConfig,
SSHKeygenAlgorithm:sshKeygenAlgorithm,
TracerProvider:tracerProvider,
Expand DownExpand Up@@ -2621,6 +2639,77 @@ func redirectHTTPToHTTPSDeprecation(ctx context.Context, logger slog.Logger, inv
}
}

funcReadAIProvidersFromEnv(environ []string) ([]codersdk.AIProviderConfig,error) {
// The index numbers must be in-order.
sort.Strings(environ)

varproviders []codersdk.AIProviderConfig
for_,v:=rangeserpent.ParseEnviron(environ,"CODER_AI_PROVIDER_") {
tokens:=strings.SplitN(v.Name,"_",2)
iflen(tokens)!=2 {
returnnil,xerrors.Errorf("invalid env var: %s",v.Name)
}

providerNum,err:=strconv.Atoi(tokens[0])
iferr!=nil {
returnnil,xerrors.Errorf("parse number: %s",v.Name)
}

varprovider codersdk.AIProviderConfig
switch {
caselen(providers)<providerNum:
returnnil,xerrors.Errorf(
"provider num %v skipped: %s",
len(providers),
v.Name,
)
caselen(providers)==providerNum:
// At the next next provider.
providers=append(providers,provider)
caselen(providers)==providerNum+1:
// At the current provider.
provider=providers[providerNum]
}

key:=tokens[1]
switchkey {
case"TYPE":
provider.Type=v.Value
case"API_KEY":
provider.APIKey=v.Value
case"BASE_URL":
provider.BaseURL=v.Value
case"MODELS":
provider.Models=strings.Split(v.Value,",")
}
providers[providerNum]=provider
}
for_,envVar:=rangeenviron {
tokens:=strings.SplitN(envVar,"=",2)
iflen(tokens)!=2 {
continue
}
switchtokens[0] {
case"OPENAI_API_KEY":
providers=append(providers, codersdk.AIProviderConfig{
Type:"openai",
APIKey:tokens[1],
})
case"ANTHROPIC_API_KEY":
providers=append(providers, codersdk.AIProviderConfig{
Type:"anthropic",
APIKey:tokens[1],
})
case"GOOGLE_API_KEY":
providers=append(providers, codersdk.AIProviderConfig{
Type:"google",
APIKey:tokens[1],
})
}
}
returnproviders,nil
}

// ReadExternalAuthProvidersFromEnv is provided for compatibility purposes with
// the viper CLI.
funcReadExternalAuthProvidersFromEnv(environ []string) ([]codersdk.ExternalAuthConfig,error) {
Expand Down
3 changes: 3 additions & 0 deletionscli/testdata/server-config.yaml.golden
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -519,6 +519,9 @@ client:
# Support links to display in the top right drop down menu.
# (default: <unset>, type: struct[[]codersdk.LinkConfig])
supportLinks: []
# Configure AI providers.
# (default: <unset>, type: struct[codersdk.AIConfig])
ai: {}
# External Authentication providers.
# (default: <unset>, type: struct[[]codersdk.ExternalAuthConfig])
externalAuthProviders: []
Expand Down
167 changes: 167 additions & 0 deletionscoderd/ai/ai.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
package ai

import (
"context"

"github.com/anthropics/anthropic-sdk-go"
anthropicoption"github.com/anthropics/anthropic-sdk-go/option"
"github.com/kylecarbs/aisdk-go"
"github.com/openai/openai-go"
openaioption"github.com/openai/openai-go/option"
"golang.org/x/xerrors"
"google.golang.org/genai"

"github.com/coder/coder/v2/codersdk"
)

typeLanguageModelstruct {
codersdk.LanguageModel
StreamFuncStreamFunc
}

typeStreamOptionsstruct {
SystemPromptstring
Modelstring
Messages []aisdk.Message
Thinkingbool
Tools []aisdk.Tool
}

typeStreamFuncfunc(ctx context.Context,optionsStreamOptions) (aisdk.DataStream,error)

// LanguageModels is a map of language model ID to language model.
typeLanguageModelsmap[string]LanguageModel

funcModelsFromConfig(ctx context.Context,configs []codersdk.AIProviderConfig) (LanguageModels,error) {
models:=make(LanguageModels)

for_,config:=rangeconfigs {
varstreamFuncStreamFunc

switchconfig.Type {
case"openai":
opts:= []openaioption.RequestOption{
openaioption.WithAPIKey(config.APIKey),
}
ifconfig.BaseURL!="" {
opts=append(opts,openaioption.WithBaseURL(config.BaseURL))
}
client:=openai.NewClient(opts...)
streamFunc=func(ctx context.Context,optionsStreamOptions) (aisdk.DataStream,error) {
openaiMessages,err:=aisdk.MessagesToOpenAI(options.Messages)
iferr!=nil {
returnnil,err
}
tools:=aisdk.ToolsToOpenAI(options.Tools)
ifoptions.SystemPrompt!="" {
openaiMessages=append([]openai.ChatCompletionMessageParamUnion{
openai.SystemMessage(options.SystemPrompt),
},openaiMessages...)
}

returnaisdk.OpenAIToDataStream(client.Chat.Completions.NewStreaming(ctx, openai.ChatCompletionNewParams{
Messages:openaiMessages,
Model:options.Model,
Tools:tools,
MaxTokens:openai.Int(8192),
})),nil
}
ifconfig.Models==nil {
models,err:=client.Models.List(ctx)
iferr!=nil {
returnnil,err
}
config.Models=make([]string,len(models.Data))
fori,model:=rangemodels.Data {
config.Models[i]=model.ID
}
}
case"anthropic":
client:=anthropic.NewClient(anthropicoption.WithAPIKey(config.APIKey))
streamFunc=func(ctx context.Context,optionsStreamOptions) (aisdk.DataStream,error) {
anthropicMessages,systemMessage,err:=aisdk.MessagesToAnthropic(options.Messages)
iferr!=nil {
returnnil,err
}
ifoptions.SystemPrompt!="" {
systemMessage= []anthropic.TextBlockParam{
*anthropic.NewTextBlock(options.SystemPrompt).OfRequestTextBlock,
}
}
returnaisdk.AnthropicToDataStream(client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
Messages:anthropicMessages,
Model:options.Model,
System:systemMessage,
Tools:aisdk.ToolsToAnthropic(options.Tools),
MaxTokens:8192,
})),nil
}
ifconfig.Models==nil {
models,err:=client.Models.List(ctx, anthropic.ModelListParams{})
iferr!=nil {
returnnil,err
}
config.Models=make([]string,len(models.Data))
fori,model:=rangemodels.Data {
config.Models[i]=model.ID
}
}
case"google":
client,err:=genai.NewClient(ctx,&genai.ClientConfig{
APIKey:config.APIKey,
Backend:genai.BackendGeminiAPI,
})
iferr!=nil {
returnnil,err
}
streamFunc=func(ctx context.Context,optionsStreamOptions) (aisdk.DataStream,error) {
googleMessages,err:=aisdk.MessagesToGoogle(options.Messages)
iferr!=nil {
returnnil,err
}
tools,err:=aisdk.ToolsToGoogle(options.Tools)
iferr!=nil {
returnnil,err
}
varsystemInstruction*genai.Content
ifoptions.SystemPrompt!="" {
systemInstruction=&genai.Content{
Parts: []*genai.Part{
genai.NewPartFromText(options.SystemPrompt),
},
Role:"model",
}
}
returnaisdk.GoogleToDataStream(client.Models.GenerateContentStream(ctx,options.Model,googleMessages,&genai.GenerateContentConfig{
SystemInstruction:systemInstruction,
Tools:tools,
})),nil
}
ifconfig.Models==nil {
models,err:=client.Models.List(ctx,&genai.ListModelsConfig{})
iferr!=nil {
returnnil,err
}
config.Models=make([]string,len(models.Items))
fori,model:=rangemodels.Items {
config.Models[i]=model.Name
}
}
default:
returnnil,xerrors.Errorf("unsupported model type: %s",config.Type)
}

for_,model:=rangeconfig.Models {
models[model]=LanguageModel{
LanguageModel: codersdk.LanguageModel{
ID:model,
DisplayName:model,
Provider:config.Type,
},
StreamFunc:streamFunc,
}
}
}

returnmodels,nil
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp