548 lines
33 KiB
HTML
548 lines
33 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
|
|
<title>Incident Summaries using LLMs</title>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
<meta name="HandheldFriendly" content="True" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<link rel="preload" as="style" href="/assets/built/screen.css?v=S0DSiw6jVHJaYsG0" />
|
|
<link rel="preload" as="script" href="/assets/built/casper.js?v=YtPhrez3GuGLVDYs" />
|
|
|
|
<link rel="stylesheet" type="text/css" href="/assets/built/screen.css?v=S0DSiw6jVHJaYsG0" />
|
|
|
|
<meta name="description" content="How to use llama-2 7b to generate summaries of your incidents, using Cloudflare workers and Workers AI. Including Pirate mode.">
|
|
<link rel="canonical" href="https://karlstoney.com/incident-summaries-using-llms/">
|
|
<meta name="referrer" content="no-referrer-when-downgrade">
|
|
|
|
<meta property="og:site_name" content="karlstoney.com">
|
|
<meta property="og:type" content="article">
|
|
<meta property="og:title" content="Incident Summaries using LLMs">
|
|
<meta property="og:description" content="How to use llama-2 7b to generate summaries of your incidents, using Cloudflare workers and Workers AI. Including Pirate mode.">
|
|
<meta property="og:url" content="https://karlstoney.com/incident-summaries-using-llms/">
|
|
<meta property="article:published_time" content="2024-01-31T15:06:30.000Z">
|
|
<meta property="article:modified_time" content="2024-07-01T08:11:26.000Z">
|
|
<meta property="article:tag" content="Cloudflare">
|
|
<meta property="article:tag" content="AI">
|
|
|
|
<meta name="twitter:card" content="summary">
|
|
<meta name="twitter:title" content="Incident Summaries using LLMs">
|
|
<meta name="twitter:description" content="How to use llama-2 7b to generate summaries of your incidents, using Cloudflare workers and Workers AI. Including Pirate mode.">
|
|
<meta name="twitter:url" content="https://karlstoney.com/incident-summaries-using-llms/">
|
|
<meta name="twitter:label1" content="Written by">
|
|
<meta name="twitter:data1" content="Karl Stoney">
|
|
<meta name="twitter:label2" content="Filed under">
|
|
<meta name="twitter:data2" content="Cloudflare, AI">
|
|
<meta name="twitter:site" content="@karlstoney">
|
|
<meta name="twitter:creator" content="@karlstoney">
|
|
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
"publisher": {
|
|
"@type": "Organization",
|
|
"name": "karlstoney.com",
|
|
"url": "https://karlstoney.com/",
|
|
"logo": {
|
|
"@type": "ImageObject",
|
|
"url": "https://karlstoney.com/favicon.ico",
|
|
"width": 48,
|
|
"height": 48
|
|
}
|
|
},
|
|
"author": {
|
|
"@type": "Person",
|
|
"name": "Karl Stoney",
|
|
"image": {
|
|
"@type": "ImageObject",
|
|
"url": "https://karlstoney.com/content/images/2020/02/0.jpeg",
|
|
"width": 200,
|
|
"height": 200
|
|
},
|
|
"url": "https://karlstoney.com/author/karl/",
|
|
"sameAs": [
|
|
"https://karlstoney.com",
|
|
"https://x.com/karlstoney"
|
|
]
|
|
},
|
|
"headline": "Incident Summaries using LLMs",
|
|
"url": "https://karlstoney.com/incident-summaries-using-llms/",
|
|
"datePublished": "2024-01-31T15:06:30.000Z",
|
|
"dateModified": "2024-07-01T08:11:26.000Z",
|
|
"keywords": "Cloudflare, AI",
|
|
"description": "How to use llama-2 7b to generate summaries of your incidents, using Cloudflare workers and Workers AI. Including Pirate mode.",
|
|
"mainEntityOfPage": "https://karlstoney.com/incident-summaries-using-llms/"
|
|
}
|
|
</script>
|
|
|
|
<meta name="generator" content="Ghost 6.62">
|
|
<link rel="alternate" type="application/rss+xml" title="karlstoney.com" href="https://karlstoney.com/rss/">
|
|
<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.71/umd/portal.min.js" data-i18n="true" data-ghost="https://karlstoney.com/" data-key="1fcef56573b2a5779958db5e8c" data-api="https://karlstoney.com/ghost/api/content/" data-locale="en" crossorigin="anonymous"></script><style id="gh-members-styles">.gh-post-upgrade-cta-content,
|
|
.gh-post-upgrade-cta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
text-align: center;
|
|
width: 100%;
|
|
color: #ffffff;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.gh-post-upgrade-cta-content {
|
|
border-radius: 8px;
|
|
padding: 40px 4vw;
|
|
}
|
|
|
|
.gh-post-upgrade-cta h2 {
|
|
color: #ffffff;
|
|
font-size: 28px;
|
|
letter-spacing: -0.2px;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.gh-post-upgrade-cta p {
|
|
margin: 20px 0 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.gh-post-upgrade-cta small {
|
|
font-size: 16px;
|
|
letter-spacing: -0.2px;
|
|
}
|
|
|
|
.gh-post-upgrade-cta a {
|
|
color: #ffffff;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
box-shadow: none;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.gh-post-upgrade-cta a:hover {
|
|
color: #ffffff;
|
|
opacity: 0.8;
|
|
box-shadow: none;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.gh-post-upgrade-cta a.gh-btn {
|
|
display: block;
|
|
background: #ffffff;
|
|
text-decoration: none;
|
|
margin: 28px 0 0;
|
|
padding: 8px 18px;
|
|
border-radius: 4px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.gh-post-upgrade-cta a.gh-btn:hover {
|
|
opacity: 0.92;
|
|
}</style><script async src="https://js.stripe.com/v3/"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="1fcef56573b2a5779958db5e8c" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://karlstoney.com/" data-locale="en" crossorigin="anonymous"></script>
|
|
|
|
<link href="https://karlstoney.com/webmentions/receive/" rel="webmention">
|
|
<script defer src="/public/cards.min.js?v=ShRHxgy4po8zN-Wf"></script>
|
|
<link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=WwnU9jw5ancNC8Gc">
|
|
<script defer src="/public/comment-counts.min.js?v=oFYkaGLdiMqB8VN9" data-ghost-comments-counts-api="https://karlstoney.com/members/api/comments/counts/"></script>
|
|
<script defer src="/public/member-attribution.min.js?v=AKG4hWena9j3yX3I"></script><style>:root {--ghost-accent-color: #15171A;}</style>
|
|
<meta name="msvalidate.01" content="6D7C1C7A2BF9A2CF366B3881023DC3BE" />
|
|
|
|
<style type='text/css'>
|
|
|
|
.gh-powered-by {
|
|
display: none;
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/brands.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
|
|
<style>
|
|
.gh-head-menu .nav-linkedin a,
|
|
.gh-head-menu .nav-mastodon a,
|
|
.gh-head-menu .nav-twitter a,
|
|
.gh-head-menu .nav-x a,
|
|
.gh-head-menu .nav-slack a,
|
|
.gh-head-menu .nav-github a {
|
|
font-size: 0 !important;
|
|
}
|
|
|
|
.gh-head-menu .nav-linkedin a::before,
|
|
.gh-head-menu .nav-mastodon a::before,
|
|
.gh-head-menu .nav-twitter a::before,
|
|
.gh-head-menu .nav-x a::before,
|
|
.gh-head-menu .nav-slack a::before,
|
|
.gh-head-menu .nav-github a::before {
|
|
font-family: "Font Awesome 6 Brands";
|
|
display: inline-block;
|
|
font-size: 20px;
|
|
font-style: normal;
|
|
font-weight: normal;
|
|
font-variant: normal;
|
|
text-rendering: auto;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
.gh-head-menu .nav-linkedin a::before {content: "\f08c"}
|
|
.gh-head-menu .nav-mastodon a::before {content: "\f4f6"}
|
|
.gh-head-menu .nav-twitter a::before {content: "\f099"}
|
|
.gh-head-menu .nav-x a::before {content: "\e61b"}
|
|
.gh-head-menu .nav-slack a::before {content: "\f198"}
|
|
.gh-head-menu .nav-github a::before {content: "\f09b"}
|
|
</style>
|
|
|
|
</head>
|
|
<body class="post-template tag-cloudflare tag-ai is-head-left-logo has-cover">
|
|
<div class="viewport">
|
|
|
|
<header id="gh-head" class="gh-head outer">
|
|
<div class="gh-head-inner inner">
|
|
<div class="gh-head-brand">
|
|
<a class="gh-head-logo no-image" href="https://karlstoney.com">
|
|
karlstoney.com
|
|
</a>
|
|
<button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
|
|
<button class="gh-burger" aria-label="Main Menu"></button>
|
|
</div>
|
|
|
|
<nav class="gh-head-menu">
|
|
<ul class="nav">
|
|
<li class="nav-home"><a href="https://karlstoney.com/">Home</a></li>
|
|
<li class="nav-about"><a href="https://karlstoney.com/about/">About</a></li>
|
|
<li class="nav-email"><a href="https://karlstoney.com/contact/">Email</a></li>
|
|
<li class="nav-github"><a href="https://github.com/Stono">Github</a></li>
|
|
<li class="nav-twitter"><a href="https://twitter.com/karlstoney">Twitter</a></li>
|
|
<li class="nav-linkedin"><a href="https://www.linkedin.com/in/karl-stoney-94995548/">LinkedIn</a></li>
|
|
</ul>
|
|
|
|
</nav>
|
|
|
|
<div class="gh-head-actions">
|
|
<button class="gh-search gh-icon-btn" aria-label="Search this site" data-ghost-search><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button>
|
|
<div class="gh-head-members">
|
|
<a class="gh-head-link" href="#/portal/signin" data-portal="signin">Sign in</a>
|
|
<a class="gh-head-button" href="#/portal/signup" data-portal="signup">Subscribe</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="site-content">
|
|
|
|
|
|
|
|
|
|
<main id="site-main" class="site-main">
|
|
<article class="article post tag-cloudflare tag-ai featured no-image ">
|
|
|
|
<header class="article-header gh-canvas">
|
|
|
|
<div class="article-tag post-card-tags">
|
|
<span class="post-card-primary-tag">
|
|
<a href="/tag/cloudflare/">Cloudflare</a>
|
|
</span>
|
|
<span class="post-card-featured"><svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4.49365 4.58752C3.53115 6.03752 2.74365 7.70002 2.74365 9.25002C2.74365 10.6424 3.29678 11.9778 4.28134 12.9623C5.26591 13.9469 6.60127 14.5 7.99365 14.5C9.38604 14.5 10.7214 13.9469 11.706 12.9623C12.6905 11.9778 13.2437 10.6424 13.2437 9.25002C13.2437 6.00002 10.9937 3.50002 9.16865 1.68127L6.99365 6.25002L4.49365 4.58752Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg> Featured</span>
|
|
</div>
|
|
|
|
<h1 class="article-title">Incident Summaries using LLMs</h1>
|
|
|
|
<p class="article-excerpt">How to use llama-2 7b to generate summaries of your incidents, using Cloudflare workers and Workers AI. Including Pirate mode.</p>
|
|
|
|
<div class="article-byline">
|
|
<section class="article-byline-content">
|
|
|
|
<ul class="author-list instapaper_ignore">
|
|
<li class="author-list-item">
|
|
<a href="/author/karl/" class="author-avatar" aria-label="Read more of Karl Stoney">
|
|
<img class="author-profile-image" src="/content/images/size/w100/2020/02/0.jpeg" alt="Karl Stoney" loading="eager" />
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="article-byline-meta">
|
|
<h4 class="author-name"><a href="/author/karl/">Karl Stoney</a></h4>
|
|
<div class="byline-meta-content">
|
|
<time class="byline-meta-date" datetime="2024-01-31">31 Jan 2024</time>
|
|
<span class="byline-reading-time"><span class="bull">•</span> 6 min read</span>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
<a href="#/share" class="gh-button gh-button-share">Share</a>
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
<section class="gh-content gh-canvas">
|
|
<p>If you've read some of my other posts such as <a href="https://karlstoney.com/enriching-alerts-with-service-metadata/" rel="noreferrer">Alert and Incident</a> enrichment, you'll know that we use Slack to pull together all the interested parties when we experience any sort of problem in a production environment.</p><p>Those channels can naturally become quite noisy as engineers discuss the problem, product folks ask for updates, etc. For anyone coming into the incident after the fact we found it could be quite an overwhelming amount of information to try and digest. We started initially by asking people to "pin" the most important lines as they go, but even that can get noisy - what we needed was a short summary. </p><h2 id="the-solution">The Solution</h2><p>If only there was a new cool trending technology that would allow us to automatically summarise a bunch of text content into an easily digestible paragraph? Oh wait, there is - <a href="https://en.wikipedia.org/wiki/Large_language_model?ref=karlstoney.com">Large Language Models</a>. Whilst I've been using tools such as <a href="https://github.com/features/copilot?ref=karlstoney.com">Copilot</a> for a while now - with mixed results, where I've found LLM's really useful is restructuring text. I find myself regularly using <a href="https://chat.openai.com/auth/login?ref=karlstoney.com">ChatGPT</a> to reword things (I've used it for some of the excerpts for articles on this site). </p><p>For privacy reasons I wanted to avoid using ChatGPT - so I decided to pipe the pinned timeline through an OSS LLM (llama2 7b) and ask it to generate a short paragraph. It does a pretty great job - and lets people quickly understand what happened. Here's one from when we got DDOS'd recently!</p><figure class="kg-card kg-image-card"><img src="https://karlstoney.com/content/images/2024/01/Screenshot-2024-01-31-at-14.11.29.png" class="kg-image" alt="" loading="lazy" width="1237" height="940" srcset="https://karlstoney.com/content/images/size/w600/2024/01/Screenshot-2024-01-31-at-14.11.29.png 600w, https://karlstoney.com/content/images/size/w1000/2024/01/Screenshot-2024-01-31-at-14.11.29.png 1000w, https://karlstoney.com/content/images/2024/01/Screenshot-2024-01-31-at-14.11.29.png 1237w" sizes="(min-width: 720px) 720px"></figure><p>This summary is what appears when searching for Incidents on our internal dashboards:</p><figure class="kg-card kg-image-card"><img src="https://karlstoney.com/content/images/2024/01/Screenshot-2024-01-31-at-14.13.31.png" class="kg-image" alt="" loading="lazy" width="1402" height="886" srcset="https://karlstoney.com/content/images/size/w600/2024/01/Screenshot-2024-01-31-at-14.13.31.png 600w, https://karlstoney.com/content/images/size/w1000/2024/01/Screenshot-2024-01-31-at-14.13.31.png 1000w, https://karlstoney.com/content/images/2024/01/Screenshot-2024-01-31-at-14.13.31.png 1402w" sizes="(min-width: 720px) 720px"></figure><h2 id="how-to-build-it-yourself">How to build it yourself</h2><p>LLM's aren't new. However what's really changed over the past few years is how accessible they have become to people who have no data science background (such as myself). OSS models like <a href="https://ai.meta.com/llama/?ref=karlstoney.com">Llama-2</a> appeared, and tools like <a href="https://ollama.ai/?ref=karlstoney.com" rel="noreferrer">Ollama</a> made is super easy to run them on your local machine. However deploying and running them was (and is) still a bit challenging, for instance - typically you'll need access to GPUs (which are actually quite expensive to provision).</p><p>This is where <a href="https://developers.cloudflare.com/workers-ai/?ref=karlstoney.com" rel="noreferrer">Cloudflare Workers AI</a> fills a gap for me. If you haven't used <a href="https://developers.cloudflare.com/workers/?ref=karlstoney.com">Cloudflare Workers</a> before, I strongly suggest giving it a go. They're just lambda's integrated into your Cloudflare account behind all the other goodies you get from Cloudflare. Workers AI builds on top of that and gives you the ability to run a <a href="https://developers.cloudflare.com/workers-ai/models/?ref=karlstoney.com">collection of OSS LLMs</a> in workers, on Cloudflares GPUs, and they make it as simple as invoking a function.</p><p>Their developer experience with <a href="https://developers.cloudflare.com/workers/wrangler/?ref=karlstoney.com">wrangler</a> is exceptional and theirs a lively and active <a href="https://discord.gg/cloudflaredev?ref=karlstoney.com">Discord</a> community of people working with the product. You can even develop your worker locally, run it locally, but have it execute on the remote GPU. It's pretty special.</p><h3 id="server-side">Server Side</h3><p>I'm not going to repeat the great content already available on Cloudflares website for setting up a basic project with wrangler, you can read it <a href="https://developers.cloudflare.com/workers-ai/get-started/workers-wrangler/?ref=karlstoney.com" rel="noreferrer">here</a>. It's super simple and will take you all of 5 minutes.</p><p>Once you've got a basic project up and running, you can create an API endpoint that runs <code>llama2-7b</code> in just a few lines of code:</p><pre><code class="language-typescript">import { Ai } from '@cloudflare/ai'
|
|
import { Hono } from 'hono'
|
|
|
|
export type Bindings = {
|
|
AI: Ai
|
|
}
|
|
|
|
const app = new Hono<{ Bindings: Bindings }>()
|
|
|
|
app.post('/ml/question', async (c) => {
|
|
const { text } = await c.req.json()
|
|
if (!text) {
|
|
return new Response('No text provided', { status: 400 })
|
|
}
|
|
|
|
const ai = new Ai(c.env.AI)
|
|
const prompt: string[] = [
|
|
'My name is Skippr and I help with Software Engineering problems.',
|
|
'I do not ask questions, I only respond to prompts. I never suggest follows up questions.',
|
|
'My answers are concise and to the point, I do not guess.'
|
|
]
|
|
|
|
const messages = [
|
|
...prompt.map((item) => ({ role: 'assistant', content: item })),
|
|
{ role: 'user', content: text }
|
|
]
|
|
const output = await ai.run('@cf/meta/llama-2-7b-chat-fp16', {
|
|
messages
|
|
})
|
|
return new Response(JSON.stringify(output))
|
|
})
|
|
</code></pre><p>The key things here:</p><ul><li>I'm using <a href="https://hono.dev/?ref=karlstoney.com">hono</a> for routing. Think express, just slimmed down for the edge. </li><li>I'm creating a <code>POST</code> endpoint, <code>/ml/question</code> which expects a json payload with <code>content</code></li><li>I'm adding some content to the prompt which is appended as an <code>assistant</code> role on each request. This is where you can tweak how you want your bot to respond.</li><li>I build up my prompts and pass them to the Workers AI function <code>ai.run</code></li></ul><p>You can run this locally with <code>wrangler dev --remote</code>, the <code>--remote</code> part here tells wrangler to execute the function on Cloudflares remote infra/gpus. It does so completely transparently to you - it really is a first class developer experience.</p><p>At this point we have an endpoint that you can post a question to, and get a response:</p><pre><code>curl -H "content-type: application/json" -XPOST -d '{"text":"hi, what is your name?"}' http://127.0.0.1:8787/ml/question
|
|
|
|
{"response":"My name is Skippr."}</code></pre><p>Pretty dam cool hey? <a href="https://developers.cloudflare.com/workers/get-started/guide/?ref=karlstoney.com#4-deploy-your-project">Get it deployed</a> and lets move onto the client side.</p><h3 id="client-side">Client Side</h3><p>This, and the <code>assistant</code> context above, fall very much into the realms of <a href="https://www.mckinsey.com/featured-insights/mckinsey-explainers/what-is-prompt-engineering?ref=karlstoney.com">prompt engineering</a>, which is the practice of crafting your inputs to LLMs to get the desired output. I've crudely found you just need to iterate over different structures of inputs to get the output you desire.</p><p>In our case, we just wanted to get the pinned items from the channel. So when the incident was closed via the <code>@Skippr close incident</code> command, we use the slack <a href="https://api.slack.com/methods/conversations.history?ref=karlstoney.com">conversations.history</a> command to retrieve the content of the channel, and then filtered it for the pinned items. We then mapped that into a timeline that looks something like this:</p><pre><code>[hh:mm:ss] An incident was created by user-1, because reason
|
|
[hh:mm:ss] [user-1] Pinned item from what user1 said
|
|
[hh:mm:ss] [user-2] Pinned item from what user2 said
|
|
[hh:mm:ss] An incident was closed</code></pre><p>Our prompt was then the above information, wrapped in <code>"</code>, followed by our request, for example:</p><pre><code>"[hh:mm:ss] An incident was created by user-1, because reason
|
|
[hh:mm:ss] [user-1] Pinned item from what user1 said
|
|
[hh:mm:ss] [user-2] Pinned item from what user2 said
|
|
[hh:mm:ss] An incident was closed"
|
|
|
|
This was a timeline of an incident that is now closed, please summarise the timeline above in one paragraph of about 100 words without quoting dates or times:</code></pre><p>This is what we would then send as the <code>text</code> field on the body of our request to our <code>/ml/question</code> endpoint:</p><pre><code>{
|
|
response: 'An incident was created by user-1 due to a reason. User-1 then pinned an item from what user-1 said. Later, user-2 pinned an item from what user-2 said. Finally, the incident was closed.'
|
|
}</code></pre><p>Wonderful! Give it a go yourself, experiment with different prompts, experiment with asking for outputs for different personas perhaps? Have you ever wanted to take technical output but make it less-technical for business stakeholders? Put that in your prompt. Want your incident summaries in pirate form? Easy:</p><pre><code>{
|
|
response: 'Arrrr, here be the timeline of the incident, me hearty! User-1 created the incident because of a reason, then user-2 pinned an item from what user-1 said. Later, user-2 pinned an item from what user-2 said, and then the incident be closed! Yarrr, a fine summary of the timeline, me matey!'
|
|
}</code></pre><h2 id="conclusion">Conclusion</h2><p>I think of LLMs as somewhat of a black box, as a more typical software engineer - it's quite alien to me to have a function of which I don't understand the internals, that gives me a non-deterministic output, that can be influenced by the structure of my input which is just free form text. As a result I can't really write tests that assert on the output, and that makes me uncomfortable using them for anything more "serious" right now. There are plentiful amounts of examples where unbounded <a href="https://news.sky.com/story/dpd-customer-service-chatbot-swears-and-calls-company-worst-delivery-service-13052037?ref=karlstoney.com">chat bots have gone of the rails</a>. I'll be interested to see how testing / building confidence in the outputs evolves. This is likely where the folks with more of a data science background will shine!</p><p>But don't let any of that put you off, as you can see here - with minimal effort - using platforms such as Cloudflare Workers AI, you can get stuck in yourself.</p><figure class="kg-card kg-image-card"><img src="https://karlstoney.com/content/images/2024/01/funny-incident-summary.png" class="kg-image" alt="" loading="lazy" width="2000" height="174" srcset="https://karlstoney.com/content/images/size/w600/2024/01/funny-incident-summary.png 600w, https://karlstoney.com/content/images/size/w1000/2024/01/funny-incident-summary.png 1000w, https://karlstoney.com/content/images/size/w1600/2024/01/funny-incident-summary.png 1600w, https://karlstoney.com/content/images/2024/01/funny-incident-summary.png 2276w" sizes="(min-width: 720px) 720px"></figure><p></p>
|
|
</section>
|
|
|
|
<section class="article-comments gh-canvas">
|
|
|
|
<script defer src="https://cdn.jsdelivr.net/ghost/comments-ui@~1.6/umd/comments-ui.min.js" data-locale="en" data-ghost-comments="https://karlstoney.com/" data-api="https://karlstoney.com/ghost/api/content/" data-admin="https://karlstoney.com/ghost/" data-key="1fcef56573b2a5779958db5e8c" data-title="null" data-count="true" data-post-id="65ba528dd7fdbe000139eb6c" data-color-scheme="auto" data-avatar-saturation="60" data-accent-color="#15171A" data-comments-enabled="all" data-publication="karlstoney.com" crossorigin="anonymous"></script>
|
|
|
|
</section>
|
|
|
|
</article>
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<aside class="read-more-wrap outer">
|
|
<div class="read-more inner">
|
|
|
|
<article class="post-card post featured no-image keep-ratio">
|
|
|
|
|
|
<div class="post-card-content">
|
|
|
|
<a class="post-card-content-link" href="/from-pr-review-bot-atai-cli-to-an-autonomous-work-queue/">
|
|
<header class="post-card-header">
|
|
<div class="post-card-tags">
|
|
<span class="post-card-featured"><svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4.49365 4.58752C3.53115 6.03752 2.74365 7.70002 2.74365 9.25002C2.74365 10.6424 3.29678 11.9778 4.28134 12.9623C5.26591 13.9469 6.60127 14.5 7.99365 14.5C9.38604 14.5 10.7214 13.9469 11.706 12.9623C12.6905 11.9778 13.2437 10.6424 13.2437 9.25002C13.2437 6.00002 10.9937 3.50002 9.16865 1.68127L6.99365 6.25002L4.49365 4.58752Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg> Featured</span>
|
|
</div>
|
|
<h2 class="post-card-title">
|
|
From PR Review Bot + ATAI CLI to an Autonomous Work Queue
|
|
</h2>
|
|
</header>
|
|
<div class="post-card-excerpt">Taking our CLI, a Kubernetes Cluster and an Orchestrator to build an autonomous AI developer agent.</div>
|
|
</a>
|
|
|
|
<footer class="post-card-meta">
|
|
<time class="post-card-meta-date" datetime="2026-04-17">17 Apr 2026</time>
|
|
<span class="post-card-meta-length">8 min read</span>
|
|
<script
|
|
data-ghost-comment-count="69de00ffd764770001f6c8dd"
|
|
data-ghost-comment-count-empty=""
|
|
data-ghost-comment-count-singular="comment"
|
|
data-ghost-comment-count-plural="comments"
|
|
data-ghost-comment-count-tag="span"
|
|
data-ghost-comment-count-class-name=""
|
|
data-ghost-comment-count-autowrap="true"
|
|
>
|
|
</script>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
<article class="post-card post featured no-image keep-ratio">
|
|
|
|
|
|
<div class="post-card-content">
|
|
|
|
<a class="post-card-content-link" href="/building-atai-for-copilot/">
|
|
<header class="post-card-header">
|
|
<div class="post-card-tags">
|
|
<span class="post-card-featured"><svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M4.49365 4.58752C3.53115 6.03752 2.74365 7.70002 2.74365 9.25002C2.74365 10.6424 3.29678 11.9778 4.28134 12.9623C5.26591 13.9469 6.60127 14.5 7.99365 14.5C9.38604 14.5 10.7214 13.9469 11.706 12.9623C12.6905 11.9778 13.2437 10.6424 13.2437 9.25002C13.2437 6.00002 10.9937 3.50002 9.16865 1.68127L6.99365 6.25002L4.49365 4.58752Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg> Featured</span>
|
|
</div>
|
|
<h2 class="post-card-title">
|
|
Building ATAI: Our Opinionated CLI for Copilot at Scale
|
|
</h2>
|
|
</header>
|
|
<div class="post-card-excerpt">How we wrapped Copilot, to accelerate the use of AI engineering tools at Autotrader</div>
|
|
</a>
|
|
|
|
<footer class="post-card-meta">
|
|
<time class="post-card-meta-date" datetime="2026-04-14">14 Apr 2026</time>
|
|
<span class="post-card-meta-length">8 min read</span>
|
|
<script
|
|
data-ghost-comment-count="69de1228d764770001f6c8e7"
|
|
data-ghost-comment-count-empty=""
|
|
data-ghost-comment-count-singular="comment"
|
|
data-ghost-comment-count-plural="comments"
|
|
data-ghost-comment-count-tag="span"
|
|
data-ghost-comment-count-class-name=""
|
|
data-ghost-comment-count-autowrap="true"
|
|
>
|
|
</script>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
<article class="post-card post no-image keep-ratio">
|
|
|
|
|
|
<div class="post-card-content">
|
|
|
|
<a class="post-card-content-link" href="/building-a-pr-review-agent/">
|
|
<header class="post-card-header">
|
|
<div class="post-card-tags">
|
|
</div>
|
|
<h2 class="post-card-title">
|
|
Building a PR Review Agent
|
|
</h2>
|
|
</header>
|
|
<div class="post-card-excerpt">Building a PR review agent using Gemini CLI on Kubernetes</div>
|
|
</a>
|
|
|
|
<footer class="post-card-meta">
|
|
<time class="post-card-meta-date" datetime="2025-10-08">08 Oct 2025</time>
|
|
<span class="post-card-meta-length">11 min read</span>
|
|
<script
|
|
data-ghost-comment-count="68c29ebee76f0800016b2076"
|
|
data-ghost-comment-count-empty=""
|
|
data-ghost-comment-count-singular="comment"
|
|
data-ghost-comment-count-plural="comments"
|
|
data-ghost-comment-count-tag="span"
|
|
data-ghost-comment-count-class-name=""
|
|
data-ghost-comment-count-autowrap="true"
|
|
>
|
|
</script>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
</div>
|
|
</aside>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<footer class="site-footer outer">
|
|
<div class="inner">
|
|
<section class="copyright"><a href="https://karlstoney.com">karlstoney.com</a> © 2026</section>
|
|
<div class="site-footer-center">
|
|
<div class="site-footer-social-links">
|
|
<a href="https://x.com/karlstoney" target="_blank" rel="noopener" aria-label="X">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M18.2439 2.25H21.5519L14.3249 10.51L22.8269 21.75H16.1699L10.9559 14.933L4.98991 21.75H1.67991L9.40991 12.915L1.25391 2.25H8.07991L12.7929 8.481L18.2439 2.25ZM17.0829 19.77H18.9159L7.08391 4.126H5.11691L17.0829 19.77Z" fill="currentColor"/>
|
|
</svg> </a>
|
|
</div>
|
|
<nav class="site-footer-nav">
|
|
<ul class="nav">
|
|
<li class="nav-twitter"><a href="https://twitter.com/karlstoney">Twitter</a></li>
|
|
<li class="nav-github"><a href="https://github.com/Stono">Github</a></li>
|
|
<li class="nav-linkedin"><a href="https://www.linkedin.com/in/karl-stoney-94995548/">LinkedIn</a></li>
|
|
</ul>
|
|
|
|
</nav>
|
|
</div>
|
|
<div class="gh-powered-by"><a href="https://ghost.org/" target="_blank" rel="noopener">Powered by Ghost</a></div>
|
|
</div>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
|
<div class="pswp__bg"></div>
|
|
|
|
<div class="pswp__scroll-wrap">
|
|
<div class="pswp__container">
|
|
<div class="pswp__item"></div>
|
|
<div class="pswp__item"></div>
|
|
<div class="pswp__item"></div>
|
|
</div>
|
|
|
|
<div class="pswp__ui pswp__ui--hidden">
|
|
<div class="pswp__top-bar">
|
|
<div class="pswp__counter"></div>
|
|
|
|
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
|
<button class="pswp__button pswp__button--share" title="Share"></button>
|
|
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
|
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
|
|
|
<div class="pswp__preloader">
|
|
<div class="pswp__preloader__icn">
|
|
<div class="pswp__preloader__cut">
|
|
<div class="pswp__preloader__donut"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
|
<div class="pswp__share-tooltip"></div>
|
|
</div>
|
|
|
|
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
|
|
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
|
|
|
|
<div class="pswp__caption">
|
|
<div class="pswp__caption__center"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="/assets/built/casper.js?v=YtPhrez3GuGLVDYs" defer></script>
|
|
|
|
<script>
|
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
|
|
ga('create', 'UA-37161145-1', 'auto');
|
|
ga('send', 'pageview');
|
|
</script>
|
|
|
|
<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"5b355c7ee12445ab8f857265175fa35d","r":1,"spa":2}' crossorigin="anonymous"></script>
|
|
</body>
|
|
</html>
|