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

Commit6553771

Browse files
feat(coderd): generate task names based on their prompt (#19335)
Closes#18159If an Anthropic API key is available, we call out to Claude to generatea task name based on the user-provided prompt instead of our random namegenerator.
1 parentc429020 commit6553771

File tree

4 files changed

+210
-2
lines changed

4 files changed

+210
-2
lines changed

‎coderd/aitasks.go‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import (
1010

1111
"github.com/google/uuid"
1212

13+
"cdr.dev/slog"
14+
1315
"github.com/coder/coder/v2/coderd/audit"
1416
"github.com/coder/coder/v2/coderd/database"
1517
"github.com/coder/coder/v2/coderd/httpapi"
1618
"github.com/coder/coder/v2/coderd/httpmw"
1719
"github.com/coder/coder/v2/coderd/rbac"
20+
"github.com/coder/coder/v2/coderd/taskname"
1821
"github.com/coder/coder/v2/codersdk"
1922
)
2023

@@ -104,8 +107,20 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
104107
return
105108
}
106109

110+
taskName:=req.Name
111+
ifanthropicAPIKey:=taskname.GetAnthropicAPIKeyFromEnv();anthropicAPIKey!="" {
112+
anthropicModel:=taskname.GetAnthropicModelFromEnv()
113+
114+
generatedName,err:=taskname.Generate(ctx,req.Prompt,taskname.WithAPIKey(anthropicAPIKey),taskname.WithModel(anthropicModel))
115+
iferr!=nil {
116+
api.Logger.Error(ctx,"unable to generate task name",slog.Error(err))
117+
}else {
118+
taskName=generatedName
119+
}
120+
}
121+
107122
createReq:= codersdk.CreateWorkspaceRequest{
108-
Name:req.Name,
123+
Name:taskName,
109124
TemplateVersionID:req.TemplateVersionID,
110125
TemplateVersionPresetID:req.TemplateVersionPresetID,
111126
RichParameterValues: []codersdk.WorkspaceBuildParameter{

‎coderd/taskname/taskname.go‎

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package taskname
2+
3+
import (
4+
"context"
5+
"io"
6+
"os"
7+
8+
"github.com/anthropics/anthropic-sdk-go"
9+
anthropicoption"github.com/anthropics/anthropic-sdk-go/option"
10+
"golang.org/x/xerrors"
11+
12+
"github.com/coder/aisdk-go"
13+
"github.com/coder/coder/v2/codersdk"
14+
)
15+
16+
const (
17+
defaultModel=anthropic.ModelClaude3_5HaikuLatest
18+
systemPrompt=`Generate a short workspace name from this AI task prompt.
19+
20+
Requirements:
21+
- Only lowercase letters, numbers, and hyphens
22+
- Start with "task-"
23+
- End with a random number between 0-99
24+
- Maximum 32 characters total
25+
- Descriptive of the main task
26+
27+
Examples:
28+
- "Help me debug a Python script" → "task-python-debug-12"
29+
- "Create a React dashboard component" → "task-react-dashboard-93"
30+
- "Analyze sales data from Q3" → "task-analyze-q3-sales-37"
31+
- "Set up CI/CD pipeline" → "task-setup-cicd-44"
32+
33+
If you cannot create a suitable name:
34+
- Respond with "task-unnamed"
35+
- Do not end with a random number`
36+
)
37+
38+
var (
39+
ErrNoAPIKey=xerrors.New("no api key provided")
40+
ErrNoNameGenerated=xerrors.New("no task name generated")
41+
)
42+
43+
typeoptionsstruct {
44+
apiKeystring
45+
model anthropic.Model
46+
}
47+
48+
typeOptionfunc(o*options)
49+
50+
funcWithAPIKey(apiKeystring)Option {
51+
returnfunc(o*options) {
52+
o.apiKey=apiKey
53+
}
54+
}
55+
56+
funcWithModel(model anthropic.Model)Option {
57+
returnfunc(o*options) {
58+
o.model=model
59+
}
60+
}
61+
62+
funcGetAnthropicAPIKeyFromEnv()string {
63+
returnos.Getenv("ANTHROPIC_API_KEY")
64+
}
65+
66+
funcGetAnthropicModelFromEnv() anthropic.Model {
67+
returnanthropic.Model(os.Getenv("ANTHROPIC_MODEL"))
68+
}
69+
70+
funcGenerate(ctx context.Context,promptstring,opts...Option) (string,error) {
71+
o:=options{}
72+
for_,opt:=rangeopts {
73+
opt(&o)
74+
}
75+
76+
ifo.model=="" {
77+
o.model=defaultModel
78+
}
79+
ifo.apiKey=="" {
80+
return"",ErrNoAPIKey
81+
}
82+
83+
conversation:= []aisdk.Message{
84+
{
85+
Role:"system",
86+
Parts: []aisdk.Part{{
87+
Type:aisdk.PartTypeText,
88+
Text:systemPrompt,
89+
}},
90+
},
91+
{
92+
Role:"user",
93+
Parts: []aisdk.Part{{
94+
Type:aisdk.PartTypeText,
95+
Text:prompt,
96+
}},
97+
},
98+
}
99+
100+
anthropicOptions:=anthropic.DefaultClientOptions()
101+
anthropicOptions=append(anthropicOptions,anthropicoption.WithAPIKey(o.apiKey))
102+
anthropicClient:=anthropic.NewClient(anthropicOptions...)
103+
104+
stream,err:=anthropicDataStream(ctx,anthropicClient,o.model,conversation)
105+
iferr!=nil {
106+
return"",xerrors.Errorf("create anthropic data stream: %w",err)
107+
}
108+
109+
varacc aisdk.DataStreamAccumulator
110+
stream=stream.WithAccumulator(&acc)
111+
112+
iferr:=stream.Pipe(io.Discard);err!=nil {
113+
return"",xerrors.Errorf("pipe data stream")
114+
}
115+
116+
iflen(acc.Messages())==0 {
117+
return"",ErrNoNameGenerated
118+
}
119+
120+
generatedName:=acc.Messages()[0].Content
121+
122+
iferr:=codersdk.NameValid(generatedName);err!=nil {
123+
return"",xerrors.Errorf("generated name %v not valid: %w",generatedName,err)
124+
}
125+
126+
ifgeneratedName=="task-unnamed" {
127+
return"",ErrNoNameGenerated
128+
}
129+
130+
returngeneratedName,nil
131+
}
132+
133+
funcanthropicDataStream(ctx context.Context,client anthropic.Client,model anthropic.Model,input []aisdk.Message) (aisdk.DataStream,error) {
134+
messages,system,err:=aisdk.MessagesToAnthropic(input)
135+
iferr!=nil {
136+
returnnil,xerrors.Errorf("convert messages to anthropic format: %w",err)
137+
}
138+
139+
returnaisdk.AnthropicToDataStream(client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
140+
Model:model,
141+
MaxTokens:24,
142+
System:system,
143+
Messages:messages,
144+
})),nil
145+
}

‎coderd/taskname/taskname_test.go‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package taskname_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/v2/coderd/taskname"
10+
"github.com/coder/coder/v2/codersdk"
11+
"github.com/coder/coder/v2/testutil"
12+
)
13+
14+
const (
15+
anthropicEnvVar="ANTHROPIC_API_KEY"
16+
)
17+
18+
funcTestGenerateTaskName(t*testing.T) {
19+
t.Parallel()
20+
21+
t.Run("Fallback",func(t*testing.T) {
22+
t.Parallel()
23+
24+
ctx:=testutil.Context(t,testutil.WaitShort)
25+
26+
name,err:=taskname.Generate(ctx,"Some random prompt")
27+
require.ErrorIs(t,err,taskname.ErrNoAPIKey)
28+
require.Equal(t,"",name)
29+
})
30+
31+
t.Run("Anthropic",func(t*testing.T) {
32+
t.Parallel()
33+
34+
apiKey:=os.Getenv(anthropicEnvVar)
35+
ifapiKey=="" {
36+
t.Skipf("Skipping test as %s not set",anthropicEnvVar)
37+
}
38+
39+
ctx:=testutil.Context(t,testutil.WaitShort)
40+
41+
name,err:=taskname.Generate(ctx,"Create a finance planning app",taskname.WithAPIKey(apiKey))
42+
require.NoError(t,err)
43+
require.NotEqual(t,"",name)
44+
45+
err=codersdk.NameValid(name)
46+
require.NoError(t,err,"name should be valid")
47+
})
48+
}

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ require (
477477
)
478478

479479
require (
480+
github.com/anthropics/anthropic-sdk-gov1.4.0
480481
github.com/brianvoe/gofakeit/v7v7.3.0
481482
github.com/coder/agentapi-sdk-gov0.0.0-20250505131810-560d1d88d225
482483
github.com/coder/aisdk-gov0.0.9
@@ -500,7 +501,6 @@ require (
500501
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metricv0.50.0// indirect
501502
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemappingv0.50.0// indirect
502503
github.com/Masterminds/semver/v3v3.3.1// indirect
503-
github.com/anthropics/anthropic-sdk-gov1.4.0// indirect
504504
github.com/aquasecurity/go-versionv0.0.1// indirect
505505
github.com/aquasecurity/trivyv0.58.2// indirect
506506
github.com/aws/aws-sdk-gov1.55.7// indirect

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp