forked from PostHog/posthog
-
Notifications
You must be signed in to change notification settings - Fork 0
142 lines (121 loc) 路 7.16 KB
/
Copy pathci-ai.yml
File metadata and controls
142 lines (121 loc) 路 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
name: AI
on:
push:
branches:
- master
paths:
- 'ee/hogai/**'
- '.github/workflows/ci-ai.yml'
pull_request:
paths:
- 'ee/hogai/**'
- '.github/workflows/ci-ai.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }} # We only want one AI CI run per PR concurrently
jobs:
eval:
timeout-minutes: 30
name: Run AI evals
runs-on: ubuntu-latest
if: github.repository == 'PostHog/posthog' && (github.event_name == 'push' || (github.event.pull_request.draft == false)) # Braintrust credentials not available on forks
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# Check out the actual branch instead of merge commit with master,
# because we want the Braintrust experiment to have accurate git metadata (on master it's empty)
ref: ${{ github.event.pull_request.head.ref }}
- name: Stop/Start stack with Docker Compose
run: |
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.dev.yml up -d
- name: Set up Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
with:
python-version-file: 'pyproject.toml'
- name: Install uv
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
with:
enable-cache: true
pyproject-file: 'pyproject.toml'
- name: Install python dependencies
shell: bash
run: UV_PROJECT_ENVIRONMENT=$pythonLocation uv sync --frozen --dev
- name: Add Kafka and ClickHouse to /etc/hosts
run: sudo echo "127.0.0.1 kafka clickhouse" | sudo tee -a /etc/hosts
- name: Wait for Clickhouse & Kafka
run: bin/check_kafka_clickhouse_up
- name: Run LLM evals
run: pytest ee/hogai/eval
env:
BRAINTRUST_API_KEY: ${{ secrets.BRAINTRUST_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
- name: Post eval summary to PR
if: github.event_name == 'pull_request'
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
with:
github-token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }}
script: |
const fs = require("fs")
// Read the eval results
const evalResults = fs
.readFileSync("eval_results.jsonl", "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line))
if (evalResults.length === 0) {
console.log("No eval results found")
return
}
// Generate concise experiment summaries
const experimentSummaries = evalResults.map((result) => {
// Format scores as bullet points with improvements/regressions and baseline comparison
const scoresList = Object.entries(result.scores || {})
.map(([key, value]) => {
const score = typeof value.score === "number" ? `${(value.score * 100).toFixed(2)}%` : value.score
let baselineComparison = null
const diffHighlight = Math.abs(value.diff) > 0.01 ? "**" : ""
let diffEmoji = "馃啎"
if (result.comparison_experiment_name?.startsWith("master-")) {
baselineComparison = `${diffHighlight}${value.diff > 0 ? "+" : value.diff < 0 ? "" : "卤"}${(
value.diff * 100
).toFixed(2)}%${diffHighlight} versus [baseline (master)](${result.project_url}/experiments/${
result.comparison_experiment_name
}) (improvements: ${value.improvements}, regressions: ${value.regressions})`
diffEmoji = value.diff > 0.01 ? "馃煝" : value.diff < -0.01 ? "馃敶" : "馃數"
}
return `${diffEmoji} **${key}**: **${score}**${baselineComparison ? `, ${baselineComparison}` : ""}`
})
.join("\n")
// Format key metrics concisely
const metrics = result.metrics || {}
const duration = metrics.duration ? `鈴憋笍 ${metrics.duration.metric.toFixed(2)} s` : null
const totalTokens = metrics.total_tokens ? `馃敘 ${Math.floor(metrics.total_tokens.metric)} tokens` : null
const cost = metrics.estimated_cost ? `馃挼 $${metrics.estimated_cost.metric.toFixed(4)} in tokens` : null
const metricsText = [duration, totalTokens, cost].filter(Boolean).join(", ")
// Create concise experiment summary with header only showing experiment name
const experimentName = result.project_name.replace(/^max-ai-/, "")
return `### [${experimentName}](${result.experiment_url})\n\n${scoresList}\n\nAvg. case performance: ${metricsText}`
})
const totalExperiments = evalResults.length
const totalMetrics = evalResults.reduce((acc, result) => acc + Object.keys(result.scores || {}).length, 0)
const body = [
`## 馃 AI eval results`,
`Evaluated **${totalExperiments}** experiments, comprising **${totalMetrics}** metrics.`,
...experimentSummaries,
`_Triggered by [this commit](https://github.com/${context.repo.owner}/${context.repo.repo}/pull/${context.payload.pull_request.number}/commits/${context.payload.pull_request.head.sha})._`,
].join("\n\n")
// Post comment on PR
if (context.payload.pull_request) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body,
})
} else {
// Just log the summary if this is a push to master
console.log(body)
}