mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add workspace awareness system message on chat creation (#23213)
When a chat is created via `chatd`, a system message is now inserted informing the model whether the chat was created with or without a workspace. **With workspace:** > This chat is attached to a workspace. You can use workspace tools like execute, read_file, write_file, etc. **Without workspace:** > There is no workspace associated with this chat yet. Create one using the create_workspace tool before using workspace tools like execute, read_file, write_file, etc. This is a model-only visibility system message (not shown to users) that helps the model understand its available capabilities upfront — particularly important for subagents spawned without a workspace, which previously would attempt to use workspace tools and fail. **Changes:** - `coderd/chatd/chatd.go`: Added workspace awareness constants and inserted the system message in `CreateChat` after the system prompt, before the initial user message. - `coderd/chatd/chatd_test.go`: Added `TestCreateChatInsertsWorkspaceAwarenessMessage` with sub-tests for both with-workspace and without-workspace cases.
This commit is contained in:
@@ -492,6 +492,43 @@ func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.C
|
||||
}
|
||||
}
|
||||
|
||||
var workspaceAwareness string
|
||||
if opts.WorkspaceID.Valid {
|
||||
workspaceAwareness = "This chat is attached to a workspace. You can use workspace tools like execute, read_file, write_file, etc."
|
||||
} else {
|
||||
workspaceAwareness = "There is no workspace associated with this chat yet. Create one using the create_workspace tool before using workspace tools like execute, read_file, write_file, etc."
|
||||
}
|
||||
workspaceAwarenessContent, err := chatprompt.MarshalParts([]codersdk.ChatMessagePart{
|
||||
codersdk.ChatMessageText(workspaceAwareness),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal workspace awareness: %w", err)
|
||||
}
|
||||
_, err = tx.InsertChatMessage(ctx, database.InsertChatMessageParams{
|
||||
ChatID: insertedChat.ID,
|
||||
CreatedBy: uuid.NullUUID{},
|
||||
ModelConfigID: uuid.NullUUID{
|
||||
UUID: opts.ModelConfigID,
|
||||
Valid: true,
|
||||
},
|
||||
Role: database.ChatMessageRoleSystem,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
Content: workspaceAwarenessContent,
|
||||
Visibility: database.ChatMessageVisibilityModel,
|
||||
InputTokens: sql.NullInt64{},
|
||||
OutputTokens: sql.NullInt64{},
|
||||
TotalTokens: sql.NullInt64{},
|
||||
ReasoningTokens: sql.NullInt64{},
|
||||
CacheCreationTokens: sql.NullInt64{},
|
||||
CacheReadTokens: sql.NullInt64{},
|
||||
ContextLimit: sql.NullInt64{},
|
||||
Compressed: sql.NullBool{},
|
||||
TotalCostMicros: sql.NullInt64{},
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace awareness message: %w", err)
|
||||
}
|
||||
|
||||
userContent, err := chatprompt.MarshalParts(opts.InitialUserContent)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal initial user content: %w", err)
|
||||
|
||||
@@ -591,6 +591,97 @@ func TestEditMessageUpdatesAndTruncatesAndClearsQueue(t *testing.T) {
|
||||
require.False(t, chatFromDB.WorkerID.Valid)
|
||||
}
|
||||
|
||||
func TestCreateChatInsertsWorkspaceAwarenessMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("WithWorkspace", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, model := seedChatDependencies(ctx, t, db)
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
tpl := dbgen.Template(t, db, database.Template{
|
||||
CreatedBy: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
ActiveVersionID: tv.ID,
|
||||
})
|
||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: tpl.ID,
|
||||
})
|
||||
|
||||
chat, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: workspace.ID, Valid: true},
|
||||
Title: "test-with-workspace",
|
||||
ModelConfigID: model.ID,
|
||||
InitialUserContent: []codersdk.ChatMessagePart{codersdk.ChatMessageText("hello")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := db.GetChatMessagesForPromptByChatID(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var workspaceMsg *database.ChatMessage
|
||||
for _, msg := range messages {
|
||||
if msg.Role == database.ChatMessageRoleSystem {
|
||||
content := string(msg.Content.RawMessage)
|
||||
if strings.Contains(content, "attached to a workspace") {
|
||||
workspaceMsg = &msg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
require.NotNil(t, workspaceMsg, "workspace awareness system message should exist")
|
||||
require.Equal(t, database.ChatMessageRoleSystem, workspaceMsg.Role)
|
||||
require.Equal(t, database.ChatMessageVisibilityModel, workspaceMsg.Visibility)
|
||||
})
|
||||
|
||||
t.Run("WithoutWorkspace", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, model := seedChatDependencies(ctx, t, db)
|
||||
|
||||
chat, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OwnerID: user.ID,
|
||||
Title: "test-without-workspace",
|
||||
ModelConfigID: model.ID,
|
||||
InitialUserContent: []codersdk.ChatMessagePart{codersdk.ChatMessageText("hello")},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := db.GetChatMessagesForPromptByChatID(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var workspaceMsg *database.ChatMessage
|
||||
for _, msg := range messages {
|
||||
if msg.Role == database.ChatMessageRoleSystem {
|
||||
content := string(msg.Content.RawMessage)
|
||||
if strings.Contains(content, "no workspace associated") {
|
||||
workspaceMsg = &msg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
require.NotNil(t, workspaceMsg, "workspace awareness system message should exist")
|
||||
require.Equal(t, database.ChatMessageRoleSystem, workspaceMsg.Role)
|
||||
require.Equal(t, database.ChatMessageVisibilityModel, workspaceMsg.Visibility)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateChatRejectsWhenUsageLimitReached(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user