mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: record model thoughts (#22676)
Depends on https://github.com/coder/aibridge/pull/203 Closes https://github.com/coder/internal/issues/1337 --------- Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
@@ -4497,6 +4497,13 @@ func (q *querier) InsertAIBridgeInterception(ctx context.Context, arg database.I
|
||||
return insert(q.log, q.auth, rbac.ResourceAibridgeInterception.WithOwner(arg.InitiatorID.String()), q.db.InsertAIBridgeInterception)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeModelThought(ctx context.Context, arg database.InsertAIBridgeModelThoughtParams) (database.AIBridgeModelThought, error) {
|
||||
if err := q.authorizeAIBridgeInterceptionAction(ctx, policy.ActionUpdate, arg.InterceptionID); err != nil {
|
||||
return database.AIBridgeModelThought{}, err
|
||||
}
|
||||
return q.db.InsertAIBridgeModelThought(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) (database.AIBridgeTokenUsage, error) {
|
||||
// All aibridge_token_usages records belong to the initiator of their associated interception.
|
||||
if err := q.authorizeAIBridgeInterceptionAction(ctx, policy.ActionUpdate, arg.InterceptionID); err != nil {
|
||||
|
||||
@@ -5167,6 +5167,17 @@ func (s *MethodTestSuite) TestAIBridge() {
|
||||
check.Args(params).Asserts(intc, policy.ActionCreate)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeModelThought", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
intID := uuid.UUID{2}
|
||||
intc := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: intID})
|
||||
db.EXPECT().GetAIBridgeInterceptionByID(gomock.Any(), intID).Return(intc, nil).AnyTimes() // Validation.
|
||||
|
||||
params := database.InsertAIBridgeModelThoughtParams{InterceptionID: intc.ID}
|
||||
expected := testutil.Fake(s.T(), faker, database.AIBridgeModelThought{InterceptionID: intc.ID})
|
||||
db.EXPECT().InsertAIBridgeModelThought(gomock.Any(), params).Return(expected, nil).AnyTimes()
|
||||
check.Args(params).Asserts(intc, policy.ActionUpdate)
|
||||
}))
|
||||
|
||||
s.Run("InsertAIBridgeTokenUsage", s.Mocked(func(db *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
intID := uuid.UUID{2}
|
||||
intc := testutil.Fake(s.T(), faker, database.AIBridgeInterception{ID: intID})
|
||||
|
||||
@@ -2951,6 +2951,14 @@ func (m queryMetricsStore) InsertAIBridgeInterception(ctx context.Context, arg d
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeModelThought(ctx context.Context, arg database.InsertAIBridgeModelThoughtParams) (database.AIBridgeModelThought, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertAIBridgeModelThought(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertAIBridgeModelThought").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "InsertAIBridgeModelThought").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) (database.AIBridgeTokenUsage, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertAIBridgeTokenUsage(ctx, arg)
|
||||
|
||||
@@ -5510,6 +5510,21 @@ func (mr *MockStoreMockRecorder) InsertAIBridgeInterception(ctx, arg any) *gomoc
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeInterception", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeInterception), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAIBridgeModelThought mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeModelThought(ctx context.Context, arg database.InsertAIBridgeModelThoughtParams) (database.AIBridgeModelThought, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertAIBridgeModelThought", ctx, arg)
|
||||
ret0, _ := ret[0].(database.AIBridgeModelThought)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertAIBridgeModelThought indicates an expected call of InsertAIBridgeModelThought.
|
||||
func (mr *MockStoreMockRecorder) InsertAIBridgeModelThought(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAIBridgeModelThought", reflect.TypeOf((*MockStore)(nil).InsertAIBridgeModelThought), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertAIBridgeTokenUsage mocks base method.
|
||||
func (m *MockStore) InsertAIBridgeTokenUsage(ctx context.Context, arg database.InsertAIBridgeTokenUsageParams) (database.AIBridgeTokenUsage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+11
@@ -1086,6 +1086,15 @@ COMMENT ON COLUMN aibridge_interceptions.thread_root_id IS 'The root interceptio
|
||||
|
||||
COMMENT ON COLUMN aibridge_interceptions.client_session_id IS 'The session ID supplied by the client (optional and not universally supported).';
|
||||
|
||||
CREATE TABLE aibridge_model_thoughts (
|
||||
interception_id uuid NOT NULL,
|
||||
content text NOT NULL,
|
||||
metadata jsonb,
|
||||
created_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_model_thoughts IS 'Audit log of model thinking in intercepted requests in AI Bridge';
|
||||
|
||||
CREATE TABLE aibridge_token_usages (
|
||||
id uuid NOT NULL,
|
||||
interception_id uuid NOT NULL,
|
||||
@@ -3562,6 +3571,8 @@ CREATE INDEX idx_aibridge_interceptions_thread_parent_id ON aibridge_interceptio
|
||||
|
||||
CREATE INDEX idx_aibridge_interceptions_thread_root_id ON aibridge_interceptions USING btree (thread_root_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_model_thoughts_interception_id ON aibridge_model_thoughts USING btree (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_interception_id ON aibridge_token_usages USING btree (interception_id);
|
||||
|
||||
CREATE INDEX idx_aibridge_token_usages_provider_response_id ON aibridge_token_usages USING btree (provider_response_id);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
DROP INDEX idx_aibridge_model_thoughts_interception_id;
|
||||
|
||||
DROP TABLE aibridge_model_thoughts;
|
||||
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE aibridge_model_thoughts (
|
||||
interception_id UUID NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
metadata jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE aibridge_model_thoughts IS 'Audit log of model thinking in intercepted requests in AI Bridge';
|
||||
|
||||
CREATE INDEX idx_aibridge_model_thoughts_interception_id ON aibridge_model_thoughts(interception_id);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
INSERT INTO
|
||||
aibridge_model_thoughts (
|
||||
interception_id,
|
||||
content,
|
||||
metadata,
|
||||
created_at
|
||||
)
|
||||
VALUES (
|
||||
'be003e1e-b38f-43bf-847d-928074dd0aa8', -- from 000370_aibridge.up.sql
|
||||
'The user is asking about their workspaces. I should use the coder_list_workspaces tool to retrieve this information.',
|
||||
'{"source": "commentary"}',
|
||||
'2025-09-15 12:45:19.123456+00'
|
||||
);
|
||||
@@ -3977,6 +3977,14 @@ type AIBridgeInterception struct {
|
||||
ClientSessionID sql.NullString `db:"client_session_id" json:"client_session_id"`
|
||||
}
|
||||
|
||||
// Audit log of model thinking in intercepted requests in AI Bridge
|
||||
type AIBridgeModelThought struct {
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
Content string `db:"content" json:"content"`
|
||||
Metadata pqtype.NullRawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// Audit log of tokens used by intercepted requests in AI Bridge
|
||||
type AIBridgeTokenUsage struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
||||
@@ -612,6 +612,7 @@ type sqlcQuerier interface {
|
||||
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error)
|
||||
GetWorkspacesForWorkspaceMetrics(ctx context.Context) ([]GetWorkspacesForWorkspaceMetricsRow, error)
|
||||
InsertAIBridgeInterception(ctx context.Context, arg InsertAIBridgeInterceptionParams) (AIBridgeInterception, error)
|
||||
InsertAIBridgeModelThought(ctx context.Context, arg InsertAIBridgeModelThoughtParams) (AIBridgeModelThought, error)
|
||||
InsertAIBridgeTokenUsage(ctx context.Context, arg InsertAIBridgeTokenUsageParams) (AIBridgeTokenUsage, error)
|
||||
InsertAIBridgeToolUsage(ctx context.Context, arg InsertAIBridgeToolUsageParams) (AIBridgeToolUsage, error)
|
||||
InsertAIBridgeUserPrompt(ctx context.Context, arg InsertAIBridgeUserPromptParams) (AIBridgeUserPrompt, error)
|
||||
|
||||
@@ -340,6 +340,11 @@ WITH
|
||||
WHERE started_at < $1::timestamp with time zone
|
||||
),
|
||||
-- CTEs are executed in order.
|
||||
model_thoughts AS (
|
||||
DELETE FROM aibridge_model_thoughts
|
||||
WHERE interception_id IN (SELECT id FROM to_delete)
|
||||
RETURNING 1
|
||||
),
|
||||
tool_usages AS (
|
||||
DELETE FROM aibridge_tool_usages
|
||||
WHERE interception_id IN (SELECT id FROM to_delete)
|
||||
@@ -361,6 +366,7 @@ WITH
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT (
|
||||
(SELECT COUNT(*) FROM model_thoughts) +
|
||||
(SELECT COUNT(*) FROM tool_usages) +
|
||||
(SELECT COUNT(*) FROM token_usages) +
|
||||
(SELECT COUNT(*) FROM user_prompts) +
|
||||
@@ -661,6 +667,39 @@ func (q *sqlQuerier) InsertAIBridgeInterception(ctx context.Context, arg InsertA
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertAIBridgeModelThought = `-- name: InsertAIBridgeModelThought :one
|
||||
INSERT INTO aibridge_model_thoughts (
|
||||
interception_id, content, metadata, created_at
|
||||
) VALUES (
|
||||
$1, $2, COALESCE($3::jsonb, '{}'::jsonb), $4
|
||||
)
|
||||
RETURNING interception_id, content, metadata, created_at
|
||||
`
|
||||
|
||||
type InsertAIBridgeModelThoughtParams struct {
|
||||
InterceptionID uuid.UUID `db:"interception_id" json:"interception_id"`
|
||||
Content string `db:"content" json:"content"`
|
||||
Metadata json.RawMessage `db:"metadata" json:"metadata"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertAIBridgeModelThought(ctx context.Context, arg InsertAIBridgeModelThoughtParams) (AIBridgeModelThought, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertAIBridgeModelThought,
|
||||
arg.InterceptionID,
|
||||
arg.Content,
|
||||
arg.Metadata,
|
||||
arg.CreatedAt,
|
||||
)
|
||||
var i AIBridgeModelThought
|
||||
err := row.Scan(
|
||||
&i.InterceptionID,
|
||||
&i.Content,
|
||||
&i.Metadata,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertAIBridgeTokenUsage = `-- name: InsertAIBridgeTokenUsage :one
|
||||
INSERT INTO aibridge_token_usages (
|
||||
id, interception_id, provider_response_id, input_tokens, output_tokens, metadata, created_at
|
||||
|
||||
@@ -53,6 +53,14 @@ INSERT INTO aibridge_tool_usages (
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertAIBridgeModelThought :one
|
||||
INSERT INTO aibridge_model_thoughts (
|
||||
interception_id, content, metadata, created_at
|
||||
) VALUES (
|
||||
@interception_id, @content, COALESCE(@metadata::jsonb, '{}'::jsonb), @created_at
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetAIBridgeInterceptionByID :one
|
||||
SELECT
|
||||
*
|
||||
@@ -362,6 +370,11 @@ WITH
|
||||
WHERE started_at < @before_time::timestamp with time zone
|
||||
),
|
||||
-- CTEs are executed in order.
|
||||
model_thoughts AS (
|
||||
DELETE FROM aibridge_model_thoughts
|
||||
WHERE interception_id IN (SELECT id FROM to_delete)
|
||||
RETURNING 1
|
||||
),
|
||||
tool_usages AS (
|
||||
DELETE FROM aibridge_tool_usages
|
||||
WHERE interception_id IN (SELECT id FROM to_delete)
|
||||
@@ -384,6 +397,7 @@ WITH
|
||||
)
|
||||
-- Cumulative count.
|
||||
SELECT (
|
||||
(SELECT COUNT(*) FROM model_thoughts) +
|
||||
(SELECT COUNT(*) FROM tool_usages) +
|
||||
(SELECT COUNT(*) FROM token_usages) +
|
||||
(SELECT COUNT(*) FROM user_prompts) +
|
||||
|
||||
@@ -235,6 +235,7 @@ sql:
|
||||
aibridge_tool_usage: AIBridgeToolUsage
|
||||
aibridge_token_usage: AIBridgeTokenUsage
|
||||
aibridge_user_prompt: AIBridgeUserPrompt
|
||||
aibridge_model_thought: AIBridgeModelThought
|
||||
rules:
|
||||
- name: do-not-use-public-schema-in-queries
|
||||
message: "do not use public schema in queries"
|
||||
|
||||
Reference in New Issue
Block a user