monitoring.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. const displayName = (s: string) =>
  2. s
  3. .split("-")
  4. .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
  5. .join(" ")
  6. .replace(/(?<=\d) (?=\d)/g, ".")
  7. const resourceName = (s: string) => displayName(s).replace(/[^a-zA-Z0-9]/g, "")
  8. const varSpec = (label: string, name: string) =>
  9. $jsonStringify({
  10. content: [
  11. {
  12. content: [
  13. {
  14. attrs: {
  15. name,
  16. label,
  17. missing: false,
  18. },
  19. type: "varSpec",
  20. },
  21. ],
  22. type: "paragraph",
  23. },
  24. ],
  25. type: "doc",
  26. })
  27. const fields = {
  28. model: incident.getAlertAttributeOutput({ name: "Model" }),
  29. product: incident.getAlertAttributeOutput({ name: "Product" }),
  30. }
  31. const alertSource = new incident.AlertSource("HoneycombAlertSource", {
  32. name: $app.stage === "production" ? "Honeycomb" : `Honeycomb (${$app.stage})`,
  33. sourceType: "honeycomb",
  34. template: {
  35. title: {
  36. literal: varSpec("Payload -> Title", "title"),
  37. },
  38. description: {
  39. literal: varSpec("Payload -> Description", "description"),
  40. },
  41. attributes: [
  42. {
  43. alertAttributeId: fields.model.id,
  44. binding: {
  45. value: {
  46. reference: 'expressions["model"]',
  47. },
  48. mergeStrategy: "first_wins",
  49. },
  50. },
  51. {
  52. alertAttributeId: fields.product.id,
  53. binding: {
  54. value: {
  55. reference: 'expressions["product"]',
  56. },
  57. mergeStrategy: "first_wins",
  58. },
  59. },
  60. ],
  61. expressions: [
  62. {
  63. label: "Model",
  64. operations: [
  65. {
  66. operationType: "parse",
  67. parse: {
  68. returns: {
  69. array: false,
  70. type: fields.model.type,
  71. },
  72. source: "$['model']",
  73. },
  74. },
  75. ],
  76. reference: "model",
  77. rootReference: "payload",
  78. },
  79. {
  80. label: "Product",
  81. operations: [
  82. {
  83. operationType: "parse",
  84. parse: {
  85. returns: {
  86. array: false,
  87. type: fields.product.type,
  88. },
  89. source: "$['product']",
  90. },
  91. },
  92. ],
  93. reference: "product",
  94. rootReference: "payload",
  95. },
  96. ],
  97. },
  98. })
  99. const webhookRecipient = new honeycomb.WebhookRecipient(`IncidentWebhook`, {
  100. name: $app.stage === "production" ? "Incident.io" : `Incident.io (${$app.stage})`,
  101. url: alertSource.alertEventsUrl,
  102. secret: alertSource.secretToken,
  103. templates: [
  104. {
  105. type: "trigger",
  106. body: $jsonStringify({
  107. title: "{{ .Name }}",
  108. description: "{{ .Description }}",
  109. status: "{{ .Alert.Status }}",
  110. deduplication_key: "{{ .Alert.InstanceID }}",
  111. source_url: "{{ .Result.URL }}",
  112. model: "{{ .Vars.model }}",
  113. product: "{{ .Vars.product }}",
  114. }),
  115. },
  116. ],
  117. variables: [
  118. {
  119. name: "model",
  120. },
  121. {
  122. name: "product",
  123. },
  124. ],
  125. })
  126. new incident.AlertRoute("HoneycombAlertRoute", {
  127. name: $app.stage === "production" ? "Honeycomb" : `Honeycomb (${$app.stage})`,
  128. enabled: true,
  129. isPrivate: false,
  130. alertSources: [
  131. {
  132. alertSourceId: alertSource.id,
  133. conditionGroups: [
  134. {
  135. conditions: [
  136. {
  137. subject: "alert.title",
  138. operation: "is_set",
  139. paramBindings: [],
  140. },
  141. ],
  142. },
  143. ],
  144. },
  145. ],
  146. conditionGroups: [
  147. {
  148. conditions: [
  149. {
  150. subject: "alert.title",
  151. operation: "is_set",
  152. paramBindings: [],
  153. },
  154. ],
  155. },
  156. ],
  157. expressions: [],
  158. escalationConfig: {
  159. autoCancelEscalations: true,
  160. escalationTargets: [],
  161. },
  162. incidentConfig: {
  163. autoDeclineEnabled: true,
  164. enabled: true,
  165. conditionGroups: [],
  166. deferTimeSeconds: 0,
  167. groupingKeys: [
  168. {
  169. reference: $interpolate`alert.attributes.${fields.model.id}`,
  170. },
  171. {
  172. reference: $interpolate`alert.attributes.${fields.product.id}`,
  173. },
  174. ],
  175. groupingWindowSeconds: 3600,
  176. },
  177. incidentTemplate: {
  178. name: {
  179. value: {
  180. literal: varSpec("Alert -> Title", "alert.title"),
  181. },
  182. },
  183. summary: {
  184. value: {
  185. literal: varSpec("Alert -> Description", "alert.description"),
  186. },
  187. },
  188. startInTriage: {
  189. value: {
  190. literal: "true",
  191. },
  192. },
  193. severity: {
  194. mergeStrategy: "first-wins",
  195. },
  196. incidentMode: {
  197. value: {
  198. literal: $app.stage === "production" ? "standard" : "test",
  199. },
  200. },
  201. },
  202. })
  203. type Product = "go" | "zen"
  204. type Trigger = (opts: { model: string; product: Product }) => {
  205. id: string
  206. title: string
  207. description: string
  208. json: honeycomb.GetQuerySpecificationOutputArgs
  209. threshold: { op: ">=" | "<="; value: number }
  210. }
  211. type Model = { id: string; products: Product[]; triggers: Trigger[] }
  212. const httpErrors: Trigger = ({ model, product }) => ({
  213. id: "increased-http-errors",
  214. title: `Increased HTTP Errors for ${displayName(model)} on ${displayName(product)}`,
  215. description: `Detected increased rate of HTTP errors for ${displayName(model)} on OpenCode ${displayName(product)}`,
  216. json: {
  217. calculations: [
  218. {
  219. op: "COUNT",
  220. name: "TOTAL",
  221. filterCombination: "AND",
  222. filters: [
  223. { column: "model", op: "=", value: model },
  224. { column: "event_type", op: "=", value: "completions" },
  225. { column: "user_agent", op: "contains", value: "opencode" },
  226. { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
  227. ],
  228. },
  229. {
  230. op: "COUNT",
  231. name: "FAILED",
  232. filterCombination: "AND",
  233. filters: [
  234. { column: "model", op: "=", value: model },
  235. { column: "event_type", op: "=", value: "completions" },
  236. { column: "user_agent", op: "contains", value: "opencode" },
  237. { column: "isGoTier", op: "=", value: product === "go" ? "true" : "false" },
  238. { column: "status", op: ">=", value: "400" },
  239. { column: "status", op: "!=", value: "401" },
  240. ],
  241. },
  242. ],
  243. formulas: [{ name: "ERROR", expression: "$FAILED / $TOTAL" }],
  244. timeRange: 900,
  245. },
  246. threshold: { op: ">=", value: 0.8 },
  247. })
  248. const models: Model[] = [
  249. { id: "kimi-k2.6", products: ["go", "zen"], triggers: [httpErrors] },
  250. { id: "kimi-k2.5", products: ["go", "zen"], triggers: [httpErrors] },
  251. { id: "deepseek-v4-flash", products: ["go", "zen"], triggers: [httpErrors] },
  252. { id: "deepseek-v4-pro", products: ["go", "zen"], triggers: [httpErrors] },
  253. { id: "glm-5.1", products: ["go", "zen"], triggers: [httpErrors] },
  254. // { id: "glm-5", products: ["go"], triggers: [httpErrors] },
  255. { id: "qwen3.6-plus", products: ["go", "zen"], triggers: [httpErrors] },
  256. { id: "qwen3.5-plus", products: ["go"], triggers: [httpErrors] },
  257. { id: "minimax-m2.7", products: ["go", "zen"], triggers: [httpErrors] },
  258. // { id: "minimax-m2.5", products: ["go", "zen"], triggers: [httpErrors] },
  259. { id: "mimo-v2.5-pro", products: ["go"], triggers: [httpErrors] },
  260. // { id: "mimo-v2.5", products: ["go"], triggers: [httpErrors] },
  261. // { id: "mimo-v2-omni", products: ["go"], triggers: [httpErrors] },
  262. // { id: "mimo-v2-pro", products: ["go"], triggers: [httpErrors] },
  263. { id: "claude-opus-4-7", products: ["zen"], triggers: [httpErrors] },
  264. // { id: "claude-opus-4-6", products: ["zen"], triggers: [httpErrors] },
  265. // { id: "claude-sonnet-4-6", products: ["zen"], triggers: [httpErrors] },
  266. { id: "gpt-5.5", products: ["zen"], triggers: [httpErrors] },
  267. { id: "big-pickle", products: ["zen"], triggers: [httpErrors] },
  268. // { id: "minimax-m2.5-free", products: ["zen"], triggers: [httpErrors] },
  269. // { id: "hy3-preview-free", products: ["zen"], triggers: [httpErrors] },
  270. // { id: "nemotron-3-super-free", products: ["zen"], triggers: [httpErrors] },
  271. // { id: "trinity-large-preview-free", products: ["zen"], triggers: [httpErrors] },
  272. // { id: "ling-2.6-flash-free", products: ["zen"], triggers: [httpErrors] },
  273. ]
  274. if ($app.stage !== "production") {
  275. models.splice(1)
  276. }
  277. for (const model of models) {
  278. for (const product of model.products) {
  279. for (const trigger of model.triggers) {
  280. const spec = trigger({ model: model.id, product })
  281. new honeycomb.Trigger(resourceName(`${spec.id}-${product}-${model.id}`), {
  282. name: spec.title,
  283. description: spec.description,
  284. queryJson: honeycomb.getQuerySpecificationOutput(spec.json).json,
  285. alertType: "on_change",
  286. frequency: 300,
  287. thresholds: [{ ...spec.threshold, exceededLimit: 1 }],
  288. recipients: [
  289. {
  290. id: webhookRecipient.id,
  291. notificationDetails: [
  292. {
  293. variables: [
  294. { name: "model", value: model.id },
  295. { name: "product", value: product },
  296. ],
  297. },
  298. ],
  299. },
  300. ],
  301. })
  302. }
  303. }
  304. }