2453 lines
531 KiB
HTML
2453 lines
531 KiB
HTML
<!doctype html>
|
||
<!-- ✨ Built with Framer • https://www.framer.com/ -->
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
|
||
|
||
<script>try{if(localStorage.get("__framer_force_showing_editorbar_since")){const n=document.createElement("link");n.rel = "modulepreload";n.href="https://framer.com/edit/init.mjs";document.head.appendChild(n)}}catch(e){}</script>
|
||
<!-- Start of headStart -->
|
||
|
||
<!-- End of headStart -->
|
||
<meta name="viewport" content="width=device-width">
|
||
<meta name="generator" content="Framer 9d1d4bc">
|
||
<title>How to build an AI Agent for SRE - Unpage</title>
|
||
<meta name="description" content="Aptible AI software engineer takes you on a step-by-step journey to build your own AI Agent for SRE teams. ">
|
||
<meta name="framer-search-index" content="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/searchIndex-8lynPxZ6B9aB.json">
|
||
<meta name="framer-search-index-fallback" content="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/searchIndex-Tk7DuDjoN6G8.json">
|
||
<link href="https://framerusercontent.com/images/c68m0uQp79FapEL9Ni7Y7HFNkI.png" rel="icon" media="(prefers-color-scheme: light)">
|
||
<link href="https://framerusercontent.com/images/c68m0uQp79FapEL9Ni7Y7HFNkI.png" rel="icon" media="(prefers-color-scheme: dark)">
|
||
<!-- Open Graph / Facebook -->
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:title" content="How to build an AI Agent for SRE - Unpage">
|
||
<meta property="og:description" content="Aptible AI software engineer takes you on a step-by-step journey to build your own AI Agent for SRE teams. ">
|
||
<meta property="og:image" content="https://framerusercontent.com/images/7u9HsNaaWXbUqDasUTO7f1E1Gs.png">
|
||
<!-- Twitter -->
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="twitter:title" content="How to build an AI Agent for SRE - Unpage">
|
||
<meta name="twitter:description" content="Aptible AI software engineer takes you on a step-by-step journey to build your own AI Agent for SRE teams. ">
|
||
<meta name="twitter:image" content="https://framerusercontent.com/images/7u9HsNaaWXbUqDasUTO7f1E1Gs.png">
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-1PL4J3CNJ9"></script>
|
||
<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag(){window.dataLayer.push(arguments);}
|
||
gtag('js', new Date());
|
||
gtag('config', 'G-1PL4J3CNJ9');
|
||
</script>
|
||
|
||
<style data-framer-font-css>/* cyrillic-ext */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EH9CsKng.woff2) format('woff2');
|
||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||
}
|
||
/* cyrillic */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EO9CsKng.woff2) format('woff2');
|
||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||
}
|
||
/* greek-ext */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EG9CsKng.woff2) format('woff2');
|
||
unicode-range: U+1F00-1FFF;
|
||
}
|
||
/* greek */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EJ9CsKng.woff2) format('woff2');
|
||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||
}
|
||
/* vietnamese */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EF9CsKng.woff2) format('woff2');
|
||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||
}
|
||
/* latin-ext */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EE9CsKng.woff2) format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
/* latin */
|
||
@font-face {
|
||
font-family: 'Source Sans 3';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/sourcesans3/v19/nwpBtKy2OAdR1K-IwhWudF-R9QMylBJAV3Bo8Ky462EK9Cs.woff2) format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
/* vietnamese */
|
||
@font-face {
|
||
font-family: 'Space Grotesk';
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVnsrPMBTTA.woff2) format('woff2');
|
||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||
}
|
||
/* latin-ext */
|
||
@font-face {
|
||
font-family: 'Space Grotesk';
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVnsqPMBTTA.woff2) format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
/* latin */
|
||
@font-face {
|
||
font-family: 'Space Grotesk';
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
font-display: swap;
|
||
src: url(https://fonts.gstatic.com/s/spacegrotesk/v22/V8mQoQDjQSkFtoMM3T6r8E7mF71Q-gOoraIAEj4PVnskPMA.woff2) format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
/* cyrillic-ext */
|
||
@font-face {
|
||
font-family: 'Fragment Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
src: url(https://fonts.gstatic.com/s/fragmentmono/v6/4iCr6K5wfMRRjxp0DA6-2CLnB45HhrUI.woff2) format('woff2');
|
||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||
}
|
||
/* latin-ext */
|
||
@font-face {
|
||
font-family: 'Fragment Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
src: url(https://fonts.gstatic.com/s/fragmentmono/v6/4iCr6K5wfMRRjxp0DA6-2CLnB41HhrUI.woff2) format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
/* latin */
|
||
@font-face {
|
||
font-family: 'Fragment Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
src: url(https://fonts.gstatic.com/s/fragmentmono/v6/4iCr6K5wfMRRjxp0DA6-2CLnB4NHhg.woff2) format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
|
||
@font-face { font-family: "Suisse Int'l Regular"; src: url("https://framerusercontent.com/assets/L3CN4MLCZQeuh9BjpUboN92cIXE.woff2"); font-display: swap }
|
||
@font-face { font-family: "Suisse Int'l Medium"; src: url("https://framerusercontent.com/assets/do6kPKhFKFxVqY3xfYZQwHKzcY.woff2"); font-display: swap }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/5vvr9Vy74if2I6bQbJvbw7SY1pQ.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/EOr0mi4hNtlgWNn9if640EZzXCo.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Y9k9QrlZAqio88Klkmbd8VoMQc.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/OYrD2tBIBPvoJXiIHnLoOXnY9M.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/JeYwfuaPfZHQhEG8U5gtPDZ7WQ.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/GrgcKwrN6d3Uz8EwcLHZxwEfC4.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/b6Y37FthZeALduNqHicBT6FutY.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/mkY5Sgyq51ik0AMrSBwhm9DJg.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/X5hj6qzcHUYv7h1390c8Rhm6550.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/gQhNpS3tN86g8RcVKYUUaKt2oMQ.woff2"); font-style: normal; font-weight: 900; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/cugnVhSraaRyANCaUtI5FV17wk.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/5HcVoGak8k5agFJSaKa4floXVu0.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/rZ5DdENNqIdFTIyQQiP5isO7M.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/P2Bw01CtL0b9wqygO0sSVogWbo.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/05KsVHGDmqXSBXM4yRZ65P8i0s.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/ky8ovPukK4dJ1Pxq74qGhOqCYI.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/vvNSqIj42qeQ2bvCRBIWKHscrc.woff2"); font-style: italic; font-weight: 900; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/3ZmXbBKToJifDV9gwcifVd1tEY.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/FNfhX3dt4ChuLJq2PwdlxHO7PU.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/gcnfba68tfm7qAyrWRCf9r34jg.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/efTfQcBJ53kM2pB1hezSZ3RDUFs.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/H89BbHkbHDzlxZzxi8uPzTsp90.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/u6gJwDuwB143kpNK1T1MDKDWkMc.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/43sJ6MfOPh1LCJt46OvyDuSbA6o.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/wccHG0r4gBDAIRhfHiOlq6oEkqw.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/WZ367JPwf9bRW6LdTHN8rXgSjw.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/ia3uin3hQWqDrVloC1zEtYHWw.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/2A4Xx7CngadFGlVV4xrO06OBHY.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/NXxvFRoY5LDh3yCm7MEP2jqYk.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/5CcgcVyoWSqO1THBiISd6oCog.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/MF544SVCvk3yNpLIz3pwDXFZPKM.woff2"); font-style: normal; font-weight: 100; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/TNtxudDBkAm2RXdtU3rvTBwoM.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/fIabp4VN5z7iJ3lNOz9qfNeQHc.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/8kSLqTnVCEtjx0nu8PxTD4Nh5UU.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/9iRSYClnXA0RMygyIn6yjjWXJw.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/MVhJhYeDWxeyqT939zMNyw9p8.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/WXQXYfAQJIi2pCJACAfWWXfIDqI.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/RJeJJARdrtNUtic58kOz7hIgBuE.woff2"); font-style: normal; font-weight: 200; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/4hBRAuM02i3fsxYDzyNvt5Az2so.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/fz1JbBffNGgK7BNUI1mmbFBlgA8.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Z4sGWU2OKBoXPWulb5P25vULA.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/eIZyQwIlHYR0mnMSneEDMtqBPgw.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/BkDpl4ghaqvMi1btKFyG2tdbec.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/zAMK70AQRFSShJgUiaR5IiIhgzk.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/IETjvc5qzUaRoaruDpPSwCUM8.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/oLCoaT3ioA0fHdJnWR9W6k7NY.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Sj0PCHQSBjFmEp6NBWg6FNaKc.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/aqiiD4LUKkKzXdjGL5UzHq8bo5w.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/H4TfENUY1rh8R9UaSD6vngjJP3M.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/5A3Ce6C9YYmCjpQx9M4inSaKU.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Qx95Xyt0Ka3SGhinnbXIGpEIyP4.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/6mJuEAguuIuMog10gGvH5d3cl8.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/xYYWaj7wCU5zSQH0eXvSaS19wo.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/otTaNuNpVK4RbdlT7zDDdKvQBA.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/UjlFhCnUjxhNfep4oYBPqnEssyo.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/DolVirEGb34pEXEp8t8FQBSK4.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/hyOgCu0Xnghbimh0pE8QTvtt2AU.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/NeGmSOXrPBfEFIy5YZeHq17LEDA.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/oYaAX5himiTPYuN8vLWnqBbfD2s.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/lEJLP4R0yuCaMCjSXYHtJw72M.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/cRJyLNuTJR5jbyKzGi33wU9cqIQ.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/yDtI2UI8XcEg1W2je9XPN3Noo.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/A0Wcc7NgXMjUuFdquHDrIZpzZw0.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/DpPBYI0sL4fYLgAkX8KXOPVt7c.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/4RAEQdEOrcnDkhHiiCbJOw92Lk.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/1K3W8DizY3v4emK8Mb08YHxTbs.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/tUSCtfYVM1I1IchuyCwz9gDdQ.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/VgYFWiwsAC5OYxAycRXXvhze58.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/syRNPWzAMIrcJ3wIlPIP43KjQs.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/GIryZETIX4IFypco5pYZONKhJIo.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/PONfPc6h4EPYwJliXQBmjVx7QxI.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/zsnJN7Z1wdzUvepJniD3rbvJIyU.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/UrzZBOy7RyJEWAZGduzOeHiHuY.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/996sR9SfSDuYELz8oHhDOcErkY.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/ftN1HpyPVJEoEb4q36SOrNdLXU.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Mput0MSwESKlJ6TMz9MPDXhgrk.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/JAur4lGGSGRGyrFi59JSIKqVgU.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/YJsHMqeEm0oDHuxRTVCwg5eZuo.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/oJJMyJlDykMObEyb5VexHSxd24.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/IpeaX0WzLaonj68howNZg4SJJaY.woff2"); font-style: italic; font-weight: 100; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/KCj1bV3vDXY5OLHttTeRYcu9J8.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/biaVHhOprxbHaR3dIP7Z8cYurHg.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/3on0VNjjmogkq1f9ziKFcrY72MI.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/gNa011yWpVpNFgUhhSlDX8nUiPQ.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/vpq17U0WM26sBGHgq9jnrUmUf8.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/bNYh7lNMEpOegeRYAtyGel1WqBE.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/FBzcXZYmdulcZC0z278U6o0cw.woff2"); font-style: italic; font-weight: 200; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/ua60IRqWK94xCrq0SC639Hbsdjg.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Pd8gNPn2dSCh4FyjWFw9PJysoQ.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/6OdIurwS6YHsVW2i5fR5CKn0gg.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/DtVQjTG8OGGKnwKYrMHRZVCyo.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/YYB6GZmCWnZq3RWZOghuZIOxQY.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/miJTzODdiyIr3tRo9KEoqXXk2PM.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/6ZMhcggRFfEfbf7lncCpaUbA.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/8sCN6PGUr4I8q5hC5twAXfcwqV0.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/aUYDUTztS7anQw5JuwCncXeLOBY.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/8mwKwShtYEXIZ5diRBT74yn9jdQ.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/yDiPvYxioBHsicnYxpPW35WQmx8.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/CfMzU8w2e7tHgF4T4rATMPuWosA.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/867QObYax8ANsfX4TGEVU9YiCM.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Oyn2ZbENFdnW7mt2Lzjk1h9Zb9k.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/cdAe8hgZ1cMyLu9g005pAW3xMo.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/DOfvtmE1UplCq161m6Hj8CSQYg.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/pKRFNWFoZl77qYCAIp84lN1h944.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/tKtBcDnBMevsEEJKdNGhhkLzYo.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/khkJkwSL66WFg8SX6Wa726c.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/0E7IMbDzcGABpBwwqNEt60wU0w.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/NTJ0nQgIF0gcDelS14zQ9NR9Q.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/QrcNhgEPfRl0LS8qz5Ln8olanl8.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/JEXmejW8mXOYMtt0hyRg811kHac.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/Bo5CNzBv77CafbxOtKIkpw9egw.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/uy9s0iWuxiNnVt8EpTI3gzohpwo.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/vxBnBhH8768IFAXAb4Qf6wQHKs.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/zSsEuoJdh8mcFVk976C05ZfQr8.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/b8ezwLrN7h2AUoPEENcsTMVJ0.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/mvNEIBLyHbscgHtwfsByjXUz3XY.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/6FI2EneKzM3qBy5foOZXey7coCA.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/fuyXZpVvOjq8NesCOfgirHCWyg.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/NHHeAKJVP0ZWHk5YZnQQChIsBM.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/if4nAQEfO1l3iBiurvlUSTaMA.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/GdJ7SQjcmkU1sz7lk5lMpKUlKY.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/sOA6LVskcCqlqggyjIZe0Zh39UQ.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/zUCSsMbWBcHOQoATrhsPVigkc.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/6eYp9yIAUvPZY7o0yfI4e2OP6g.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/8AYN209PzikkmqPEbOGJlpgu6M.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter"; src: url("https://framerusercontent.com/assets/L9nHaKi7ULzGWchEehsfwttxOwM.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/mYcqTSergLb16PdbJJQMl9ebYm4.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/ZRl8AlxwsX1m7xS1eJCiSPbztg.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/nhSQpBRqFmXNUBY2p5SENQ8NplQ.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/DYHjxG0qXjopUuruoacfl5SA.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/s7NH6sl7w4NU984r5hcmo1tPSYo.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/7lw0VWkeXrGYJT05oB3DsFy8BaY.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/wx5nfqEgOXnxuFaxB0Mn9OhmcZA.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/FMAZXZOvzvP9daUF2pk7jeNQE.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/XRXDXcGEcHEhj5vRoCcaNzLUII.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/WL8JiibS9vHU7Cb3NGaJKioRjQ.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/LfqxQLLXCFxnUuZS8fkv9AT0uI.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/3IqofWpyruZr7Qc3kaedPmw7o.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/N9O1YBTY0ROZrE0GiMsoMSyPbQ.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Variable"; src: url("https://framerusercontent.com/assets/Qpek1Wzh1dJ3f8j88Vebg4yC92w.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/2uIBiALfCHVpWbHqRMZutfT7giU.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/Zwfz6xbVe5pmcWRJRgBDHnMkOkI.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/U9LaDDmbRhzX3sB8g8glTy5feTE.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/tVew2LzXJ1t7QfxP1gdTIdj2o0g.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/DF7bjCRmStYPqSb945lAlMfCCVQ.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/bHYNJqzTyl2lqvmMiRRS6Y16Es.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/vebZUMjGyKkYsfcY73iwWTzLNag.woff2"); font-display: swap; font-style: normal; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/Vu4RH4OOAYHIXg4LpXVzNVmbpY.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/eOWZqb2nQ1SLbtYmcGBFYFSOE5U.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/H0Z3dXk6VFBv9TPQedY7FZVVVQ.woff2"); font-style: normal; font-weight: 100; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/sMud1BrfhejzYfhsGoUtjWdRb90.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/SDel4nEmK9AyiBktaYZfYI9DrY.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/GoWsxcsKZ1Hiy2Y8l40zBgNwYvo.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/iPDparXDXhfwSxmuiEXzr0b1vAU.woff2"); font-style: normal; font-weight: 100; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/kZK78sVb0ChIxwI4EF00ArQvpu0.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/zLXnIInNs9VhCJZQ1B0FvHgtWDM.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/yHa3FUh9QDCLkYGoHU44PsRbTI.woff2"); font-style: normal; font-weight: 200; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/w3wwH92cnNPcZVAf63gAmGQW0k.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/w9aHRXFhel7kScIgRMsPqEwE3AY.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/t5RNiwIqoB2GvYhcdv6NPQF6kfk.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/eDNmF3DmGWYDX8NrhNZOl1SDyQ.woff2"); font-style: normal; font-weight: 200; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/MaMAn5Jp5gJg1z3VaLH65QwWPLc.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/fEqgEChcTaneQFxeugexHq7Bk.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/nL7d8Ph0ViwUQorApF89PoAagQI.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/YOHXUQlY1iC2t7qT4HeLSoBDtn4.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/TfzHIi6ZmZDuhnIFGcgM6Ipuim4.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/CnMzVKZxLPB68RITfNGUfLe65m4.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/ub5XwqqEAMrXQz31ej6fNqbxnBc.woff2"); font-display: swap; font-style: normal; font-weight: 300; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/ePuN3mCjzajIHnyCdvKBFiZkyY0.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/V3j1L0o5vPFKe26Sw4HcpXCfHo.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/F3kdpd2N0cToWV5huaZjjgM.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/0iDmxkizU9goZoclqIqsV5rvETU.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/r0mv3NegmA0akcQsNFotG32Las.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/iwWTDc49ENF2tCHbqlNARXw6Ug.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/Ii21jnSJkulBKsHHXKlapi7fv9w.woff2"); font-display: swap; font-style: normal; font-weight: 500; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/gazZKZuUEtvr9ULhdA4SprP0AZ0.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/pe8RoujoPxuTZhqoNzYqHX2MXA.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/teGhWnhH3bCqefKGsIsqFy3hK8.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/qQHxgTnEk6Czu1yW4xS82HQWFOk.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/MJ3N6lfN4iP5Um8rJGqLYl03tE.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/PfdOpgzFf7N2Uye9JX7xRKYTgSc.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/0SEEmmWc3vovhaai4RlRQSWRrz0.woff2"); font-display: swap; font-style: normal; font-weight: 600; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/I11LrmuBDQZweplJ62KkVsklU5Y.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/UjFZPDy3qGuDktQM4q9CxhKfIa8.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/8exwVHJy2DhJ4N5prYlVMrEKmQ.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/UTeedEK21hO5jDxEUldzdScUqpg.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/Ig8B8nzy11hzIWEIYnkg91sofjo.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/qITWJ2WdG0wrgQPDb8lvnYnTXDg.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/qctQFoJqJ9aIbRSIp0AhCQpFxn8.woff2"); font-display: swap; font-style: normal; font-weight: 700; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/RDJfWXA0aFdpD2OO2AJLBh2Z7E.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/e97VFGU1js4GGgvQCnvIiHpRf0.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/9WZaFuMg9OiQ7an8bugAJkUG04.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/Kc2pkIGjh9K7EmlGx2sorKHcJDw.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/pFve2DToKu0uyKsWs4NBTHpK7Vc.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/9Nu9BRcGkRrLnJCGqVdSUXJt2Ig.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/4hhgqiTCDCCa8WhFmTB7QXODupk.woff2"); font-display: swap; font-style: normal; font-weight: 800; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/EZSFVoaPWKnZ6GuVk5dlS5pk.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/TMYuLEOqLWgunxD5qCFeADEgk.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/EL4v8OG6t2XwHSJLO1YF25DNiDw.woff2"); font-style: normal; font-weight: 900; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/vSoBOmgqFUd0rVCgogjwL7jQI.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/bIwBTbnyeAOa9wQIBagEdRDHXhE.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/x8eJ5VkmPMHVhVbF0E8JYFJmwc.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/fmXYBApyDC604dR7g2aaACGvCk.woff2"); font-style: normal; font-weight: 900; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/fWaKVy6hRB8Z8aBaXHMiFEvPILU.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/syX0favWwGMDkTs34aSUJp2FDg.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/MRDnh8epalyri0qENYQVaYkjNjA.woff2"); font-style: italic; font-weight: 100; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/dkR8k5Cze1rY2rQWCcFiLvNxm8M.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/3RO0kKTqHkJmvsJcJAr5at0.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/mKzAiDvphQ1qAmJcRwlQ0QOK3Dc.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/GSo3TkcX0SwmUNLnnUlsKhqgZI.woff2"); font-style: italic; font-weight: 100; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/7b925B3vAlG2atcrbV7LoSudp7c.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/FuEG87bF2T2uEeI84KQYYAE9wA.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/l72nQILmlFUAK8OXlvJB8Qybl2M.woff2"); font-style: italic; font-weight: 200; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/PjAOIKsdFTryeYPWcKg4xHgWCTI.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/5u1LyW8isA2RA3QfYvCc6TVeGSU.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/OxW7bkxVYziGRnONbuXLAeuUEkQ.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/vFOa28RMJNgff4GnWLns8E8vGZw.woff2"); font-style: italic; font-weight: 200; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/JVGAsgKKXCCsDQ3MCrxIs5u1U.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/GeFYhvsobZhw8u0Xza2zCmC8Cw.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/8dxmIktsyEgZt0OloSu5gz6TgLI.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/f2kclCrwmFV8YeznrosrT3kcjE.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/HTPyW5jOpXfwIja9CrgNJUDriY4.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/raPlI8zqlyoXu4yBGiiecGg2dI.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/xFtzow8wtMpaEQuIGgCbt25Qorg.woff2"); font-display: swap; font-style: italic; font-weight: 300; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/THWAFHoAcmqLMy81E8hCSdziVKA.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/sQxGYWDlRkDr0eOKqiNRl6g5rs.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/NNTAT1XAm8ZRkr824inYPkjNeL4.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/P2qr9PAWBt905929rHfxmneMUG0.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/2BmSa4TZZvFKAZg2DydxTbvKlTU.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/P0JCAnd2N1Q6qwTQohre3XmQ.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/KMFW46iYsEZaUBwXbwPc9nQm71o.woff2"); font-display: swap; font-style: italic; font-weight: 400; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/A5P4nkYCJlLQxGxaS1lzG8PNSc.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/vuPfygr1n1zYxscvWgGI8hRf3LE.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/jplzYzqFHXreyADwk9yrkQlWQ.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/sSIKP2TfVPvfK7YVENPE5H87A.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/gawbeo7iEJSRZ4kcrh6YRrU8o.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/RkMAGv2iAm3rw7tZzs7FaZf0rM.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/8E92vrr3j1gDqzepmeSbD2u0JxA.woff2"); font-display: swap; font-style: italic; font-weight: 500; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/KKQXBq6BF0xEmVbrTnWxh3Yw.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/ugFYeIKZEJXDxI6Kh0YQUX6EK9I.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/DgDysCLt0HkIpWMu2uN5tivgQ.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/2yEyxokC6poTg3Uc5E2ogfVi6WE.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/wmHLUqO4QxLaADUScavxvknnBQ.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/e3ySEnhUUpItjJUo5buJawUl4Y.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/DuYGXsyDXsSnxlZ8qzmcA4x3JiI.woff2"); font-display: swap; font-style: italic; font-weight: 600; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/fXvVh2JeZlehNcEhKHpHH0frSl0.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/7pScaNeb6M7n2HF2jKemDqzCIr4.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/qS4UjQYyATcVV9rODk0Zx9KhkY8.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/VfD2n20yM7v0hrUEBHEyafsmMBY.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/4oIO9fB59bn3cKFWz7piCj28z9s.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/MzumQQZJQBC6KM1omtmwOtsogtI.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/F5Lmfd3fCAu7TwiYbI4DLWw4ks.woff2"); font-display: swap; font-style: italic; font-weight: 700; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/toNasQAsgu8B7dP8n9xdXRzdNhU.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/xPVm3TFQkuvdIaC7nzaBBAiE5Y.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/xwWV9Y5bwNwM4PjHKFG1XmdwA.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/oG51mTqVfF2JDETyCzOOIQOpzY.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/1aaySnsnw0pKE2qUemKvHaM1tw.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/QTKFuIAPTQlFFMI0HG9beHs3Xc.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/gLJVFeatQcrirAaS7jqVJIO4.woff2"); font-display: swap; font-style: italic; font-weight: 800; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/yHWoQPBZSEiCC3aW3JXtcoFby8o.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/UHqhq621TbUtspKtPKPA1Ne8qY.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/6dsy2CbviAWDcszGLlgyaLpRMk.woff2"); font-style: italic; font-weight: 900; unicode-range: U+1F00-1FFF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/TQqnntSFYYGDv7XtyT3ZhfvDA.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0370-03FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/nE03Xxh2GngsU8hDpg6CRGxqfoM.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/U1kEiQQO1ar9PuYJyyRkclmq8o.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2070, U+2074-207E, U+2080-208E, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
|
||
@font-face { font-family: "Inter Display"; src: url("https://framerusercontent.com/assets/dWFiPjtJpfcxAIDV8RPyuCq04.woff2"); font-style: italic; font-weight: 900; unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB }
|
||
@font-face { font-family: "Space Grotesk"; src: url("https://framerusercontent.com/third-party-assets/fontshare/wf/6DG6HUOGHOB35UGAANBDBVY77OCFNQOA/MX56D7EXTFRCL3EZPNM332VF6D5TDENT/2MGP255ZY2RSFHKW6LNN6W6BWQGC2LUO.woff2"); font-display: swap; font-style: normal; font-weight: 400 }
|
||
@font-face { font-family: "Space Grotesk Placeholder"; src: local("Arial"); ascent-override: 88.75%; descent-override: 26.34%; line-gap-override: 0.00%; size-adjust: 110.87% }
|
||
@font-face { font-family: "Source Sans 3 Placeholder"; src: local("Arial"); ascent-override: 108.71%; descent-override: 42.47%; line-gap-override: 0.00%; size-adjust: 94.19% }
|
||
@font-face { font-family: "Suisse Int'l Regular Placeholder"; src: local("Arial"); ascent-override: 95.68%; descent-override: 30.18%; line-gap-override: 0.00%; size-adjust: 103.05% }
|
||
@font-face { font-family: "Suisse Int'l Medium Placeholder"; src: local("Arial"); ascent-override: 94.03%; descent-override: 29.66%; line-gap-override: 0.00%; size-adjust: 104.86% }
|
||
@font-face { font-family: "Inter Placeholder"; src: local("Arial"); ascent-override: 89.79%; descent-override: 22.36%; line-gap-override: 0.00%; size-adjust: 107.89% }
|
||
@font-face { font-family: "Inter Variable Placeholder"; src: local("Arial"); ascent-override: 89.79%; descent-override: 22.36%; line-gap-override: 0.00%; size-adjust: 107.89% }
|
||
@font-face { font-family: "Inter Display Placeholder"; src: local("Arial"); ascent-override: 98.56%; descent-override: 24.54%; line-gap-override: 0.00%; size-adjust: 98.29% }
|
||
@font-face { font-family: "Space Grotesk Placeholder"; src: local("Arial"); ascent-override: 88.74%; descent-override: 26.33%; line-gap-override: 0.00%; size-adjust: 110.88% }</style>
|
||
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
|
||
<meta name="robots" content="max-image-preview:large"><link rel="canonical" href="https://www.unpage.ai/guides/how-to-build-an-ai-agent-for-sre"><meta property="og:url" content="https://www.unpage.ai/guides/how-to-build-an-ai-agent-for-sre"><style data-framer-breakpoint-css>@media(min-width: 1440px){.hidden-1f4b9ar{display:none!important}}@media(min-width: 1200px) and (max-width: 1439.98px){.hidden-be8ipx{display:none!important}}@media(min-width: 810px) and (max-width: 1199.98px){.hidden-126m487{display:none!important}}@media(max-width: 809.98px){.hidden-1ibmtzm{display:none!important}}</style><style data-framer-css-ssr-minified data-framer-components="framer-lib-cursors-host framer-iwu1g PropertyOverrides framer-B0Zy3 framer-azFTY framer-8s6rc framer-PqHEJ framer-lib-codeblock"></style><style>
|
||
html, body, #main {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
:root {
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
* {
|
||
box-sizing: border-box;
|
||
-webkit-font-smoothing: inherit;
|
||
}
|
||
h1, h2, h3, h4, h5, h6, p, figure {
|
||
margin: 0;
|
||
}
|
||
body, input, textarea, select, button { font-size: 12px; font-family: sans-serif; }
|
||
body { --token-4f207428-b738-44b3-8299-0e04fb0a7645: rgb(255, 255, 255);--token-31370923-de67-495c-864a-920fd49f64a7: rgb(17, 25, 32);--token-2ffc668e-e171-46ae-b234-52dc68b5a454: rgb(255, 255, 255);--token-a318d4a5-3880-40a7-a4c4-f543116a49df: rgb(255, 182, 7);--token-16ff4860-3fc9-42a6-86bb-5867ff72c362: rgb(253, 219, 28); @supports (color: color(display-p3 1 1 1)) { } }
|
||
@media (prefers-color-scheme: dark) { body { @supports (color: color(display-p3 1 1 1)) { } } }
|
||
|
||
|
||
</style><style>body { --framer-will-change-override: none; } @supports (background: -webkit-named-image(i)) and (not (grid-template-rows: subgrid)) { body { --framer-will-change-override: transform; } } [data-framer-component-type] { position: absolute; } [data-framer-component-type="Text"] { cursor: inherit; } [data-framer-component-text-autosized] * { white-space: pre; }
|
||
[data-framer-component-type="Text"] > * {
|
||
text-align: var(--framer-text-alignment, start);
|
||
}
|
||
[data-framer-component-type="Text"] span span,
|
||
[data-framer-component-type="Text"] p span,
|
||
[data-framer-component-type="Text"] h1 span,
|
||
[data-framer-component-type="Text"] h2 span,
|
||
[data-framer-component-type="Text"] h3 span,
|
||
[data-framer-component-type="Text"] h4 span,
|
||
[data-framer-component-type="Text"] h5 span,
|
||
[data-framer-component-type="Text"] h6 span {
|
||
display: block;
|
||
}
|
||
[data-framer-component-type="Text"] span span span,
|
||
[data-framer-component-type="Text"] p span span,
|
||
[data-framer-component-type="Text"] h1 span span,
|
||
[data-framer-component-type="Text"] h2 span span,
|
||
[data-framer-component-type="Text"] h3 span span,
|
||
[data-framer-component-type="Text"] h4 span span,
|
||
[data-framer-component-type="Text"] h5 span span,
|
||
[data-framer-component-type="Text"] h6 span span {
|
||
display: unset;
|
||
}
|
||
[data-framer-component-type="Text"] div div span,
|
||
[data-framer-component-type="Text"] a div span,
|
||
[data-framer-component-type="Text"] span span span,
|
||
[data-framer-component-type="Text"] p span span,
|
||
[data-framer-component-type="Text"] h1 span span,
|
||
[data-framer-component-type="Text"] h2 span span,
|
||
[data-framer-component-type="Text"] h3 span span,
|
||
[data-framer-component-type="Text"] h4 span span,
|
||
[data-framer-component-type="Text"] h5 span span,
|
||
[data-framer-component-type="Text"] h6 span span,
|
||
[data-framer-component-type="Text"] a {
|
||
font-family: var(--font-family);
|
||
font-style: var(--font-style);
|
||
font-weight: min(calc(var(--framer-font-weight-increase, 0) + var(--font-weight, 400)), 900);
|
||
color: var(--text-color);
|
||
letter-spacing: var(--letter-spacing);
|
||
font-size: var(--font-size);
|
||
text-transform: var(--text-transform);
|
||
--text-decoration: var(--framer-text-decoration-style, solid) var(--framer-text-decoration, none) var(--framer-text-decoration-color, currentcolor) var(--framer-text-decoration-thickness, auto);
|
||
--text-decoration-skip-ink: var(--framer-text-decoration-skip-ink);
|
||
--text-underline-offset: var(--framer-text-decoration-offset);
|
||
line-height: var(--line-height);
|
||
}
|
||
[data-framer-component-type="Text"] div div span,
|
||
[data-framer-component-type="Text"] a div span,
|
||
[data-framer-component-type="Text"] span span span,
|
||
[data-framer-component-type="Text"] p span span,
|
||
[data-framer-component-type="Text"] h1 span span,
|
||
[data-framer-component-type="Text"] h2 span span,
|
||
[data-framer-component-type="Text"] h3 span span,
|
||
[data-framer-component-type="Text"] h4 span span,
|
||
[data-framer-component-type="Text"] h5 span span,
|
||
[data-framer-component-type="Text"] h6 span span,
|
||
[data-framer-component-type="Text"] a {
|
||
--font-family: var(--framer-font-family);
|
||
--font-style: var(--framer-font-style);
|
||
--font-weight: var(--framer-font-weight);
|
||
--text-color: var(--framer-text-color);
|
||
--letter-spacing: var(--framer-letter-spacing);
|
||
--font-size: var(--framer-font-size);
|
||
--text-transform: var(--framer-text-transform);
|
||
--text-decoration: var(--framer-text-decoration-style, solid) var(--framer-text-decoration, none) var(--framer-text-decoration-color, currentcolor) var(--framer-text-decoration-thickness, auto);
|
||
--text-decoration-skip-ink: var(--framer-text-decoration-skip-ink);
|
||
--text-underline-offset: var(--framer-text-decoration-offset);
|
||
--line-height: var(--framer-line-height);
|
||
}
|
||
[data-framer-component-type="Text"] a,
|
||
[data-framer-component-type="Text"] a div span,
|
||
[data-framer-component-type="Text"] a span span span,
|
||
[data-framer-component-type="Text"] a p span span,
|
||
[data-framer-component-type="Text"] a h1 span span,
|
||
[data-framer-component-type="Text"] a h2 span span,
|
||
[data-framer-component-type="Text"] a h3 span span,
|
||
[data-framer-component-type="Text"] a h4 span span,
|
||
[data-framer-component-type="Text"] a h5 span span,
|
||
[data-framer-component-type="Text"] a h6 span span {
|
||
--font-family: var(--framer-link-font-family, var(--framer-font-family));
|
||
--font-style: var(--framer-link-font-style, var(--framer-font-style));
|
||
--font-weight: var(--framer-link-font-weight, var(--framer-font-weight));
|
||
--text-color: var(--framer-link-text-color, var(--framer-text-color));
|
||
--font-size: var(--framer-link-font-size, var(--framer-font-size));
|
||
--text-transform: var(--framer-link-text-transform, var(--framer-text-transform));
|
||
--text-decoration: var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, solid)) var(--framer-link-text-decoration, var(--framer-text-decoration, none)) var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, currentcolor)) var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, auto));
|
||
--text-decoration-skip-ink: var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink));
|
||
--text-underline-offset: var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset));
|
||
}
|
||
[data-framer-component-type="Text"] a:hover,
|
||
[data-framer-component-type="Text"] a div span:hover,
|
||
[data-framer-component-type="Text"] a span span span:hover,
|
||
[data-framer-component-type="Text"] a p span span:hover,
|
||
[data-framer-component-type="Text"] a h1 span span:hover,
|
||
[data-framer-component-type="Text"] a h2 span span:hover,
|
||
[data-framer-component-type="Text"] a h3 span span:hover,
|
||
[data-framer-component-type="Text"] a h4 span span:hover,
|
||
[data-framer-component-type="Text"] a h5 span span:hover,
|
||
[data-framer-component-type="Text"] a h6 span span:hover {
|
||
--font-family: var(--framer-link-hover-font-family, var(--framer-link-font-family, var(--framer-font-family)));
|
||
--font-style: var(--framer-link-hover-font-style, var(--framer-link-font-style, var(--framer-font-style)));
|
||
--font-weight: var(--framer-link-hover-font-weight, var(--framer-link-font-weight, var(--framer-font-weight)));
|
||
--text-color: var(--framer-link-hover-text-color, var(--framer-link-text-color, var(--framer-text-color)));
|
||
--font-size: var(--framer-link-hover-font-size, var(--framer-link-font-size, var(--framer-font-size)));
|
||
--text-transform: var(--framer-link-hover-text-transform, var(--framer-link-text-transform, var(--framer-text-transform)));
|
||
--text-decoration: var(--framer-link-hover-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, solid))) var(--framer-link-hover-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, none))) var(--framer-link-hover-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, currentcolor))) var(--framer-link-hover-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, auto)));
|
||
--text-decoration-skip-ink: var(--framer-link-hover-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink)));
|
||
--text-underline-offset: var(--framer-link-hover-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset)));
|
||
}
|
||
[data-framer-component-type="Text"].isCurrent a,
|
||
[data-framer-component-type="Text"].isCurrent a div span,
|
||
[data-framer-component-type="Text"].isCurrent a span span span,
|
||
[data-framer-component-type="Text"].isCurrent a p span span,
|
||
[data-framer-component-type="Text"].isCurrent a h1 span span,
|
||
[data-framer-component-type="Text"].isCurrent a h2 span span,
|
||
[data-framer-component-type="Text"].isCurrent a h3 span span,
|
||
[data-framer-component-type="Text"].isCurrent a h4 span span,
|
||
[data-framer-component-type="Text"].isCurrent a h5 span span,
|
||
[data-framer-component-type="Text"].isCurrent a h6 span span {
|
||
--font-family: var(--framer-link-current-font-family, var(--framer-link-font-family, var(--framer-font-family)));
|
||
--font-style: var(--framer-link-current-font-style, var(--framer-link-font-style, var(--framer-font-style)));
|
||
--font-weight: var(--framer-link-current-font-weight, var(--framer-link-font-weight, var(--framer-font-weight)));
|
||
--text-color: var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-text-color)));
|
||
--font-size: var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size)));
|
||
--text-transform: var(--framer-link-current-text-transform, var(--framer-link-text-transform, var(--framer-text-transform)));
|
||
--text-decoration: var(--framer-link-current-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, solid))) var(--framer-link-current-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, none))) var(--framer-link-current-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, currentcolor))) var(--framer-link-current-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, auto)));
|
||
--text-decoration-skip-ink: var(--framer-link-current-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink)));
|
||
--text-underline-offset: var(--framer-link-current-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset)));
|
||
}
|
||
[data-framer-component-type="RichTextContainer"] {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
outline: none;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
p.framer-text,
|
||
div.framer-text,
|
||
figure.framer-text,
|
||
h1.framer-text,
|
||
h2.framer-text,
|
||
h3.framer-text,
|
||
h4.framer-text,
|
||
h5.framer-text,
|
||
h6.framer-text,
|
||
ol.framer-text,
|
||
ul.framer-text {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
p.framer-text,
|
||
div.framer-text,
|
||
h1.framer-text,
|
||
h2.framer-text,
|
||
h3.framer-text,
|
||
h4.framer-text,
|
||
h5.framer-text,
|
||
h6.framer-text,
|
||
li.framer-text,
|
||
ol.framer-text,
|
||
ul.framer-text,
|
||
mark.framer-text,
|
||
span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-blockquote-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-blockquote-font-style, var(--framer-font-style, normal));
|
||
font-weight: var(--framer-blockquote-font-weight, var(--framer-font-weight, 400));
|
||
color: var(--framer-blockquote-text-color, var(--framer-text-color, #000));
|
||
font-size: calc(var(--framer-blockquote-font-size, var(--framer-font-size, 16px)) * var(--framer-font-size-scale, 1));
|
||
letter-spacing: var(--framer-blockquote-letter-spacing, var(--framer-letter-spacing, 0));
|
||
text-transform: var(--framer-blockquote-text-transform, var(--framer-text-transform, none));
|
||
text-decoration-line: var(--framer-blockquote-text-decoration, var(--framer-text-decoration, initial));
|
||
text-decoration-style: var(--framer-blockquote-text-decoration-style, var(--framer-text-decoration-style, initial));
|
||
text-decoration-color: var(--framer-blockquote-text-decoration-color, var(--framer-text-decoration-color, initial));
|
||
text-decoration-thickness: var(--framer-blockquote-text-decoration-thickness, var(--framer-text-decoration-thickness, initial));
|
||
text-decoration-skip-ink: var(--framer-blockquote-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink, initial));
|
||
text-underline-offset: var(--framer-blockquote-text-decoration-offset, var(--framer-text-decoration-offset, initial));
|
||
line-height: var(--framer-blockquote-line-height, var(--framer-line-height, 1.2em));
|
||
text-align: var(--framer-blockquote-text-alignment, var(--framer-text-alignment, start));
|
||
-webkit-text-stroke-width: var(--framer-text-stroke-width, initial);
|
||
-webkit-text-stroke-color: var(--framer-text-stroke-color, initial);
|
||
-moz-font-feature-settings: var(--framer-font-open-type-features, initial);
|
||
-webkit-font-feature-settings: var(--framer-font-open-type-features, initial);
|
||
font-feature-settings: var(--framer-font-open-type-features, initial);
|
||
font-variation-settings: var(--framer-font-variation-axes, normal);
|
||
text-wrap: var(--framer-text-wrap-override, var(--framer-text-wrap));
|
||
}
|
||
|
||
mark.framer-text,
|
||
p.framer-text,
|
||
div.framer-text,
|
||
h1.framer-text,
|
||
h2.framer-text,
|
||
h3.framer-text,
|
||
h4.framer-text,
|
||
h5.framer-text,
|
||
h6.framer-text,
|
||
li.framer-text,
|
||
ol.framer-text,
|
||
ul.framer-text {
|
||
background-color: var(--framer-blockquote-text-background-color, var(--framer-text-background-color, initial));
|
||
border-radius: var(--framer-blockquote-text-background-radius, var(--framer-text-background-radius, initial));
|
||
padding: var(--framer-blockquote-text-background-padding, var(--framer-text-background-padding, initial));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
p.framer-text,
|
||
div.framer-text,
|
||
h1.framer-text,
|
||
h2.framer-text,
|
||
h3.framer-text,
|
||
h4.framer-text,
|
||
h5.framer-text,
|
||
h6.framer-text,
|
||
li.framer-text,
|
||
ol.framer-text,
|
||
ul.framer-text,
|
||
span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))));
|
||
-webkit-text-stroke-color: var(--framer-text-stroke-color-rgb, var(--framer-text-stroke-color, initial));
|
||
}
|
||
|
||
mark.framer-text {
|
||
background-color: var(--framer-blockquote-text-background-color-rgb, var(--framer-blockquote-text-background-color, var(--framer-text-background-color-rgb, var(--framer-text-background-color, initial))));
|
||
}
|
||
}
|
||
|
||
.framer-fit-text .framer-text {
|
||
white-space: nowrap;
|
||
white-space-collapse: preserve;
|
||
}
|
||
|
||
strong.framer-text {
|
||
font-family: var(--framer-blockquote-font-family-bold, var(--framer-font-family-bold));
|
||
font-style: var(--framer-blockquote-font-style-bold, var(--framer-font-style-bold));
|
||
font-weight: var(--framer-blockquote-font-weight-bold, var(--framer-font-weight-bold, bolder));
|
||
font-variation-settings: var(--framer-blockquote-font-variation-axes-bold, var(--framer-font-variation-axes-bold));
|
||
}
|
||
|
||
em.framer-text {
|
||
font-family: var(--framer-blockquote-font-family-italic, var(--framer-font-family-italic));
|
||
font-style: var(--framer-blockquote-font-style-italic, var(--framer-font-style-italic, italic));
|
||
font-weight: var(--framer-blockquote-font-weight-italic, var(--framer-font-weight-italic));
|
||
font-variation-settings: var(--framer-blockquote-font-variation-axes-italic, var(--framer-font-variation-axes-italic));
|
||
}
|
||
|
||
em.framer-text > strong.framer-text {
|
||
font-family: var(--framer-blockquote-font-family-bold-italic, var(--framer-font-family-bold-italic));
|
||
font-style: var(--framer-blockquote-font-style-bold-italic, var(--framer-font-style-bold-italic, italic));
|
||
font-weight: var(--framer-blockquote-font-weight-bold-italic, var(--framer-font-weight-bold-italic, bolder));
|
||
font-variation-settings: var(--framer-blockquote-font-variation-axes-bold-italic, var(--framer-font-variation-axes-bold-italic));
|
||
}
|
||
|
||
p.framer-text:not(:first-child),
|
||
div.framer-text:not(:first-child),
|
||
h1.framer-text:not(:first-child),
|
||
h2.framer-text:not(:first-child),
|
||
h3.framer-text:not(:first-child),
|
||
h4.framer-text:not(:first-child),
|
||
h5.framer-text:not(:first-child),
|
||
h6.framer-text:not(:first-child),
|
||
ol.framer-text:not(:first-child),
|
||
ul.framer-text:not(:first-child),
|
||
blockquote.framer-text:not(:first-child),
|
||
table.framer-text:not(:first-child),
|
||
figure.framer-text:not(:first-child),
|
||
.framer-image.framer-text:not(:first-child) {
|
||
margin-top: var(--framer-blockquote-paragraph-spacing, var(--framer-paragraph-spacing, 0));
|
||
}
|
||
|
||
li.framer-text > ul.framer-text:nth-child(2),
|
||
li.framer-text > ol.framer-text:nth-child(2) {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.framer-text[data-text-fill] {
|
||
display: inline-block;
|
||
background-clip: text;
|
||
-webkit-background-clip: text;
|
||
/* make this a transparent color if you want to visualise the clipping */
|
||
-webkit-text-fill-color: transparent;
|
||
padding: max(0em, calc(calc(1.3em - var(--framer-blockquote-line-height, var(--framer-line-height, 1.3em))) / 2));
|
||
margin: min(0em, calc(calc(1.3em - var(--framer-blockquote-line-height, var(--framer-line-height, 1.3em))) / -2));
|
||
}
|
||
|
||
code.framer-text,
|
||
code.framer-text span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-code-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-blockquote-font-style, var(--framer-code-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-blockquote-font-weight, var(--framer-code-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-blockquote-text-color, var(--framer-code-text-color, var(--framer-text-color, #000)));
|
||
font-size: calc(var(--framer-blockquote-font-size, var(--framer-font-size, 16px)) * var(--framer-font-size-scale, 1));
|
||
letter-spacing: var(--framer-blockquote-letter-spacing, var(--framer-letter-spacing, 0));
|
||
line-height: var(--framer-blockquote-line-height, var(--framer-line-height, 1.2em));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
code.framer-text,
|
||
code.framer-text span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-code-text-color-rgb, var(--framer-code-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))));
|
||
}
|
||
}
|
||
|
||
blockquote.framer-text {
|
||
margin-block-start: initial;
|
||
margin-block-end: initial;
|
||
margin-inline-start: initial;
|
||
margin-inline-end: initial;
|
||
unicode-bidi: initial;
|
||
}
|
||
|
||
a.framer-text,
|
||
a.framer-text span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-nested-link],
|
||
span.framer-text[data-nested-link] span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-blockquote-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif)));
|
||
font-style: var(--framer-blockquote-font-style, var(--framer-link-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-blockquote-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-blockquote-text-color, var(--framer-link-text-color, var(--framer-text-color, #000)));
|
||
font-size: calc(var(--framer-blockquote-font-size, var(--framer-font-size, 16px)) * var(--framer-font-size-scale, 1));
|
||
text-transform: var(--framer-blockquote-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none)));
|
||
/* Cursor inherit to overwrite the user agent stylesheet on rich text links. */
|
||
cursor: var(--framer-custom-cursors, pointer);
|
||
/* Don't inherit background styles from any parent text style. */
|
||
background-color: var(--framer-link-text-background-color, initial);
|
||
border-radius: var(--framer-link-text-background-radius, initial);
|
||
padding: var(--framer-link-text-background-padding, initial);
|
||
}
|
||
|
||
a.framer-text,
|
||
span.framer-text[data-nested-link] {
|
||
text-decoration-line: var(--framer-blockquote-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, initial)));
|
||
text-decoration-style: var(--framer-blockquote-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, initial)));
|
||
text-decoration-color: var(--framer-blockquote-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, initial)));
|
||
text-decoration-thickness: var(--framer-blockquote-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, initial)));
|
||
text-decoration-skip-ink: var(--framer-blockquote-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink, initial)));
|
||
text-underline-offset: var(--framer-blockquote-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset, initial)));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
a.framer-text,
|
||
a.framer-text span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-nested-link],
|
||
span.framer-text[data-nested-link] span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))));
|
||
background-color: var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, initial));
|
||
}
|
||
}
|
||
|
||
code.framer-text a.framer-text,
|
||
code.framer-text a.framer-text span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-nested-link],
|
||
code.framer-text span.framer-text[data-nested-link] span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-code-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-blockquote-font-style, var(--framer-code-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-blockquote-font-weight, var(--framer-code-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-blockquote-text-color, var(--framer-link-text-color, var(--framer-code-text-color, var(--framer-text-color, #000))));
|
||
font-size: calc(var(--framer-blockquote-font-size, var(--framer-font-size, 16px)) * var(--framer-font-size-scale, 1));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
code.framer-text a.framer-text,
|
||
code.framer-text a.framer-text span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-nested-link],
|
||
code.framer-text span.framer-text[data-nested-link] span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-code-text-color-rgb, var(--framer-code-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))));
|
||
}
|
||
}
|
||
|
||
a.framer-text:hover,
|
||
a.framer-text:hover span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-nested-link]:hover,
|
||
span.framer-text[data-nested-link]:hover span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-link-hover-font-family, var(--framer-blockquote-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif))));
|
||
font-style: var(--framer-link-hover-font-style, var(--framer-blockquote-font-style, var(--framer-link-font-style, var(--framer-font-style, normal))));
|
||
font-weight: var(--framer-link-hover-font-weight, var(--framer-blockquote-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400))));
|
||
color: var(--framer-link-hover-text-color, var(--framer-blockquote-text-color, var(--framer-link-text-color, var(--framer-text-color, #000))));
|
||
font-size: calc(var(--framer-link-hover-font-size, var(--framer-blockquote-font-size, var(--framer-font-size, 16px))) * var(--framer-font-size-scale, 1));
|
||
text-transform: var(--framer-link-hover-text-transform, var(--framer-blockquote-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none))));
|
||
background-color: var(--framer-link-hover-text-background-color, var(--framer-link-text-background-color, var(--framer-text-background-color, initial)));
|
||
border-radius: var(--framer-link-hover-text-background-radius, var(--framer-link-text-background-radius, var(--framer-text-background-radius, initial)));
|
||
padding: var(--framer-link-hover-text-background-padding, var(--framer-link-text-background-padding, var(--framer-text-background-padding, initial)));
|
||
}
|
||
|
||
a.framer-text:hover,
|
||
span.framer-text[data-nested-link]:hover {
|
||
text-decoration-line: var(--framer-link-hover-text-decoration, var(--framer-blockquote-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, initial))));
|
||
text-decoration-style: var(--framer-link-hover-text-decoration-style, var(--framer-blockquote-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, initial))));
|
||
text-decoration-color: var(--framer-link-hover-text-decoration-color, var(--framer-blockquote-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, initial))));
|
||
text-decoration-thickness: var(--framer-link-hover-text-decoration-thickness, var(--framer-blockquote-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, initial))));
|
||
text-decoration-skip-ink: var(--framer-link-hover-text-decoration-skip-ink, var(--framer-blockquote-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink, initial))));
|
||
text-underline-offset: var(--framer-link-hover-text-decoration-offset, var(--framer-blockquote-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset, initial))));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
a.framer-text:hover,
|
||
a.framer-text:hover span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-nested-link]:hover,
|
||
span.framer-text[data-nested-link]:hover span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-hover-text-color-rgb, var(--framer-link-hover-text-color, var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))));
|
||
background-color: var(--framer-link-hover-text-background-color-rgb, var(--framer-link-hover-text-background-color, var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, var(--framer-text-background-color-rgb, var(--framer-text-background-color, initial))))));
|
||
}
|
||
}
|
||
|
||
code.framer-text a.framer-text:hover,
|
||
code.framer-text a.framer-text:hover span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-nested-link]:hover,
|
||
code.framer-text span.framer-text[data-nested-link]:hover span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-code-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-blockquote-font-style, var(--framer-code-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-blockquote-font-weight, var(--framer-code-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-link-hover-text-color, var(--framer-blockquote-text-color, var(--framer-link-text-color, var(--framer-code-text-color, var(--framer-text-color, #000)))));
|
||
font-size: calc(var(--framer-link-hover-font-size, var(--framer-blockquote-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px)))) * var(--framer-font-size-scale, 1));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
code.framer-text a.framer-text:hover,
|
||
code.framer-text a.framer-text:hover span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-nested-link]:hover,
|
||
code.framer-text span.framer-text[data-nested-link]:hover span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-hover-text-color-rgb, var(--framer-link-hover-text-color, var(--framer-blockquote-text-color-rgb, var(--framer-blockquote-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))));
|
||
}
|
||
}
|
||
|
||
a.framer-text[data-framer-page-link-current],
|
||
a.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-framer-page-link-current],
|
||
span.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-link-current-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif)));
|
||
font-style: var(--framer-link-current-font-style, var(--framer-link-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-link-current-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-text-color, #000)));
|
||
font-size: calc(var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px))) * var(--framer-font-size-scale, 1));
|
||
text-transform: var(--framer-link-current-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none)));
|
||
background-color: var(--framer-link-current-text-background-color, var(--framer-link-text-background-color, initial));
|
||
border-radius: var(--framer-link-current-text-background-radius, var(--framer-link-text-background-radius, initial));
|
||
padding: var(--framer-link-current-text-background-padding, var(--framer-link-text-background-padding, initial));
|
||
}
|
||
|
||
a.framer-text[data-framer-page-link-current],
|
||
span.framer-text[data-framer-page-link-current] {
|
||
text-decoration-line: var(--framer-link-current-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, initial)));
|
||
text-decoration-style: var(--framer-link-current-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, initial)));
|
||
text-decoration-color: var(--framer-link-current-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, initial)));
|
||
text-decoration-thickness: var(--framer-link-current-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, initial)));
|
||
text-decoration-skip-ink: var(--framer-link-current-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink, initial)));
|
||
text-underline-offset: var(--framer-link-current-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset, initial)));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
a.framer-text[data-framer-page-link-current],
|
||
a.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-framer-page-link-current],
|
||
span.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-current-text-color-rgb, var(--framer-link-current-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))));
|
||
background-color: var(--framer-link-current-text-background-color-rgb, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, var(--framer-text-background-color-rgb, var(--framer-text-background-color, initial))))));
|
||
}
|
||
}
|
||
|
||
code.framer-text a.framer-text[data-framer-page-link-current],
|
||
code.framer-text a.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-framer-page-link-current],
|
||
code.framer-text span.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-code-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-code-font-style, var(--framer-font-style, normal));
|
||
font-weight: var(--framer-code-font-weight, var(--framer-font-weight, 400));
|
||
color: var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-code-text-color, var(--framer-text-color, #000))));
|
||
font-size: calc(var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px))) * var(--framer-font-size-scale, 1));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
code.framer-text a.framer-text[data-framer-page-link-current],
|
||
code.framer-text a.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-framer-page-link-current],
|
||
code.framer-text span.framer-text[data-framer-page-link-current] span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-current-text-color-rgb, var(--framer-link-current-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-code-text-color-rgb, var(--framer-code-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))));
|
||
background-color: var(--framer-link-current-text-background-color-rgb, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, var(--framer-text-background-color-rgb, var(--framer-text-background-color, initial))))));
|
||
}
|
||
}
|
||
|
||
a.framer-text[data-framer-page-link-current]:hover,
|
||
a.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-framer-page-link-current]:hover,
|
||
span.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-link-hover-font-family, var(--framer-link-current-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif))));
|
||
font-style: var(--framer-link-hover-font-style, var(--framer-link-current-font-style, var(--framer-link-font-style, var(--framer-font-style, normal))));
|
||
font-weight: var(--framer-link-hover-font-weight, var(--framer-link-current-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400))));
|
||
color: var(--framer-link-hover-text-color, var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-text-color, #000))));
|
||
font-size: calc(var(--framer-link-hover-font-size, var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px)))) * var(--framer-font-size-scale, 1));
|
||
text-transform: var(--framer-link-hover-text-transform, var(--framer-link-current-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none))));
|
||
background-color: var(--framer-link-hover-text-background-color, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color, initial)));
|
||
border-radius: var(--framer-link-hover-text-background-radius, var(--framer-link-current-text-background-radius, var(--framer-link-text-background-radius, initial)));
|
||
padding: var(--framer-link-hover-text-background-padding, var(--framer-link-current-text-background-padding, var(--framer-link-text-background-padding, initial)));
|
||
}
|
||
|
||
a.framer-text[data-framer-page-link-current]:hover,
|
||
span.framer-text[data-framer-page-link-current]:hover {
|
||
text-decoration-line: var(--framer-link-hover-text-decoration, var(--framer-link-current-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, initial))));
|
||
text-decoration-style: var(--framer-link-hover-text-decoration-style, var(--framer-link-current-text-decoration-style, var(--framer-link-text-decoration-style, var(--framer-text-decoration-style, initial))));
|
||
text-decoration-color: var(--framer-link-hover-text-decoration-color, var(--framer-link-current-text-decoration-color, var(--framer-link-text-decoration-color, var(--framer-text-decoration-color, initial))));
|
||
text-decoration-thickness: var(--framer-link-hover-text-decoration-thickness, var(--framer-link-current-text-decoration-thickness, var(--framer-link-text-decoration-thickness, var(--framer-text-decoration-thickness, initial))));
|
||
text-decoration-skip-ink: var(--framer-link-hover-text-decoration-skip-ink, var(--framer-link-current-text-decoration-skip-ink, var(--framer-link-text-decoration-skip-ink, var(--framer-text-decoration-skip-ink, initial))));
|
||
text-underline-offset: var(--framer-link-hover-text-decoration-offset, var(--framer-link-current-text-decoration-offset, var(--framer-link-text-decoration-offset, var(--framer-text-decoration-offset, initial))));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
a.framer-text[data-framer-page-link-current]:hover,
|
||
a.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]),
|
||
span.framer-text[data-framer-page-link-current]:hover,
|
||
span.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-hover-text-color-rgb, var(--framer-link-hover-text-color, var(--framer-link-current-text-color-rgb, var(--framer-link-current-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-code-text-color-rgb, var(--framer-code-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))))));
|
||
background-color: var(--framer-link-hover-text-background-color-rgb, var(--framer-link-hover-text-background-color, var(--framer-link-current-text-background-color-rgb, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, initial))))));
|
||
}
|
||
}
|
||
|
||
code.framer-text a.framer-text[data-framer-page-link-current]:hover,
|
||
code.framer-text a.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-framer-page-link-current]:hover,
|
||
code.framer-text span.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]) {
|
||
font-family: var(--framer-code-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-code-font-style, var(--framer-font-style, normal));
|
||
font-weight: var(--framer-code-font-weight, var(--framer-font-weight, 400));
|
||
color: var(--framer-link-hover-text-color, var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-code-text-color, var(--framer-text-color, #000)))));
|
||
font-size: calc(var(--framer-link-hover-font-size, var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px)))) * var(--framer-font-size-scale, 1));
|
||
background-color: var(--framer-link-hover-text-background-color, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color, var(--framer-text-background-color, initial))));
|
||
border-radius: var(--framer-link-hover-text-background-radius, var(--framer-link-current-text-background-radius, var(--framer-link-text-background-radius, var(--framer-text-background-radius, initial))));
|
||
padding: var(--framer-link-hover-text-background-padding, var(--framer-link-current-text-background-padding, var(--framer-link-text-background-padding, var(--framer-text-background-padding, initial))));
|
||
}
|
||
|
||
@supports not (color: color(display-p3 1 1 1)) {
|
||
code.framer-text a.framer-text[data-framer-page-link-current]:hover,
|
||
code.framer-text a.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]),
|
||
code.framer-text span.framer-text[data-framer-page-link-current]:hover,
|
||
code.framer-text span.framer-text[data-framer-page-link-current]:hover span.framer-text:not([data-text-fill]) {
|
||
color: var(--framer-link-hover-text-color-rgb, var(--framer-link-hover-text-color, var(--framer-link-current-text-color-rgb, var(--framer-link-current-text-color, var(--framer-link-text-color-rgb, var(--framer-link-text-color, var(--framer-code-text-color-rgb, var(--framer-code-text-color, var(--framer-text-color-rgb, var(--framer-text-color, #000))))))))));
|
||
background-color: var(--framer-link-hover-text-background-color-rgb, var(--framer-link-hover-text-background-color, var(--framer-link-current-text-background-color-rgb, var(--framer-link-current-text-background-color, var(--framer-link-text-background-color-rgb, var(--framer-link-text-background-color, initial))))));
|
||
}
|
||
}
|
||
|
||
.framer-image.framer-text {
|
||
display: block;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.text-styles-preset-reset.framer-text {
|
||
--framer-font-family: Inter, Inter Placeholder, sans-serif;
|
||
--framer-font-style: normal;
|
||
--framer-font-weight: 500;
|
||
--framer-text-color: #000;
|
||
--framer-font-size: 16px;
|
||
--framer-letter-spacing: 0;
|
||
--framer-text-transform: none;
|
||
--framer-text-decoration: none;
|
||
--framer-text-decoration-style: none;
|
||
--framer-text-decoration-color: none;
|
||
--framer-text-decoration-thickness: none;
|
||
--framer-text-decoration-skip-ink: none;
|
||
--framer-text-decoration-offset: none;
|
||
--framer-line-height: 1.2em;
|
||
--framer-text-alignment: start;
|
||
--framer-font-open-type-features: normal;
|
||
--framer-text-background-color: initial;
|
||
--framer-text-background-radius: initial;
|
||
--framer-text-background-padding: initial;
|
||
}
|
||
|
||
ol.framer-text {
|
||
--list-style-type: decimal;
|
||
}
|
||
|
||
ul.framer-text,
|
||
ol.framer-text {
|
||
padding-inline-start: 0;
|
||
position: relative;
|
||
}
|
||
|
||
li.framer-text {
|
||
counter-increment: list-item;
|
||
list-style: none;
|
||
padding-inline-start: 2ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text::before {
|
||
position: absolute;
|
||
inset-inline-start: 0;
|
||
content: counter(list-item, var(--list-style-type)) ".";
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 10),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 10) ~ li {
|
||
padding-inline-start: 3ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 100),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 100) ~ li {
|
||
padding-inline-start: 4ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 1000),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 1000) ~ li {
|
||
padding-inline-start: 5ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 10000),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 10000) ~ li {
|
||
padding-inline-start: 6ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 100000),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 100000) ~ li {
|
||
padding-inline-start: 7ch;
|
||
}
|
||
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 1000000),
|
||
ol.framer-text > li.framer-text:nth-last-child(n + 1000000) ~ li {
|
||
padding-inline-start: 8ch;
|
||
}
|
||
|
||
ul.framer-text > li.framer-text::before {
|
||
position: absolute;
|
||
inset-inline-start: 0;
|
||
content: "•";
|
||
}
|
||
|
||
.framer-table-wrapper {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
table.framer-text,
|
||
.framer-table-wrapper table.framer-text {
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
table-layout: auto;
|
||
word-break: normal;
|
||
width: 100%;
|
||
}
|
||
|
||
td.framer-text,
|
||
th.framer-text {
|
||
min-width: 16ch;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.framer-text-module[style*="aspect-ratio"] > :first-child {
|
||
width: 100%;
|
||
}
|
||
|
||
@supports not (aspect-ratio: 1) {
|
||
.framer-text-module[style*="aspect-ratio"] {
|
||
position: relative;
|
||
}
|
||
}
|
||
|
||
@supports not (aspect-ratio: 1) {
|
||
.framer-text-module[style*="aspect-ratio"]::before {
|
||
content: "";
|
||
display: block;
|
||
padding-bottom: calc(100% / calc(var(--aspect-ratio)));
|
||
}
|
||
}
|
||
|
||
@supports not (aspect-ratio: 1) {
|
||
.framer-text-module[style*="aspect-ratio"] > :first-child {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
height: 100%;
|
||
}
|
||
}
|
||
[data-framer-component-type="DeprecatedRichText"] { cursor: inherit; }
|
||
[data-framer-component-type="DeprecatedRichText"] .text-styles-preset-reset {
|
||
--framer-font-family: Inter, Inter Placeholder, sans-serif;
|
||
--framer-font-style: normal;
|
||
--framer-font-weight: 500;
|
||
--framer-text-color: #000;
|
||
--framer-font-size: 16px;
|
||
--framer-letter-spacing: 0;
|
||
--framer-text-transform: none;
|
||
--framer-text-decoration: none;
|
||
--framer-line-height: 1.2em;
|
||
--framer-text-alignment: start;
|
||
--framer-font-open-type-features: normal;
|
||
--font-variation-settings: normal;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] p,
|
||
[data-framer-component-type="DeprecatedRichText"] div,
|
||
[data-framer-component-type="DeprecatedRichText"] h1,
|
||
[data-framer-component-type="DeprecatedRichText"] h2,
|
||
[data-framer-component-type="DeprecatedRichText"] h3,
|
||
[data-framer-component-type="DeprecatedRichText"] h4,
|
||
[data-framer-component-type="DeprecatedRichText"] h5,
|
||
[data-framer-component-type="DeprecatedRichText"] h6 {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] p,
|
||
[data-framer-component-type="DeprecatedRichText"] div,
|
||
[data-framer-component-type="DeprecatedRichText"] h1,
|
||
[data-framer-component-type="DeprecatedRichText"] h2,
|
||
[data-framer-component-type="DeprecatedRichText"] h3,
|
||
[data-framer-component-type="DeprecatedRichText"] h4,
|
||
[data-framer-component-type="DeprecatedRichText"] h5,
|
||
[data-framer-component-type="DeprecatedRichText"] h6,
|
||
[data-framer-component-type="DeprecatedRichText"] li,
|
||
[data-framer-component-type="DeprecatedRichText"] ol,
|
||
[data-framer-component-type="DeprecatedRichText"] ul,
|
||
[data-framer-component-type="DeprecatedRichText"] span:not([data-text-fill]) {
|
||
font-family: var(--framer-font-family, Inter, Inter Placeholder, sans-serif);
|
||
font-style: var(--framer-font-style, normal);
|
||
font-weight: var(--framer-font-weight, 400);
|
||
color: var(--framer-text-color, #000);
|
||
font-size: var(--framer-font-size, 16px);
|
||
letter-spacing: var(--framer-letter-spacing, 0);
|
||
text-transform: var(--framer-text-transform, none);
|
||
text-decoration: var(--framer-text-decoration, none);
|
||
line-height: var(--framer-line-height, 1.2em);
|
||
text-align: var(--framer-text-alignment, start);
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] p:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] div:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h1:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h2:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h3:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h4:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h5:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] h6:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] ol:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] ul:not(:first-child),
|
||
[data-framer-component-type="DeprecatedRichText"] .framer-image:not(:first-child) {
|
||
margin-top: var(--framer-paragraph-spacing, 0);
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] span[data-text-fill] {
|
||
display: inline-block;
|
||
background-clip: text;
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] a,
|
||
[data-framer-component-type="DeprecatedRichText"] a span:not([data-text-fill]) {
|
||
font-family: var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif));
|
||
font-style: var(--framer-link-font-style, var(--framer-font-style, normal));
|
||
font-weight: var(--framer-link-font-weight, var(--framer-font-weight, 400));
|
||
color: var(--framer-link-text-color, var(--framer-text-color, #000));
|
||
font-size: var(--framer-link-font-size, var(--framer-font-size, 16px));
|
||
text-transform: var(--framer-link-text-transform, var(--framer-text-transform, none));
|
||
text-decoration: var(--framer-link-text-decoration, var(--framer-text-decoration, none));
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] a:hover,
|
||
[data-framer-component-type="DeprecatedRichText"] a:hover span:not([data-text-fill]) {
|
||
font-family: var(--framer-link-hover-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif)));
|
||
font-style: var(--framer-link-hover-font-style, var(--framer-link-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-link-hover-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-link-hover-text-color, var(--framer-link-text-color, var(--framer-text-color, #000)));
|
||
font-size: var(--framer-link-hover-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px)));
|
||
text-transform: var(--framer-link-hover-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none)));
|
||
text-decoration: var(--framer-link-hover-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, none)));
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] a[data-framer-page-link-current],
|
||
[data-framer-component-type="DeprecatedRichText"] a[data-framer-page-link-current] span:not([data-text-fill]):not([data-nested-link]) {
|
||
font-family: var(--framer-link-current-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif)));
|
||
font-style: var(--framer-link-current-font-style, var(--framer-link-font-style, var(--framer-font-style, normal)));
|
||
font-weight: var(--framer-link-current-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400)));
|
||
color: var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-text-color, #000)));
|
||
font-size: var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px)));
|
||
text-transform: var(--framer-link-current-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none)));
|
||
text-decoration: var(--framer-link-current-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, none)));
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] a[data-framer-page-link-current]:hover,
|
||
[data-framer-component-type="DeprecatedRichText"] a[data-framer-page-link-current]:hover span:not([data-text-fill]):not([data-nested-link]) {
|
||
font-family: var(--framer-link-hover-font-family, var(--framer-link-current-font-family, var(--framer-link-font-family, var(--framer-font-family, Inter, Inter Placeholder, sans-serif))));
|
||
font-style: var(--framer-link-hover-font-style, var(--framer-link-current-font-style, var(--framer-link-font-style, var(--framer-font-style, normal))));
|
||
font-weight: var(--framer-link-hover-font-weight, var(--framer-link-current-font-weight, var(--framer-link-font-weight, var(--framer-font-weight, 400))));
|
||
color: var(--framer-link-hover-text-color, var(--framer-link-current-text-color, var(--framer-link-text-color, var(--framer-text-color, #000))));
|
||
font-size: var(--framer-link-hover-font-size, var(--framer-link-current-font-size, var(--framer-link-font-size, var(--framer-font-size, 16px))));
|
||
text-transform: var(--framer-link-hover-text-transform, var(--framer-link-current-text-transform, var(--framer-link-text-transform, var(--framer-text-transform, none))));
|
||
text-decoration: var(--framer-link-hover-text-decoration, var(--framer-link-current-text-decoration, var(--framer-link-text-decoration, var(--framer-text-decoration, none))));
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] strong {
|
||
font-weight: bolder;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] em {
|
||
font-style: italic;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] .framer-image {
|
||
display: block;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] ul,
|
||
[data-framer-component-type="DeprecatedRichText"] ol {
|
||
display: table;
|
||
width: 100%;
|
||
padding-left: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] li {
|
||
display: table-row;
|
||
counter-increment: list-item;
|
||
list-style: none;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] ol > li::before {
|
||
display: table-cell;
|
||
width: 2.25ch;
|
||
box-sizing: border-box;
|
||
padding-right: 0.75ch;
|
||
content: counter(list-item) ".";
|
||
white-space: nowrap;
|
||
}
|
||
|
||
[data-framer-component-type="DeprecatedRichText"] ul > li::before {
|
||
display: table-cell;
|
||
width: 2.25ch;
|
||
box-sizing: border-box;
|
||
padding-right: 0.75ch;
|
||
content: "•";
|
||
}
|
||
|
||
:not([data-framer-generated]) > [data-framer-stack-content-wrapper] > *,
|
||
:not([data-framer-generated]) > [data-framer-stack-content-wrapper] > [data-framer-component-type],
|
||
:not([data-framer-generated]) > [data-framer-stack-content-wrapper] > [data-framer-legacy-stack-gap-enabled] > *,
|
||
:not([data-framer-generated]) > [data-framer-stack-content-wrapper] > [data-framer-legacy-stack-gap-enabled] > [data-framer-component-type] {
|
||
position: relative;
|
||
}
|
||
.flexbox-gap-not-supported [data-framer-legacy-stack-gap-enabled="true"] > *, [data-framer-legacy-stack-gap-enabled="true"][data-framer-stack-flexbox-gap="false"] {
|
||
margin-top: calc(var(--stack-gap-y) / 2);
|
||
margin-bottom: calc(var(--stack-gap-y) / 2);
|
||
margin-right: calc(var(--stack-gap-x) / 2);
|
||
margin-left: calc(var(--stack-gap-x) / 2);
|
||
}
|
||
[data-framer-stack-content-wrapper][data-framer-stack-gap-enabled="true"] {
|
||
row-gap: var(--stack-native-row-gap);
|
||
column-gap: var(--stack-native-column-gap);
|
||
} .flexbox-gap-not-supported [data-framer-stack-content-wrapper][data-framer-stack-gap-enabled="true"] {
|
||
row-gap: unset;
|
||
column-gap: unset;
|
||
}
|
||
.flexbox-gap-not-supported
|
||
[data-framer-stack-direction-reverse="false"]
|
||
[data-framer-legacy-stack-gap-enabled="true"]
|
||
> *:first-child,
|
||
[data-framer-stack-direction-reverse="false"]
|
||
[data-framer-legacy-stack-gap-enabled="true"][data-framer-stack-flexbox-gap="false"]
|
||
> *:first-child,
|
||
.flexbox-gap-not-supported
|
||
[data-framer-stack-direction-reverse="true"]
|
||
[data-framer-legacy-stack-gap-enabled="true"]
|
||
> *:last-child,
|
||
[data-framer-stack-direction-reverse="true"]
|
||
[data-framer-legacy-stack-gap-enabled="true"][data-framer-stack-flexbox-gap="false"]
|
||
> *:last-child {
|
||
margin-top: 0;
|
||
margin-left: 0;
|
||
}
|
||
.flexbox-gap-not-supported
|
||
[data-framer-stack-direction-reverse="false"]
|
||
[data-framer-legacy-stack-gap-enabled="true"]
|
||
> *:last-child,
|
||
[data-framer-stack-direction-reverse="false"]
|
||
[data-framer-legacy-stack-gap-enabled="true"][data-framer-stack-flexbox-gap="false"]
|
||
> *:last-child,
|
||
.flexbox-gap-not-supported
|
||
[data-framer-stack-direction-reverse="true"]
|
||
[data-framer-legacy-stack-gap-enabled="true"]
|
||
> *:first-child,
|
||
[data-framer-stack-direction-reverse="true"]
|
||
[data-framer-legacy-stack-gap-enabled="true"][data-framer-stack-flexbox-gap="false"]
|
||
> *:first-child {
|
||
margin-right: 0;
|
||
margin-bottom: 0;
|
||
}
|
||
NavigationContainer
|
||
[data-framer-component-type="NavigationContainer"] > *,
|
||
[data-framer-component-type="NavigationContainer"] > [data-framer-component-type] {
|
||
position: relative;
|
||
} [data-framer-component-type="Scroll"]::-webkit-scrollbar { display: none; } [data-framer-component-type="ScrollContentWrapper"] > * { position: relative; } [data-framer-component-type="NativeScroll"] { -webkit-overflow-scrolling: touch; } [data-framer-component-type="NativeScroll"] > * { position: relative; } [data-framer-component-type="NativeScroll"].direction-both { overflow-x: auto; overflow-y: auto; } [data-framer-component-type="NativeScroll"].direction-vertical { overflow-x: hidden; overflow-y: auto; } [data-framer-component-type="NativeScroll"].direction-horizontal { overflow-x: auto; overflow-y: hidden; } [data-framer-component-type="NativeScroll"].direction-vertical > * { width: 100% !important; } [data-framer-component-type="NativeScroll"].direction-horizontal > * { height: 100% !important; } [data-framer-component-type="NativeScroll"].scrollbar-hidden::-webkit-scrollbar { display: none; } [data-framer-component-type="PageContentWrapper"] > *, [data-framer-component-type="PageContentWrapper"] > [data-framer-component-type] { position: relative; } [data-framer-component-type="DeviceComponent"].no-device > * { width: 100% !important; height: 100% !important; } [data-is-present="false"], [data-is-present="false"] * { pointer-events: none !important; } [data-framer-cursor="pointer"] { cursor: pointer; } [data-framer-cursor="grab"] { cursor: grab; } [data-framer-cursor="grab"]:active { cursor: grabbing; } [data-framer-component-type="Frame"] *, [data-framer-component-type="Stack"] * { pointer-events: auto; } [data-framer-generated] * { pointer-events: unset } .svgContainer svg { display: block; } [data-reset="button"] {
|
||
border-width: 0;
|
||
padding: 0;
|
||
background: none;
|
||
} [data-hide-scrollbars="true"]::-webkit-scrollbar { width: 0px; height: 0px; } [data-hide-scrollbars="true"]::-webkit-scrollbar-thumb { background: transparent; } [data-hide-scrollbars="true"] { scrollbar-width: none; } @supports (not (overflow: clip)) {
|
||
:root { --overflow-clip-fallback: hidden; }
|
||
} .framer-lightbox-container { opacity: 1 !important; pointer-events: auto !important; } @supports (background: -webkit-named-image(i)) and (not (contain-intrinsic-size: inherit)) {
|
||
/* Render block-like elements inline when text is truncated, otherwise default to user agent (revert) */
|
||
div.framer-text, p.framer-text, h1.framer-text, h2.framer-text, h3.framer-text, h4.framer-text, h5.framer-text, h6.framer-text, ol.framer-text, ul.framer-text, li.framer-text, blockquote.framer-text, .framer-text.framer-image { display: var(--text-truncation-display-inline-for-safari-16, revert) }
|
||
|
||
/* Add a line break after each block-like element that we render inline, to resemble the block-like behavior */
|
||
div.framer-text::after, p.framer-text::after, h1.framer-text::after, h2.framer-text::after, h3.framer-text::after, h4.framer-text::after, h5.framer-text::after, h6.framer-text::after, ol.framer-text::after, ul.framer-text::after, li.framer-text::after, blockquote.framer-text::after, .framer-text.framer-image::after { content: var(--text-truncation-line-break-for-safari-16); white-space: pre; }
|
||
|
||
/* Don't render modules (e.g. videos, code-blocks), or tables when text is truncated, because often these can't be truncated and their children might be block elements */
|
||
.framer-text.framer-text-module,
|
||
.framer-text.framer-table-wrapper { display: var(--text-truncation-display-none-for-safari-16, revert) }
|
||
|
||
/* Render text-fill elements inline when text is truncated, otherwise default to their default value (e.g. inline-block) */
|
||
p.framer-text[data-text-fill] { display: var(--text-truncation-display-inline-for-safari-16, inline-block) }
|
||
}</style><style>.framer-cursor-none, .framer-cursor-none * { cursor: none !important; }
|
||
.framer-pointer-events-none, .framer-pointer-events-none * { pointer-events: none !important; }</style><style>@supports (aspect-ratio: 1) { body { --framer-aspect-ratio-supported: auto; } }
|
||
.framer-iwu1g.framer-1v1cbx9, .framer-iwu1g .framer-1v1cbx9 { display: block; }
|
||
.framer-iwu1g.framer-1f4b9ar { align-content: center; align-items: center; background-color: #0c1235; display: flex; flex-direction: column; flex-wrap: nowrap; gap: 160px; height: min-content; justify-content: flex-start; overflow: var(--overflow-clip-fallback, clip); padding: 0px 0px 140px 0px; position: relative; width: 1440px; }
|
||
.framer-iwu1g .framer-1jw3r6s-container { flex: none; height: auto; position: relative; width: 100%; }
|
||
.framer-iwu1g .framer-695qvr { align-content: flex-start; align-items: flex-start; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 120px; height: min-content; justify-content: center; overflow: var(--overflow-clip-fallback, clip); padding: 0px; position: relative; width: 90%; }
|
||
.framer-iwu1g .framer-rsgex4 { flex: none; height: 87px; overflow: var(--overflow-clip-fallback, clip); position: relative; width: 20%; }
|
||
.framer-iwu1g .framer-1q6tkmr { flex: none; height: auto; left: 50%; position: absolute; top: 0px; transform: translateX(-50%); white-space: pre; width: auto; }
|
||
.framer-iwu1g .framer-10i7rne { align-content: flex-start; align-items: flex-start; display: flex; flex: 1 0 0px; flex-direction: column; flex-wrap: nowrap; gap: 60px; height: min-content; justify-content: flex-start; max-width: 100%; padding: 0px; position: relative; width: 1px; }
|
||
.framer-iwu1g .framer-1ki6j2f { align-content: flex-start; align-items: flex-start; display: flex; flex: none; flex-direction: column; flex-wrap: nowrap; gap: 15px; height: min-content; justify-content: flex-start; max-width: 100%; padding: 0px; position: relative; width: 100%; }
|
||
.framer-iwu1g .framer-1hhg6mn, .framer-iwu1g .framer-q6iizf, .framer-iwu1g .framer-wlugre, .framer-iwu1g .framer-7mb3ft { flex: none; height: auto; max-width: 600px; position: relative; white-space: pre-wrap; width: 100%; word-break: break-word; word-wrap: break-word; }
|
||
.framer-iwu1g .framer-8a2lbe { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 10px; height: min-content; justify-content: center; overflow: var(--overflow-clip-fallback, clip); padding: 0px; position: relative; width: 600px; }
|
||
.framer-iwu1g .framer-1gfmqvt { aspect-ratio: 1 / 1; border-bottom-left-radius: 60px; border-bottom-right-radius: 60px; border-top-left-radius: 60px; border-top-right-radius: 60px; flex: none; height: var(--framer-aspect-ratio-supported, 64px); position: relative; width: 64px; }
|
||
.framer-iwu1g .framer-gkfqux { align-content: center; align-items: center; display: flex; flex: 1 0 0px; flex-direction: column; flex-wrap: nowrap; gap: 4px; height: 80px; justify-content: center; overflow: var(--overflow-clip-fallback, clip); padding: 0px; position: relative; width: 1px; }
|
||
.framer-iwu1g .framer-4g90hh { flex: none; height: 400px; position: relative; width: 80%; }
|
||
.framer-iwu1g .framer-e1t4z3 { --framer-paragraph-spacing: 32px; flex: none; height: auto; max-width: 800px; position: relative; white-space: pre-wrap; width: 80%; word-break: break-word; word-wrap: break-word; }
|
||
.framer-iwu1g .framer-1dl4bqz { --framer-paragraph-spacing: 32px; flex: none; height: auto; max-width: 600px; position: relative; white-space: pre-wrap; width: 100%; word-break: break-word; word-wrap: break-word; }
|
||
.framer-iwu1g .framer-1ntbddf { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 10px; height: min-content; justify-content: center; max-width: 600px; padding: 0px; position: relative; width: 100%; }
|
||
.framer-iwu1g .framer-1lyp7e4, .framer-iwu1g .framer-r1lrfl { flex: none; height: auto; position: relative; white-space: pre; width: auto; }
|
||
.framer-iwu1g .framer-gf8tl1 { align-self: stretch; flex: 1 0 0px; height: auto; position: relative; width: 1px; }
|
||
.framer-Jwf7E .framer-styles-preset-1pof2ao:not(.rich-text-wrapper), .framer-Jwf7E .framer-styles-preset-1pof2ao.rich-text-wrapper h5 { --framer-font-family: "Space Grotesk", "Space Grotesk Placeholder", sans-serif; --framer-font-open-type-features: normal; --framer-font-size: 24px; --framer-font-style: normal; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 117%; --framer-paragraph-spacing: 40px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: uppercase; }
|
||
.framer-W2ScL .framer-styles-preset-1ig7lvc:not(.rich-text-wrapper), .framer-W2ScL .framer-styles-preset-1ig7lvc.rich-text-wrapper a { --framer-link-hover-text-color: var(--token-16ff4860-3fc9-42a6-86bb-5867ff72c362, #fddb1c); --framer-link-text-color: #ffffff; }
|
||
.framer-2ISu1 .framer-styles-preset-ivdbsg:not(.rich-text-wrapper), .framer-2ISu1 .framer-styles-preset-ivdbsg.rich-text-wrapper p { --framer-font-family: "Suisse Int'l Regular", "Suisse Int'l Regular Placeholder", sans-serif; --framer-font-open-type-features: 'blwf' on, 'cv09' on, 'cv03' on, 'cv04' on, 'cv11' on; --framer-font-size: 20px; --framer-font-style: normal; --framer-font-variation-axes: normal; --framer-font-weight: 400; --framer-letter-spacing: -0.02em; --framer-line-height: 1.6em; --framer-paragraph-spacing: 20px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-l9VZC .framer-styles-preset-y1p1zx:not(.rich-text-wrapper), .framer-l9VZC .framer-styles-preset-y1p1zx.rich-text-wrapper h1 { --framer-font-family: "Space Grotesk", "Space Grotesk Placeholder", sans-serif; --framer-font-open-type-features: normal; --framer-font-size: 56px; --framer-font-style: normal; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 117%; --framer-paragraph-spacing: 0px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-gP2PZ .framer-styles-preset-1pyagxk:not(.rich-text-wrapper), .framer-gP2PZ .framer-styles-preset-1pyagxk.rich-text-wrapper h2 { --framer-font-family: "Inter", "Inter Placeholder", sans-serif; --framer-font-family-bold: "Inter", sans-serif; --framer-font-family-bold-italic: "Inter", sans-serif; --framer-font-family-italic: "Inter", "Inter Placeholder", sans-serif; --framer-font-open-type-features: 'blwf' on, 'cv09' on, 'cv03' on, 'cv04' on, 'cv11' on; --framer-font-size: 30px; --framer-font-style: normal; --framer-font-style-bold: normal; --framer-font-style-bold-italic: italic; --framer-font-style-italic: italic; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-font-weight-bold: 900; --framer-font-weight-bold-italic: 900; --framer-font-weight-italic: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 1.4em; --framer-paragraph-spacing: 40px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-SpJQu .framer-styles-preset-1tjgqms:not(.rich-text-wrapper), .framer-SpJQu .framer-styles-preset-1tjgqms.rich-text-wrapper h4 { --framer-font-family: "Space Grotesk", "Space Grotesk Placeholder", sans-serif; --framer-font-family-bold: "Inter", sans-serif; --framer-font-family-bold-italic: "Inter", sans-serif; --framer-font-family-italic: "Inter", "Inter Placeholder", sans-serif; --framer-font-open-type-features: normal; --framer-font-size: 32px; --framer-font-style: normal; --framer-font-style-bold: normal; --framer-font-style-bold-italic: italic; --framer-font-style-italic: italic; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-font-weight-bold: 900; --framer-font-weight-bold-italic: 900; --framer-font-weight-italic: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 117%; --framer-paragraph-spacing: 40px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-Vi77g .framer-styles-preset-is84e0:not(.rich-text-wrapper), .framer-Vi77g .framer-styles-preset-is84e0.rich-text-wrapper h6 { --framer-font-family: "Inter", "Inter Placeholder", sans-serif; --framer-font-family-bold: "Inter", "Inter Placeholder", sans-serif; --framer-font-family-bold-italic: "Inter", "Inter Placeholder", sans-serif; --framer-font-family-italic: "Inter", "Inter Placeholder", sans-serif; --framer-font-open-type-features: 'blwf' on, 'cv09' on, 'cv03' on, 'cv04' on, 'cv11' on; --framer-font-size: 16px; --framer-font-style: normal; --framer-font-style-bold: normal; --framer-font-style-bold-italic: italic; --framer-font-style-italic: italic; --framer-font-variation-axes: normal; --framer-font-weight: 600; --framer-font-weight-bold: 700; --framer-font-weight-bold-italic: 700; --framer-font-weight-italic: 600; --framer-letter-spacing: -0.02em; --framer-line-height: 1.4em; --framer-paragraph-spacing: 40px; --framer-text-alignment: start; --framer-text-color: #000000; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-NnO4D .framer-styles-preset-wtpq7e:not(.rich-text-wrapper), .framer-NnO4D .framer-styles-preset-wtpq7e.rich-text-wrapper a { --framer-link-text-color: #bdfbff; --framer-link-text-decoration: underline; --framer-link-text-decoration-offset: 3px; }
|
||
.framer-Fwfi0 .framer-styles-preset-go1021:not(.rich-text-wrapper), .framer-Fwfi0 .framer-styles-preset-go1021.rich-text-wrapper blockquote { --framer-blockquote-font-family: "Inter", "Inter Placeholder", sans-serif; --framer-blockquote-font-family-bold: "Inter", "Inter Placeholder", sans-serif; --framer-blockquote-font-family-bold-italic: "Inter", "Inter Placeholder", sans-serif; --framer-blockquote-font-family-italic: "Inter", "Inter Placeholder", sans-serif; --framer-blockquote-font-size: 16px; --framer-blockquote-font-style: italic; --framer-blockquote-font-style-bold: normal; --framer-blockquote-font-style-bold-italic: italic; --framer-blockquote-font-style-italic: italic; --framer-blockquote-font-variation-axes: normal; --framer-blockquote-font-weight: 400; --framer-blockquote-font-weight-bold: 700; --framer-blockquote-font-weight-bold-italic: 700; --framer-blockquote-font-weight-italic: 400; --framer-blockquote-letter-spacing: 0em; --framer-blockquote-line-height: 1.8em; --framer-blockquote-paragraph-spacing: 20px; --framer-blockquote-text-color: #999999; --framer-blockquote-text-stroke-color: initial; --framer-blockquote-text-stroke-width: initial; --framer-font-open-type-features: normal; padding: 0px 0px 0px 22px; position: relative; }
|
||
.framer-Fwfi0 .framer-styles-preset-go1021:not(.rich-text-wrapper)::before, .framer-Fwfi0 .framer-styles-preset-go1021.rich-text-wrapper blockquote::before { background-color: #ddd; border-radius: 1px; content: " "; display: block; height: 100%; left: 0px; position: absolute; top: 0px; width: 2px; }
|
||
.framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper), .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table { border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; border-color: rgba(153, 153, 153, 0.25); border-style: solid; border-top-left-radius: 8px; border-top-right-radius: 8px; border-width: 1px; overflow: hidden; }
|
||
.framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) th, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table th, .framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) td, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table td { padding: 10px; }
|
||
.framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) th, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table th { background-color: rgba(153, 153, 153, 0.1); }
|
||
.framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) tr + tr td, .framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) tr + tr th, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table tr + tr td, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table tr + tr th { border-top-color: rgba(153, 153, 153, 0.25); border-top-style: solid; border-top-width: 1px; }
|
||
.framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) td + td, .framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) th + th, .framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) td + th, .framer-3iZg2 .framer-styles-preset-1m92a5q:not(.rich-text-wrapper) th + td, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table td + td, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table th + th, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table td + th, .framer-3iZg2 .framer-styles-preset-1m92a5q.rich-text-wrapper table th + td { border-left-color: rgba(153, 153, 153, 0.25); border-left-style: solid; border-left-width: 1px; }
|
||
.framer-jNrE7 .framer-styles-preset-1hyt0we:not(.rich-text-wrapper), .framer-jNrE7 .framer-styles-preset-1hyt0we.rich-text-wrapper code { --framer-code-font-family: "CUSTOM;Suisse Int'l Mono Regular", monospace; --framer-code-font-style: normal; --framer-code-font-weight: 400; --framer-code-text-color: #98256a; --framer-font-size-scale: 0.88; background-color: rgba(152, 37, 106, 0.24); border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; border-top-left-radius: 8px; border-top-right-radius: 8px; padding-bottom: 0.2em; padding-left: 0.3em; padding-right: 0.3em; padding-top: 0.2em; }
|
||
.framer-J6O1U .framer-styles-preset-17grp1i { }
|
||
.framer-q9X0b .framer-styles-preset-wnzxc5:not(.rich-text-wrapper), .framer-q9X0b .framer-styles-preset-wnzxc5.rich-text-wrapper h3 { --framer-font-family: "Space Grotesk", "Space Grotesk Placeholder", sans-serif; --framer-font-open-type-features: normal; --framer-font-size: 36px; --framer-font-style: normal; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 117%; --framer-paragraph-spacing: 40px; --framer-text-alignment: left; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: none; }
|
||
.framer-CWcyA .framer-styles-preset-1mbxhs3:not(.rich-text-wrapper), .framer-CWcyA .framer-styles-preset-1mbxhs3.rich-text-wrapper h4 { --framer-font-family: "Space Grotesk", "Space Grotesk Placeholder", sans-serif; --framer-font-open-type-features: normal; --framer-font-size: 32px; --framer-font-style: normal; --framer-font-variation-axes: normal; --framer-font-weight: 700; --framer-letter-spacing: -0.04em; --framer-line-height: 117%; --framer-paragraph-spacing: 40px; --framer-text-alignment: start; --framer-text-color: #ffffff; --framer-text-decoration: none; --framer-text-stroke-color: initial; --framer-text-stroke-width: initial; --framer-text-transform: uppercase; }
|
||
@media (min-width: 1200px) and (max-width: 1439.98px) { .framer-iwu1g.framer-1f4b9ar { width: 1200px; } .framer-iwu1g .framer-695qvr { gap: 80px; } .framer-iwu1g .framer-4g90hh { width: 90%; }}
|
||
@media (min-width: 810px) and (max-width: 1199.98px) { .framer-iwu1g.framer-1f4b9ar { width: 810px; } .framer-iwu1g .framer-695qvr { align-content: center; align-items: center; flex-direction: column; gap: 80px; } .framer-iwu1g .framer-rsgex4, .framer-iwu1g .framer-4g90hh, .framer-iwu1g .framer-e1t4z3 { width: 100%; } .framer-iwu1g .framer-1q6tkmr { white-space: pre-wrap; width: 98%; word-break: break-word; word-wrap: break-word; } .framer-iwu1g .framer-10i7rne { flex: none; max-width: 90%; width: 100%; }}
|
||
@media (max-width: 809.98px) { .framer-iwu1g.framer-1f4b9ar { width: 390px; } .framer-iwu1g .framer-695qvr { align-content: center; align-items: center; flex-direction: column; gap: 80px; } .framer-iwu1g .framer-rsgex4, .framer-iwu1g .framer-e1t4z3 { width: 100%; } .framer-iwu1g .framer-1q6tkmr { white-space: pre-wrap; width: 98%; word-break: break-word; word-wrap: break-word; } .framer-iwu1g .framer-10i7rne { flex: none; height: 16423px; max-width: 90%; width: 100%; } .framer-iwu1g .framer-4g90hh { height: 176px; width: 100%; }}</style><style>.ssr-variant { display: contents }</style><style>@supports (aspect-ratio: 1) { body { --framer-aspect-ratio-supported: auto; } }
|
||
.framer-B0Zy3.framer-mqx3xp, .framer-B0Zy3 .framer-mqx3xp { display: block; }
|
||
.framer-B0Zy3.framer-7tb29w { align-content: center; align-items: center; display: flex; flex-direction: row; flex-wrap: nowrap; height: min-content; justify-content: space-between; overflow: visible; padding: 24px 80px 24px 80px; position: relative; width: 1440px; }
|
||
.framer-B0Zy3 .framer-ae3s53 { align-content: center; align-items: center; display: flex; flex: none; flex-direction: column; flex-wrap: nowrap; gap: 10px; height: min-content; justify-content: center; overflow: visible; padding: 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-146rl1k-container { flex: none; height: auto; position: relative; width: auto; }
|
||
.framer-B0Zy3 .framer-kbz1xf { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 10px; height: min-content; justify-content: center; overflow: visible; padding: 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-1s0e3pk { flex: none; height: auto; position: relative; white-space: pre; width: auto; }
|
||
.framer-B0Zy3 .framer-357n6m { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 32px; height: min-content; justify-content: flex-start; overflow: visible; padding: 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-xdnazc { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 48px; height: min-content; justify-content: flex-start; overflow: visible; padding: 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-4aolzb, .framer-B0Zy3 .framer-esdrld { align-content: flex-start; align-items: flex-start; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 0px; height: min-content; justify-content: flex-start; overflow: visible; padding: 12px 0px 12px 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-n2dwnp-container, .framer-B0Zy3 .framer-1lqcgtr-container, .framer-B0Zy3 .framer-mi4ve3-container { flex: none; height: 20px; position: relative; width: auto; }
|
||
.framer-B0Zy3 .framer-5fo7hl { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 4px; height: min-content; justify-content: flex-start; overflow: visible; padding: 12px 0px 12px 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-1d7i85n { align-content: center; align-items: center; display: flex; flex: none; flex-direction: row; flex-wrap: nowrap; gap: 8px; height: min-content; justify-content: center; overflow: visible; padding: 0px; position: relative; width: min-content; }
|
||
.framer-B0Zy3 .framer-luav3i-container { flex: none; height: 24px; position: relative; width: 25px; }
|
||
.framer-B0Zy3.framer-v-4ja6tg.framer-7tb29w { padding: 24px 40px 24px 40px; }
|
||
.framer-B0Zy3.framer-v-cm15kg.framer-7tb29w { padding: 24px 40px 24px 40px; width: 390px; }
|
||
.framer-B0Zy3.framer-v-cm15kg .framer-ae3s53 { flex: 1 0 0px; flex-direction: row; gap: unset; justify-content: space-between; width: 1px; }
|
||
.framer-B0Zy3.framer-v-cm15kg .framer-146rl1k-container { order: 1; z-index: 1; }
|
||
.framer-B0Zy3.framer-v-cm15kg .framer-kbz1xf, .framer-B0Zy3.framer-v-1wgp1z6 .framer-kbz1xf { order: 0; }
|
||
.framer-B0Zy3.framer-v-1wgp1z6.framer-7tb29w { align-content: flex-start; align-items: flex-start; flex-direction: column; gap: 40px; justify-content: center; padding: 24px 40px 24px 40px; width: 390px; }
|
||
.framer-B0Zy3.framer-v-1wgp1z6 .framer-ae3s53 { flex-direction: row; gap: unset; height: 42px; justify-content: space-between; width: 100%; }
|
||
.framer-B0Zy3.framer-v-1wgp1z6 .framer-146rl1k-container { order: 1; }
|
||
.framer-B0Zy3.framer-v-1wgp1z6 .framer-357n6m { flex-direction: column; }
|
||
.framer-B0Zy3.framer-v-1wgp1z6 .framer-xdnazc { align-content: flex-start; align-items: flex-start; flex-direction: column; gap: 8px; }
|
||
.framer-EdnDn .framer-styles-preset-1gaq3q8:not(.rich-text-wrapper), .framer-EdnDn .framer-styles-preset-1gaq3q8.rich-text-wrapper a { --framer-link-text-color: #ffffff; }</style><style>@supports (aspect-ratio: 1) { body { --framer-aspect-ratio-supported: auto; } }
|
||
.framer-azFTY.framer-1q0urgz, .framer-azFTY .framer-1q0urgz { display: block; }
|
||
.framer-azFTY.framer-1phi7jx { align-content: center; align-items: center; cursor: pointer; display: flex; flex-direction: row; flex-wrap: nowrap; gap: 10px; height: 20px; justify-content: center; overflow: var(--overflow-clip-fallback, clip); padding: 0px; position: relative; text-decoration: none; width: min-content; }
|
||
.framer-azFTY .framer-1l02ivz { flex: none; height: auto; position: relative; white-space: pre; width: auto; }</style><style>@supports (aspect-ratio: 1) { body { --framer-aspect-ratio-supported: auto; } }
|
||
.framer-8s6rc.framer-11qa0re, .framer-8s6rc .framer-11qa0re { display: block; }
|
||
.framer-8s6rc.framer-eeze19 { cursor: pointer; height: 24px; overflow: var(--overflow-clip-fallback, clip); position: relative; text-decoration: none; width: 25px; }
|
||
.framer-8s6rc .framer-xb934c { height: 24px; left: 0px; position: absolute; top: 0px; width: 25px; }</style><style>@supports (aspect-ratio: 1) { body { --framer-aspect-ratio-supported: auto; } }
|
||
.framer-PqHEJ.framer-g0j324, .framer-PqHEJ .framer-g0j324 { display: block; }
|
||
.framer-PqHEJ.framer-aejhsi { align-content: center; align-items: center; cursor: pointer; display: flex; flex-direction: column; flex-wrap: nowrap; gap: 10px; height: min-content; justify-content: center; padding: 0px; position: relative; width: min-content; }
|
||
.framer-PqHEJ .framer-ayk48a { aspect-ratio: 1.3333333333333333 / 1; flex: none; height: var(--framer-aspect-ratio-supported, 24px); overflow: visible; position: relative; width: 32px; }
|
||
.framer-PqHEJ .framer-1jnwuhd { bottom: 0px; flex: none; height: 2px; left: 0px; overflow: hidden; position: absolute; right: 0px; }
|
||
.framer-PqHEJ .framer-1oxwytt { flex: none; height: 2px; left: 0px; overflow: hidden; position: absolute; right: 0px; top: calc(50.00000000000002% - 2px / 2); }
|
||
.framer-PqHEJ .framer-1ediuhh { flex: none; height: 2px; left: 0px; overflow: hidden; position: absolute; right: 0px; top: 0px; }
|
||
.framer-PqHEJ.framer-v-ul6kas .framer-1jnwuhd, .framer-PqHEJ.framer-v-ul6kas .framer-1ediuhh { bottom: 11px; height: unset; top: 11px; }</style><style>.framer-cb .sp-pre-placeholder {
|
||
padding: var(--cb-padding) !important;
|
||
margin: 0 !important;
|
||
width: max-content;
|
||
}
|
||
.framer-cb .cm-scroller {
|
||
display: unset !important;
|
||
padding: 0 !important;
|
||
}
|
||
.framer-cb .cm-content {
|
||
padding: var(--cb-padding) !important;
|
||
width: max-content;
|
||
}
|
||
.framer-cb .sp-wrapper {
|
||
color-scheme: var(--cb-color-scheme, inherit);
|
||
}
|
||
@media screen and (max-width: 768px) {
|
||
@supports (-webkit-overflow-scrolling: touch) {
|
||
.cb-code-editor .cm-content,
|
||
.cb-code-editor .sp-pre-placeholder {
|
||
font-size: var(--sp-font-size, inherit);
|
||
-webkit-text-size-adjust: 100%;
|
||
}
|
||
}
|
||
}
|
||
@media screen and (max-width: 768px) {
|
||
.framer-cb .sp-editor-viewer.sp-stack {
|
||
height: 100%;
|
||
}
|
||
}
|
||
.framer-cb {
|
||
|
||
/* Colors */
|
||
--cb-colors-surface1: var(--cb-colors-surface1-light);
|
||
--cb-colors-surface2: var(--cb-colors-surface2-light);
|
||
--cb-colors-surface3: var(--cb-colors-surface3-light);
|
||
--cb-colors-disabled: var(--cb-colors-disabled-light);
|
||
--cb-colors-base: var(--cb-colors-base-light);
|
||
--cb-colors-clickable: var(--cb-colors-clickable-light);
|
||
--cb-colors-hover: var(--cb-colors-hover-light);
|
||
--cb-colors-accent: var(--cb-colors-accent-light);
|
||
--cb-colors-error: var(--cb-colors-error-light);
|
||
--cb-colors-errorSurface: var(--cb-colors-errorSurface-light);
|
||
--cb-colors-warning: var(--cb-colors-warning-light);
|
||
--cb-colors-warningSurface: var(--cb-colors-warningSurface-light);
|
||
|
||
/* Syntax */
|
||
--cb-syntax-color-plain: var(--cb-syntax-color-plain-light);
|
||
--cb-syntax-color-comment: var(--cb-syntax-color-comment-light);
|
||
--cb-syntax-color-keyword: var(--cb-syntax-color-keyword-light);
|
||
--cb-syntax-color-definition: var(--cb-syntax-color-definition-light);
|
||
--cb-syntax-color-punctuation: var(--cb-syntax-color-punctuation-light);
|
||
--cb-syntax-color-property: var(--cb-syntax-color-property-light);
|
||
--cb-syntax-color-tag: var(--cb-syntax-color-tag-light);
|
||
--cb-syntax-color-static: var(--cb-syntax-color-static-light);
|
||
--cb-syntax-color-string: var(--cb-syntax-color-string-light);
|
||
|
||
/* Other */
|
||
--cb-color-scheme: var(--cb-color-scheme-light);
|
||
|
||
}
|
||
body[data-framer-theme="dark"] .framer-cb {
|
||
|
||
/* Colors */
|
||
--cb-colors-surface1: var(--cb-colors-surface1-dark);
|
||
--cb-colors-surface2: var(--cb-colors-surface2-dark);
|
||
--cb-colors-surface3: var(--cb-colors-surface3-dark);
|
||
--cb-colors-disabled: var(--cb-colors-disabled-dark);
|
||
--cb-colors-base: var(--cb-colors-base-dark);
|
||
--cb-colors-clickable: var(--cb-colors-clickable-dark);
|
||
--cb-colors-hover: var(--cb-colors-hover-dark);
|
||
--cb-colors-accent: var(--cb-colors-accent-dark);
|
||
--cb-colors-error: var(--cb-colors-error-dark);
|
||
--cb-colors-errorSurface: var(--cb-colors-errorSurface-dark);
|
||
--cb-colors-warning: var(--cb-colors-warning-dark);
|
||
--cb-colors-warningSurface: var(--cb-colors-warningSurface-dark);
|
||
|
||
/* Syntax */
|
||
--cb-syntax-color-plain: var(--cb-syntax-color-plain-dark);
|
||
--cb-syntax-color-comment: var(--cb-syntax-color-comment-dark);
|
||
--cb-syntax-color-keyword: var(--cb-syntax-color-keyword-dark);
|
||
--cb-syntax-color-definition: var(--cb-syntax-color-definition-dark);
|
||
--cb-syntax-color-punctuation: var(--cb-syntax-color-punctuation-dark);
|
||
--cb-syntax-color-property: var(--cb-syntax-color-property-dark);
|
||
--cb-syntax-color-tag: var(--cb-syntax-color-tag-dark);
|
||
--cb-syntax-color-static: var(--cb-syntax-color-static-dark);
|
||
--cb-syntax-color-string: var(--cb-syntax-color-string-dark);
|
||
|
||
/* Other */
|
||
--cb-color-scheme: var(--cb-color-scheme-dark);
|
||
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
body:not([data-framer-theme]) .framer-cb {
|
||
|
||
/* Colors */
|
||
--cb-colors-surface1: var(--cb-colors-surface1-dark);
|
||
--cb-colors-surface2: var(--cb-colors-surface2-dark);
|
||
--cb-colors-surface3: var(--cb-colors-surface3-dark);
|
||
--cb-colors-disabled: var(--cb-colors-disabled-dark);
|
||
--cb-colors-base: var(--cb-colors-base-dark);
|
||
--cb-colors-clickable: var(--cb-colors-clickable-dark);
|
||
--cb-colors-hover: var(--cb-colors-hover-dark);
|
||
--cb-colors-accent: var(--cb-colors-accent-dark);
|
||
--cb-colors-error: var(--cb-colors-error-dark);
|
||
--cb-colors-errorSurface: var(--cb-colors-errorSurface-dark);
|
||
--cb-colors-warning: var(--cb-colors-warning-dark);
|
||
--cb-colors-warningSurface: var(--cb-colors-warningSurface-dark);
|
||
|
||
/* Syntax */
|
||
--cb-syntax-color-plain: var(--cb-syntax-color-plain-dark);
|
||
--cb-syntax-color-comment: var(--cb-syntax-color-comment-dark);
|
||
--cb-syntax-color-keyword: var(--cb-syntax-color-keyword-dark);
|
||
--cb-syntax-color-definition: var(--cb-syntax-color-definition-dark);
|
||
--cb-syntax-color-punctuation: var(--cb-syntax-color-punctuation-dark);
|
||
--cb-syntax-color-property: var(--cb-syntax-color-property-dark);
|
||
--cb-syntax-color-tag: var(--cb-syntax-color-tag-dark);
|
||
--cb-syntax-color-static: var(--cb-syntax-color-static-dark);
|
||
--cb-syntax-color-string: var(--cb-syntax-color-string-dark);
|
||
|
||
/* Other */
|
||
--cb-color-scheme: var(--cb-color-scheme-dark);
|
||
|
||
}
|
||
}
|
||
@supports not (color(display-p3 1 1 1)) {
|
||
--cb-custom-background: var(--cb-background-rgb)
|
||
}</style><!-- Start of headEnd -->
|
||
<script>
|
||
(function(a,p,t,i,b,l,e) {
|
||
a.aptible=a.aptible||{_q:[]};f='event,identify'.split(',');
|
||
l=function(n){return function(){a.aptible._q.push([n,Array.prototype.slice.call(arguments)])}};
|
||
for(e=0;e<f.length;e++){a.aptible[f[e]]=l(f[e])};
|
||
i=p.createElement(t);i.type='text/javascript';i.async=1;i.src="https://minnow.unpage.ai/minnow.js";
|
||
b=p.getElementsByTagName(t)[0];b.parentNode.insertBefore(i,b);
|
||
})(window, document, 'script');
|
||
aptible.event('pageview');
|
||
|
||
function addPushStateListener(listener) {
|
||
if (!Proxy) return;
|
||
window.history.pushState = new Proxy(window.history.pushState, {
|
||
apply: (target, thisArg, argArray) => {
|
||
target.apply(thisArg, argArray);
|
||
listener();
|
||
},
|
||
});
|
||
}
|
||
|
||
addPushStateListener(() => {
|
||
aptible.event('pageview');
|
||
});
|
||
</script>
|
||
|
||
<!-- End of headEnd -->
|
||
</head>
|
||
<body>
|
||
<script async src="https://events.framer.com/script?v=2" data-fid="8a2e420fc3901e8137c7d28f7a88124c7169311a67149105250e118b5c03e3af" data-no-nt></script>
|
||
<!-- Start of bodyStart -->
|
||
|
||
<!-- End of bodyStart -->
|
||
<div id="main" data-framer-hydrate-v2="{"routeId":"sneE9th9z","localeId":"default","pathVariables":{"PW4491FI5":"how-to-build-an-ai-agent-for-sre"},"breakpoints":[{"hash":"1f4b9ar","mediaQuery":"(min-width: 1440px)"},{"hash":"be8ipx","mediaQuery":"(min-width: 1200px) and (max-width: 1439.98px)"},{"hash":"126m487","mediaQuery":"(min-width: 810px) and (max-width: 1199.98px)"},{"hash":"1ibmtzm","mediaQuery":"(max-width: 809.98px)"}]}" data-framer-ssr-released-at="2025-11-04T18:22:57.272Z" data-framer-page-optimized-at="2025-11-04T20:23:35.255Z" data-framer-generated-page><!--$--><style data-framer-html-style>html body { background: rgb(12, 18, 53); }</style><div data-framer-root class="framer-iwu1g framer-Jwf7E framer-W2ScL framer-2ISu1 framer-l9VZC framer-gP2PZ framer-SpJQu framer-Vi77g framer-NnO4D framer-Fwfi0 framer-3iZg2 framer-jNrE7 framer-J6O1U framer-q9X0b framer-CWcyA framer-1f4b9ar" style="min-height:100vh;width:auto"><div class="framer-1jw3r6s-container"><div class="ssr-variant hidden-1ibmtzm hidden-126m487 hidden-be8ipx"><div class="framer-B0Zy3 framer-EdnDn framer-7tb29w framer-v-7tb29w" data-framer-name="Desktop-Wide" style="background-color:rgba(0, 0, 0, 0);width:100%"><div class="framer-ae3s53" data-framer-name="Container"><div class="framer-kbz1xf" data-framer-name="Placeholder Logo"><div class="framer-1s0e3pk" data-framer-name="Unpage" data-framer-component-type="RichTextContainer" style="justify-content:center;--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3BhY2UgR3JvdGVzay03MDA=;--framer-font-family:"Space Grotesk", "Space Grotesk Placeholder", sans-serif;--framer-font-size:36px;--framer-font-weight:700;--framer-letter-spacing:-0.04em;--framer-line-height:117%;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))"><!--$--><a class="framer-text framer-styles-preset-1gaq3q8" data-styles-preset="XpKsMIMeb" href="../">Unpage</a><!--/$--></p></div></div></div><div class="framer-357n6m" data-framer-name="Nav Links"><div class="framer-xdnazc" data-framer-name="Nav"><div class="framer-4aolzb" data-framer-name="Nav Link"><div class="framer-n2dwnp-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="https://docs.unpage.ai/" rel="noopener" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Docs</p></div></a><!--/$--></div></div><div class="framer-5fo7hl" data-framer-name="Nav Link"><!--$--><div class="framer-1lqcgtr-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../changelog" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Changelog</p></div></a><!--/$--></div><!--/$--></div><div class="framer-esdrld" data-framer-name="Nav Link"><!--$--><div class="framer-mi4ve3-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../guides" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Guides</p></div></a><!--/$--></div><!--/$--></div><div class="framer-1d7i85n" data-framer-name="Icon Button"><div class="framer-luav3i-container"><!--$--><a class="framer-8s6rc framer-eeze19 framer-v-eeze19 framer-11qa0re" data-framer-name="Default" href="https://github.com/aptible/unpage" target="_blank" rel="noopener" style="height:100%;width:100%"><div data-framer-component-type="SVG" parentsize="0" _constraints="[object Object]" rotation="0" shadows class="framer-xb934c" aria-hidden="true" style="image-rendering:pixelated;flex-shrink:0;background-size:100% 100%;background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24.407 24" overflow="visible"><path d="M 12.213 0 C 5.46 0 0 5.5 0 12.304 C 0 17.743 3.498 22.347 8.351 23.977 C 8.958 24.099 9.18 23.712 9.18 23.386 C 9.18 23.101 9.16 22.123 9.16 21.104 C 5.763 21.838 5.055 19.638 5.055 19.638 C 4.509 18.212 3.7 17.845 3.7 17.845 C 2.588 17.091 3.781 17.091 3.781 17.091 C 5.015 17.173 5.662 18.354 5.662 18.354 C 6.754 20.228 8.513 19.699 9.221 19.373 C 9.322 18.578 9.645 18.028 9.989 17.723 C 7.279 17.438 4.428 16.378 4.428 11.652 C 4.428 10.308 4.913 9.208 5.682 8.352 C 5.561 8.047 5.136 6.783 5.803 5.093 C 5.803 5.093 6.835 4.767 9.16 6.356 C 10.155 6.086 11.182 5.949 12.213 5.948 C 13.245 5.948 14.296 6.091 15.267 6.356 C 17.592 4.767 18.623 5.093 18.623 5.093 C 19.291 6.783 18.866 8.047 18.745 8.352 C 19.533 9.208 19.998 10.308 19.998 11.652 C 19.998 16.378 17.147 17.417 14.417 17.723 C 14.862 18.11 15.246 18.843 15.246 20.004 C 15.246 21.654 15.226 22.979 15.226 23.386 C 15.226 23.712 15.449 24.099 16.055 23.977 C 20.908 22.347 24.407 17.743 24.407 12.304 C 24.427 5.5 18.947 0 12.213 0 Z" fill="rgb(255,255,255)"></path></svg>')"></div></a><!--/$--></div></div></div></div></div></div><div class="ssr-variant hidden-126m487 hidden-1f4b9ar hidden-be8ipx"><div class="framer-B0Zy3 framer-EdnDn framer-7tb29w framer-v-cm15kg" data-framer-name="Mobile - Closed" style="background-color:rgba(0, 0, 0, 0);width:100%"><div class="framer-ae3s53" data-framer-name="Container"><div class="framer-146rl1k-container"><div class="framer-PqHEJ framer-aejhsi framer-v-aejhsi" data-framer-name="Closed" data-highlight="true" tabindex="0"><div class="framer-ayk48a" data-framer-name="Hamburger Menu"><div class="framer-1jnwuhd" data-framer-name="Bottom" style="background-color:rgb(255, 255, 255);transform:none"></div><div class="framer-1oxwytt" data-framer-name="Mid" style="background-color:rgb(255, 255, 255)"></div><div class="framer-1ediuhh" data-framer-name="Top" style="background-color:rgb(255, 255, 255);transform:none"></div></div></div></div><div class="framer-kbz1xf" data-framer-name="Placeholder Logo"><div class="framer-1s0e3pk" data-framer-name="Unpage" data-framer-component-type="RichTextContainer" style="justify-content:center;--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3BhY2UgR3JvdGVzay03MDA=;--framer-font-family:"Space Grotesk", "Space Grotesk Placeholder", sans-serif;--framer-font-size:36px;--framer-font-weight:700;--framer-letter-spacing:-0.04em;--framer-line-height:117%;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))"><!--$--><a class="framer-text framer-styles-preset-1gaq3q8" data-styles-preset="XpKsMIMeb" href="../">Unpage</a><!--/$--></p></div></div></div></div></div><div class="ssr-variant hidden-1ibmtzm hidden-1f4b9ar hidden-be8ipx"><div class="framer-B0Zy3 framer-EdnDn framer-7tb29w framer-v-4ja6tg" data-framer-name="Desktop-Narrow" style="background-color:rgba(0, 0, 0, 0);width:100%"><div class="framer-ae3s53" data-framer-name="Container"><div class="framer-kbz1xf" data-framer-name="Placeholder Logo"><div class="framer-1s0e3pk" data-framer-name="Unpage" data-framer-component-type="RichTextContainer" style="justify-content:center;--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3BhY2UgR3JvdGVzay03MDA=;--framer-font-family:"Space Grotesk", "Space Grotesk Placeholder", sans-serif;--framer-font-size:36px;--framer-font-weight:700;--framer-letter-spacing:-0.04em;--framer-line-height:117%;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))"><!--$--><a class="framer-text framer-styles-preset-1gaq3q8" data-styles-preset="XpKsMIMeb" href="../">Unpage</a><!--/$--></p></div></div></div><div class="framer-357n6m" data-framer-name="Nav Links"><div class="framer-xdnazc" data-framer-name="Nav"><div class="framer-4aolzb" data-framer-name="Nav Link"><div class="framer-n2dwnp-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="https://docs.unpage.ai/" rel="noopener" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Docs</p></div></a><!--/$--></div></div><div class="framer-5fo7hl" data-framer-name="Nav Link"><!--$--><div class="framer-1lqcgtr-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../changelog" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Changelog</p></div></a><!--/$--></div><!--/$--></div><div class="framer-esdrld" data-framer-name="Nav Link"><!--$--><div class="framer-mi4ve3-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../guides" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Guides</p></div></a><!--/$--></div><!--/$--></div><div class="framer-1d7i85n" data-framer-name="Icon Button"><div class="framer-luav3i-container"><!--$--><a class="framer-8s6rc framer-eeze19 framer-v-eeze19 framer-11qa0re" data-framer-name="Default" href="https://github.com/aptible/unpage" target="_blank" rel="noopener" style="height:100%;width:100%"><div data-framer-component-type="SVG" parentsize="0" _constraints="[object Object]" rotation="0" shadows class="framer-xb934c" aria-hidden="true" style="image-rendering:pixelated;flex-shrink:0;background-size:100% 100%;background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24.407 24" overflow="visible"><path d="M 12.213 0 C 5.46 0 0 5.5 0 12.304 C 0 17.743 3.498 22.347 8.351 23.977 C 8.958 24.099 9.18 23.712 9.18 23.386 C 9.18 23.101 9.16 22.123 9.16 21.104 C 5.763 21.838 5.055 19.638 5.055 19.638 C 4.509 18.212 3.7 17.845 3.7 17.845 C 2.588 17.091 3.781 17.091 3.781 17.091 C 5.015 17.173 5.662 18.354 5.662 18.354 C 6.754 20.228 8.513 19.699 9.221 19.373 C 9.322 18.578 9.645 18.028 9.989 17.723 C 7.279 17.438 4.428 16.378 4.428 11.652 C 4.428 10.308 4.913 9.208 5.682 8.352 C 5.561 8.047 5.136 6.783 5.803 5.093 C 5.803 5.093 6.835 4.767 9.16 6.356 C 10.155 6.086 11.182 5.949 12.213 5.948 C 13.245 5.948 14.296 6.091 15.267 6.356 C 17.592 4.767 18.623 5.093 18.623 5.093 C 19.291 6.783 18.866 8.047 18.745 8.352 C 19.533 9.208 19.998 10.308 19.998 11.652 C 19.998 16.378 17.147 17.417 14.417 17.723 C 14.862 18.11 15.246 18.843 15.246 20.004 C 15.246 21.654 15.226 22.979 15.226 23.386 C 15.226 23.712 15.449 24.099 16.055 23.977 C 20.908 22.347 24.407 17.743 24.407 12.304 C 24.427 5.5 18.947 0 12.213 0 Z" fill="rgb(255,255,255)"></path></svg>')"></div></a><!--/$--></div></div></div></div></div></div><div class="ssr-variant hidden-1ibmtzm hidden-126m487 hidden-1f4b9ar"><div class="framer-B0Zy3 framer-EdnDn framer-7tb29w framer-v-4ja6tg" data-framer-name="Desktop-Narrow" style="background-color:rgba(0, 0, 0, 0);width:100%"><div class="framer-ae3s53" data-framer-name="Container"><div class="framer-kbz1xf" data-framer-name="Placeholder Logo"><div class="framer-1s0e3pk" data-framer-name="Unpage" data-framer-component-type="RichTextContainer" style="justify-content:center;--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3BhY2UgR3JvdGVzay03MDA=;--framer-font-family:"Space Grotesk", "Space Grotesk Placeholder", sans-serif;--framer-font-size:36px;--framer-font-weight:700;--framer-letter-spacing:-0.04em;--framer-line-height:117%;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))"><!--$--><a class="framer-text framer-styles-preset-1gaq3q8" data-styles-preset="XpKsMIMeb" href="../">Unpage</a><!--/$--></p></div></div></div><div class="framer-357n6m" data-framer-name="Nav Links"><div class="framer-xdnazc" data-framer-name="Nav"><div class="framer-4aolzb" data-framer-name="Nav Link"><div class="framer-n2dwnp-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="https://docs.unpage.ai/" rel="noopener" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Docs</p></div></a><!--/$--></div></div><div class="framer-5fo7hl" data-framer-name="Nav Link"><!--$--><div class="framer-1lqcgtr-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../changelog" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Changelog</p></div></a><!--/$--></div><!--/$--></div><div class="framer-esdrld" data-framer-name="Nav Link"><!--$--><div class="framer-mi4ve3-container"><!--$--><a class="framer-azFTY framer-1phi7jx framer-v-1phi7jx framer-1q0urgz" data-framer-name="Default" href="../guides" style="height:100%"><div class="framer-1l02ivz" data-framer-name="Docs" data-framer-component-type="RichTextContainer" style="--extracted-r6o4lv:rgb(255, 255, 255);--framer-paragraph-spacing:0px;transform:none"><p class="framer-text" style="--font-selector:R0Y7U3Vpc3NlIEludCdsLTUwMA==;--framer-font-family:"Suisse Int'l", "Suisse Int'l Placeholder", sans-serif;--framer-font-size:20px;--framer-font-weight:500;--framer-line-height:20px;--framer-text-alignment:center;--framer-text-color:var(--extracted-r6o4lv, rgb(255, 255, 255))">Guides</p></div></a><!--/$--></div><!--/$--></div><div class="framer-1d7i85n" data-framer-name="Icon Button"><div class="framer-luav3i-container"><!--$--><a class="framer-8s6rc framer-eeze19 framer-v-eeze19 framer-11qa0re" data-framer-name="Default" href="https://github.com/aptible/unpage" target="_blank" rel="noopener" style="height:100%;width:100%"><div data-framer-component-type="SVG" parentsize="0" _constraints="[object Object]" rotation="0" shadows class="framer-xb934c" aria-hidden="true" style="image-rendering:pixelated;flex-shrink:0;background-size:100% 100%;background-image:url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24.407 24" overflow="visible"><path d="M 12.213 0 C 5.46 0 0 5.5 0 12.304 C 0 17.743 3.498 22.347 8.351 23.977 C 8.958 24.099 9.18 23.712 9.18 23.386 C 9.18 23.101 9.16 22.123 9.16 21.104 C 5.763 21.838 5.055 19.638 5.055 19.638 C 4.509 18.212 3.7 17.845 3.7 17.845 C 2.588 17.091 3.781 17.091 3.781 17.091 C 5.015 17.173 5.662 18.354 5.662 18.354 C 6.754 20.228 8.513 19.699 9.221 19.373 C 9.322 18.578 9.645 18.028 9.989 17.723 C 7.279 17.438 4.428 16.378 4.428 11.652 C 4.428 10.308 4.913 9.208 5.682 8.352 C 5.561 8.047 5.136 6.783 5.803 5.093 C 5.803 5.093 6.835 4.767 9.16 6.356 C 10.155 6.086 11.182 5.949 12.213 5.948 C 13.245 5.948 14.296 6.091 15.267 6.356 C 17.592 4.767 18.623 5.093 18.623 5.093 C 19.291 6.783 18.866 8.047 18.745 8.352 C 19.533 9.208 19.998 10.308 19.998 11.652 C 19.998 16.378 17.147 17.417 14.417 17.723 C 14.862 18.11 15.246 18.843 15.246 20.004 C 15.246 21.654 15.226 22.979 15.226 23.386 C 15.226 23.712 15.449 24.099 16.055 23.977 C 20.908 22.347 24.407 17.743 24.407 12.304 C 24.427 5.5 18.947 0 12.213 0 Z" fill="rgb(255,255,255)"></path></svg>')"></div></a><!--/$--></div></div></div></div></div></div></div><div class="framer-695qvr"><div class="framer-rsgex4"><div class="framer-1q6tkmr" data-framer-component-type="RichTextContainer" style="transform:translateX(-50%)"><h5 class="framer-text framer-styles-preset-1pof2ao" data-styles-preset="a6Bqha9KS"><!--$--><a class="framer-text framer-styles-preset-1ig7lvc" data-styles-preset="pi9Zxi98b" href="../guides">← all Guides</a><!--/$--></h5></div></div><div class="framer-10i7rne"><div class="framer-1ki6j2f"><div class="framer-1hhg6mn" data-framer-name="Date" data-framer-component-type="RichTextContainer" style="transform:none"><p class="framer-text framer-styles-preset-ivdbsg" data-styles-preset="uDP1R0D66" style="--framer-text-alignment:left;--framer-text-color:rgb(214, 214, 214)">May 23, 2025</p></div><div class="framer-q6iizf" data-framer-name="Title" data-framer-component-type="RichTextContainer" style="transform:none"><h1 class="framer-text framer-styles-preset-y1p1zx" data-styles-preset="tQEIs45iS" style="--framer-text-alignment:left">How to build an AI Agent for SRE</h1></div><div class="framer-8a2lbe" data-framer-name="Author Details"><div class="framer-1gfmqvt" data-framer-name="Author Image"><div style="position:absolute;border-radius:inherit;top:0;right:0;bottom:0;left:0" data-framer-background-image-wrapper="true"><img decoding="async" width="512" height="512" sizes="64px" src="https://framerusercontent.com/images/NzkRM6Ca0ng3cUawiuiuEVk4.jpeg?width=512&height=512" alt style="display:block;width:100%;height:100%;border-radius:inherit;object-position:center;object-fit:cover"></div></div><div class="framer-gkfqux" data-framer-name="Author + Title"><div class="framer-wlugre" data-framer-name="Author" data-framer-component-type="RichTextContainer" style="transform:none"><p style="--font-selector:Q1VTVE9NO1N1aXNzZSBJbnQnbCBNZWRpdW0=;--framer-font-family:"Suisse Int'l Medium", "Suisse Int'l Medium Placeholder", sans-serif;--framer-font-open-type-features:'blwf' on, 'cv09' on, 'cv03' on, 'cv04' on, 'cv11' on;--framer-font-size:20px;--framer-letter-spacing:-0.02em;--framer-line-height:1.6em;--framer-text-alignment:left;--framer-text-color:rgb(255, 255, 255)" class="framer-text">Eric Abruzzese</p></div><div class="framer-7mb3ft" data-framer-name="Author Title" data-framer-component-type="RichTextContainer" style="transform:none"><p class="framer-text framer-styles-preset-ivdbsg" data-styles-preset="uDP1R0D66" style="--framer-text-alignment:left;--framer-text-color:rgb(176, 176, 176)">Software Engineer, Aptible AI</p></div></div></div></div><div class="ssr-variant hidden-1ibmtzm hidden-126m487 hidden-be8ipx"><div class="framer-4g90hh" data-framer-name="Banner"><div style="position:absolute;border-radius:inherit;top:0;right:0;bottom:0;left:0" data-framer-background-image-wrapper="true"><img decoding="async" width="2400" height="1336" sizes="(min-width: 1440px) calc(min(max(100vw * 0.72 - 120px, 1px), 100vw * 0.9) * 0.8), (max-width: 809.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 810px) and (max-width: 1199.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 1200px) and (max-width: 1439.98px) calc(min(max(100vw * 0.72 - 80px, 1px), 100vw * 0.9) * 0.9)" srcset="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=512&width=2400&height=1336 512w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=1024&width=2400&height=1336 1024w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=2048&width=2400&height=1336 2048w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336 2400w" src="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336" alt="Build an AI Agent for SRE" style="display:block;width:100%;height:100%;border-radius:inherit;object-position:center;object-fit:cover" data-framer-original-sizes="calc(min(max(100vw * 0.72 - 120px, 1px), 100vw * 0.9) * 0.8)"></div></div></div><div class="ssr-variant hidden-126m487 hidden-1f4b9ar hidden-be8ipx"><div class="framer-4g90hh" data-framer-name="Banner"><div style="position:absolute;border-radius:inherit;top:0;right:0;bottom:0;left:0" data-framer-background-image-wrapper="true"><img decoding="async" width="2400" height="1336" sizes="(min-width: 1440px) calc(min(max(100vw * 0.72 - 120px, 1px), 100vw * 0.9) * 0.8), (max-width: 809.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 810px) and (max-width: 1199.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 1200px) and (max-width: 1439.98px) calc(min(max(100vw * 0.72 - 80px, 1px), 100vw * 0.9) * 0.9)" srcset="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=512&width=2400&height=1336 512w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=1024&width=2400&height=1336 1024w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=2048&width=2400&height=1336 2048w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336 2400w" src="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336" alt="Build an AI Agent for SRE" style="display:block;width:100%;height:100%;border-radius:inherit;object-position:center;object-fit:cover" data-framer-original-sizes="min(100vw * 0.9, 100vw * 0.81)"></div></div></div><div class="ssr-variant hidden-1ibmtzm hidden-1f4b9ar hidden-be8ipx"><div class="framer-4g90hh" data-framer-name="Banner"><div style="position:absolute;border-radius:inherit;top:0;right:0;bottom:0;left:0" data-framer-background-image-wrapper="true"><img decoding="async" width="2400" height="1336" sizes="(min-width: 1440px) calc(min(max(100vw * 0.72 - 120px, 1px), 100vw * 0.9) * 0.8), (max-width: 809.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 810px) and (max-width: 1199.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 1200px) and (max-width: 1439.98px) calc(min(max(100vw * 0.72 - 80px, 1px), 100vw * 0.9) * 0.9)" srcset="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=512&width=2400&height=1336 512w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=1024&width=2400&height=1336 1024w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=2048&width=2400&height=1336 2048w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336 2400w" src="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336" alt="Build an AI Agent for SRE" style="display:block;width:100%;height:100%;border-radius:inherit;object-position:center;object-fit:cover" data-framer-original-sizes="min(100vw * 0.9, 100vw * 0.81)"></div></div></div><div class="ssr-variant hidden-1ibmtzm hidden-126m487 hidden-1f4b9ar"><div class="framer-4g90hh" data-framer-name="Banner"><div style="position:absolute;border-radius:inherit;top:0;right:0;bottom:0;left:0" data-framer-background-image-wrapper="true"><img decoding="async" width="2400" height="1336" sizes="(min-width: 1440px) calc(min(max(100vw * 0.72 - 120px, 1px), 100vw * 0.9) * 0.8), (max-width: 809.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 810px) and (max-width: 1199.98px) min(100vw * 0.9, 100vw * 0.81), (min-width: 1200px) and (max-width: 1439.98px) calc(min(max(100vw * 0.72 - 80px, 1px), 100vw * 0.9) * 0.9)" srcset="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=512&width=2400&height=1336 512w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=1024&width=2400&height=1336 1024w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?scale-down-to=2048&width=2400&height=1336 2048w,https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336 2400w" src="https://framerusercontent.com/images/xbHqQjzT3ft8wL9ROYOxxsgRE.png?width=2400&height=1336" alt="Build an AI Agent for SRE" style="display:block;width:100%;height:100%;border-radius:inherit;object-position:center;object-fit:cover" data-framer-original-sizes="calc(min(max(100vw * 0.72 - 80px, 1px), 100vw * 0.9) * 0.9)"></div></div></div><div class="framer-e1t4z3" data-framer-name="Content" data-framer-component-type="RichTextContainer" style="transform:none"><h2 class="framer-text framer-styles-preset-1pyagxk"><br class="framer-text">Get started with your AI SRE Agent</h2><p class="framer-text framer-styles-preset-ivdbsg">In this guide, we'll be covering: </p><ol class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">Some background:</strong> why so many companies (including Aptible) are investing in building an AI Agent to supplement incident response</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">Some philosophical considerations:</strong> decisions we made with Aptible AI, what we’ve built so far, and a few pro tips</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">A complete step-by-step guide on how to build your own AI Agent, including:</strong></p><ul class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to set up an application in Chainlit</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to connect your application to an LLM (in this case, gpt-4o)</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to make your Agent faster and add real-time “typing” (similar to ChatGPT)</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to give your Agent a personality and specialization</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to give your Agent the ability to search a collection of files</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">How to integrate your Agent with external tools (in this case, PagerDuty)</p></li></ul></li></ol><h5 class="framer-text framer-styles-preset-1pof2ao"><strong class="framer-text">🚨 Unpage is now live 🚨 </strong></h5><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">We’ve taken our experience building AI SRE agents and created <strong class="framer-text">Unpage</strong>, an open source agent-building framework that helps you build your own AI SRE with the infrastructure context it needs to act safely and effectively.</p><p class="framer-text framer-styles-preset-ivdbsg"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://docs.unpage.ai/?utm_source=guide" rel="noopener">Get started here</a><!--/$-->, and join us and the rest of the <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://join.slack.com/t/unpage/shared_invite/zt-3a85b8rnp-Hf1OIZq8SNu5FyrFhWaGQw" rel="noopener">Unpage community on Slack</a><!--/$-->.</p></blockquote><h2 class="framer-text framer-styles-preset-1pyagxk">Why use an AI Agent for incident response?</h2><p class="framer-text framer-styles-preset-ivdbsg">Incident response is messy. Knowledge silos end up forcing you to rely on a handful of subject matter experts and tenured engineers; SREs waste time on manual investigations during incidents at the expense of high impact work; and all of this leads to more downtime, higher MTTR, and potentially lost revenue. Aptible isn’t the first team to feel those pains, and we won’t be the last. But we’ve found a better way by implementing an AI Agent to act as an assistant to our SREs. Here’s why:</p><p class="framer-text framer-styles-preset-ivdbsg">AI is really good at mundane, time consuming tasks like quickly retrieving information from disparate sources, summarizing huge amounts of data, and pattern matching. These benefits — when combined with human instinct, logic, and decision making skills — make AI ripe for augmenting on-call engineering teams during incidents.</p><p class="framer-text framer-styles-preset-ivdbsg">At Aptible, we saw the potential here for our own incident response processes and decided to build an AI SRE Agent to help lower MTTR, eliminate knowledge silos, and decrease reliance on our most senior engineers.</p><p class="framer-text framer-styles-preset-ivdbsg">We’ve spoken to quite a few engineering leads who are building the same type of tool for their teams, and we can all agree that building any kind of nontrivial AI assistant is hard for several reasons:</p><ul class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Given that AI is still so new, there are few established standards or best practices</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Frameworks are changing all the time</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">So much information online is already outdated (or will be soon)</p></li></ul><p class="framer-text framer-styles-preset-ivdbsg">Because the demand for an AI Agent that <em class="framer-text">actually works</em> is so high — and since we’ve put so much time into building our own already — we’re going to share a detailed explanation of what we’ve built so far, the main considerations we had to take into account, and suggestions on how you can do it yourself.</p><h2 class="framer-text framer-styles-preset-1pyagxk">What we’ve built so far + decisions we’ve made along the way</h2><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">Is there already a tool for that?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> If you don’t need to build something new, then you shouldn’t. There are <em class="framer-text">so. many. tools</em> on the market. And depending on your specific use case, you may be able to find one that works for you.</p><p class="framer-text framer-styles-preset-ivdbsg">The impetus for Aptible’s search and eventual development of our own incident response was that we struggled with knowledge silos and had a tendency to rely on three or four subject matter experts any time we encountered an issue with a particular system.</p><p class="framer-text framer-styles-preset-ivdbsg">So we started with an open source tool called <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.danswer.ai/" rel="noopener">Danswer</a><!--/$--> to improve information retrieval (similar to its popular commercial competitor, <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.glean.com/" rel="noopener">Glean</a><!--/$-->). It plugged directly into Slack and could retrieve answers to natural language questions from various information sources. The problem was that it was limited to indexed data only (i.e., just our docs and our chat history).</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> What we needed was a tool that could integrate with all our other systems (not just docs and Slack). We needed to retrieve logs and metrics, application health status, and generate reports and postmortems after our incidents were over. So we designed an AI Agent that’s essentially built on a series of integrations that allow you to connect with both real-time and indexed data. More on that later!</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> <br class="framer-text">Before you decide to build your own product, look into what’s already available. A great place to start might be crowdsourcing ideas from Reddit (check out <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.reddit.com/r/sre/comments/14upvvg/ideas_of_ai_tools_in_sredevops/" rel="noopener">this thread</a><!--/$-->, for one) or checking out some of the open source tools out there (<!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://github.com/pain0x0/awesome-incident-response" rel="noopener">here’s a good place to start in github</a><!--/$--> if you’re looking for incident response tools specifically). There’s also a <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://github.com/kyrolabs/awesome-agents" rel="noopener">long list</a><!--/$--> of open source AI Agents that you could start using tomorrow.</p></blockquote><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="2" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">What’s your integration strategy?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> As mentioned above, the biggest consideration here is: what sort of information do you need your Agent to have access to? You could maybe get away with simply integrating it with third-party providers via an API, but if you need the integration to be more specific to your needs then you’ll need to be more thoughtful with how your integrations work.</p><p class="framer-text framer-styles-preset-ivdbsg">By carefully considering what you’ll need to integrate with before you start building, you’ll save yourself some headache later on. Do you need your Agent to be able to execute custom scripts to query your databases? Do you need real-time retrieval of logs and metrics, and how will you design the Agent to retrieve that information? Will it return the link to the source? Will it return a chunk of lines of logs that you still have to manually sift through, or will it be able to deduce where the anomaly may be?</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> At its core, Aptible AI is built on a series of integrations. An integration is more than just a connection to a third-party provider, it’s also a collection of configurations that are unique to how our team uses that provider. For example, Aptible AI supports multiple integrations for the same provider since we may want to use that provider in different ways. Different teams use Datadog differently and care about different metrics or use different tags, so each team can use the integration to the same tool in the way that they need.</p><p class="framer-text framer-styles-preset-ivdbsg">Aptible AI supports a range of common SRE tooling, including:</p><ul class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Chat and other highly synchronous communications</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Documentation and other knowledge repositories</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Observability</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Alerting</p></li></ul><p class="framer-text framer-styles-preset-ivdbsg">The actual implementation of these integrations fits into one of three categories of customizability:</p><ol class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">For starters, you have a basic integration that requires no customization (PagerDuty is one example). Since it’s just pulling data from PagerDuty and adding it to the AI’s context, every single team that leverages the PagerDuty integration uses it in the same way.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Next, we have more customizable integrations (like the Datadog example from before) that are built on top of a generic InfluxDB integration but customized to the specific use cases of looking up container metrics and looking up restart activity.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Finally, there are fully custom tools that would likely make no sense to anyone outside of Aptible (an example here would be our integration that gets containers for an application). These are entirely specific to how we run our infrastructure and can be implemented either by a lightweight PubSub interface or a websocket-based “safe” proxy.</p></li></ol><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> <br class="framer-text">Less is more! If you give the model too many tools to choose from, it can start choosing incorrect tools and confuse itself. More on that in the next section</p></blockquote><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="3" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">Speed vs. quality — what’s the right balance?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> So, first of all, no one wants an AI Agent that takes ages to respond. It defeats the whole purpose of building an Agent in the first place if you could do it faster yourself. There are two things to consider here: first of all (and we’ll get into this more in the next section), tweaking your prompt to ask the agent to be more brief will help to speed up your Agent a bit; secondly, you <em class="framer-text">could</em> consider using a faster model like 4o-mini, but you may be sacrificing a bit on the capabilities side (which might be fine, depending on your use case).</p><p class="framer-text framer-styles-preset-ivdbsg">It’s also important to keep in mind that you must show the user that the bot has gotten the request and is working on a response (otherwise, they may get frustrated or confused and stop engaging). In the step-by-step lab below, we’ll show you how to enable real-time typing to indicate to the user that the Agent is about to respond (similar to ChatGPT, as you likely know). However, because we originally built Aptible AI as a Slack integration, we’ve been limited on how we can show that the Agent has seen the request and is working on gathering a response.</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> We’ve spend a lot of time tweaking the prompts for Aptible AI (again, more on that in the next section) to ensure that it can respond quickly and effectively. As for the Slack integration, since we can’t show live typing, we tell the Agent to react to any question that it will react with a 👀 to indicate that the Agent has seen your request and is working on a response. We made sure to send that reaction as fast as possible to give our users confidence that we’re handling their request.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">💡 Pro tip:</strong></p><p class="framer-text framer-styles-preset-ivdbsg">The model that you choose will impact speed; it’s important to strike the proper balance between speed and capability. <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://artificialanalysis.ai/" rel="noopener">Here is a detailed breakdown</a><!--/$--> of the quality, speed, and price of leading LLM models to get you started.</p></blockquote><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol start="4" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">So many models, how do you pick one?!</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> Here’s the thing with models… new ones pop up every day, and there are several considerations to keep in mind when choosing one (mainly to do with your specific use cases). Should you self-host? Do you need your Agent to be conversational or task-based or both? Will it be conducting simple or complex tasks? Do you need real-time performance?</p><p class="framer-text framer-styles-preset-ivdbsg">There’s no need for us to go through all the models that exist since that content is already all over the place (if you want a deep dive, <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.geeksforgeeks.org/common-ai-models-and-when-to-use-them/" rel="noopener">this is a great resource</a><!--/$-->), but we can walk through the decisions that we had to make when building Aptible AI and the options we considered.</p><p class="framer-text framer-styles-preset-ivdbsg">It’s a tricky process because you can’t really avoid tradeoffs. If you need your Agent to conduct complex tasks, then you’ll have to sacrifice a bit on speed and cost.</p><p class="framer-text framer-styles-preset-ivdbsg">The model’s size, capability, and architecture depend heavily on whether the tasks require simple classification or highly complex reasoning and interaction. If simple, a smaller, lightweight model like a decision tree, random forest, or simple neural network would suffice. If more complex, then you may consider a more powerful model like GPT-4, BERT, or a similar transformer-based architecture.</p><p class="framer-text framer-styles-preset-ivdbsg">If you choose to self-host to avoid the security headache, you’ll likely have to sacrifice on features and functionality since your self-hosted version will lag behind the hosted options.</p><p class="framer-text framer-styles-preset-ivdbsg">If you need your Agent to be trained on domain-specific knowledge, then you’ll need to curate or create your own datasets for fine-tuning. See if you can get away with using a pre-trained model that’s already been trained on large datasets to avoid the data quality issue (though this may be impossible depending on the data you need your Agent to have access to).</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> We’re currently using GPT-4o for Aptible AI because we believe that it’s most likely to give us the highest quality answers. However, we recognize that customers using Aptible AI may want to use their own models (including self-hosted models). As such, we’re keeping that in mind as we build.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> <br class="framer-text">Your Agent will only be as smart as the information that you give it. LLMs need help understanding how and when to use the information you give it, and if you don’t give it instructions on how to interpret information, it’ll just make something up. Spend real effort upfront curating the information you feed to your LLM!</p></blockquote><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="5" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">What about prompting techniques?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> You might be tempted to retrieve as much data as possible (documentation, Slack conversations, code repositories, issue trackers, etc.), throw it all at a RAG application**,** and ask it questions. But in our experience, there’s almost always going to be too much noise for this to be useful. That’s where prompt engineering comes in.</p><p class="framer-text framer-styles-preset-ivdbsg">We’ve alluded to this already, but prompt engineering is a critical piece of the puzzle here (for a great overview on prompting techniques, <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.promptingguide.ai/techniques" rel="noopener">check this out</a><!--/$-->). The better your prompt engineering, the better your Agent will be.</p><p class="framer-text framer-styles-preset-ivdbsg">For context, here are a few that we considered (over time) when building Aptible AI:</p><ul class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.promptingguide.ai/techniques/zeroshot" rel="noopener">Zero-shot prompting</a><!--/$-->: this is what most people do when they talk to ChatGPT; they just ask it a question then they get a response. If the response is bad, then they just ask the question differently.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.promptingguide.ai/techniques/fewshot" rel="noopener">Few-shot prompting</a><!--/$-->: this is what slightly-more-experienced people do when talking to ChatGPT; they ask it a question and include examples of the output they want. You might use zero- and/or few-shot prompting for very simple tasks that the underlying model already knows how to do.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.promptingguide.ai/techniques/rag" rel="noopener">Retrieval Augmented Generation (RAG)</a><!--/$-->: this is a technique that allows the model to retrieve additional context and use it to answer the question. This is particularly useful for AI-powered document search (see also: Glean and Danswer).</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://www.promptingguide.ai/techniques/react" rel="noopener">ReAct</a><!--/$-->: this technique allows an agent to generate “thoughts” and take “actions” in an iterative way to solve a problem, most similar to human reasoning. ReAct is great for moderately complex problems, like navigating references through documentation and tools in real time to compose an answer.</p></li></ul><p class="framer-text framer-styles-preset-ivdbsg">An important thing to keep in mind is that you can mix and match with these techniques (we’ll cover the multi-agent approach next). Here’s what we did…</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> Because Aptible AI has a multi-agent structure (more on that later), we’ve implemented a mix of ReAct and RAG depending on the complexity of the task/question.</p><img alt width="1213" height="1181" src="https://framerusercontent.com/images/j2bemXAOzJRIR7IpYnqo9BCvi6A.png?width=2426&height=2362" srcset="https://framerusercontent.com/images/j2bemXAOzJRIR7IpYnqo9BCvi6A.png?scale-down-to=512&width=2426&height=2362 512w,https://framerusercontent.com/images/j2bemXAOzJRIR7IpYnqo9BCvi6A.png?scale-down-to=1024&width=2426&height=2362 1024w,https://framerusercontent.com/images/j2bemXAOzJRIR7IpYnqo9BCvi6A.png?scale-down-to=2048&width=2426&height=2362 2048w,https://framerusercontent.com/images/j2bemXAOzJRIR7IpYnqo9BCvi6A.png?width=2426&height=2362 2426w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2426 / 2362" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><p class="framer-text framer-styles-preset-ivdbsg">So when you ask the AI a question, we hand off all of the integrations (with instructions on how to use them) to the AI. The AI then makes decisions about what tools to call based on the information it has available to it. After each integration call, the AI has the option of deciding it has enough information to provide an answer, or deciding that additional integrations are relevant and could potentially yield additional information.</p><p class="framer-text framer-styles-preset-ivdbsg">Throughout the process, we’re trying to help the AI make better decisions about what integrations to leverage via a few different mechanisms:</p><ul class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">Extensive prompt engineering for the integrations, to make sure it’s really clear when and how to use each integration, as well as how to interpret the output.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">We’ve built a self-rating system that asks the AI to self-rate the value of the response from an integration. Even when the AI makes a dumb decision in calling a integration (or provides bad inputs), it’s typically able to recognize that after the fact if you ask it to self-rate whether or not the output of the integration was useful. We can then use that to influence how much a specific output factors into a response. We can also block the AI from proceeding if it’s consistently making bad decisions.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">We’ve implemented Naïve Bayes based on past experience. For example, if most of the time you call integration A and then B, and that yields useful results, it’s probably useful to continue doing so. The Agent can also use things like comparing to previous similar incidents to further narrow what integrations are useful, and when, in specific scenarios.</p></li></ul><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> <br class="framer-text">To avoid nonsense answers that <em class="framer-text">sound</em> correct but aren’t, be sure to take a step back and consider where your most useful information typically comes from for the problems that you’re trying to solve with AI – then design your Agent based on that.</p></blockquote><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="6" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">Multi-agent or single agent?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> Multi-agent approaches are becoming more popular, but they can be complicated and potentially unnecessary depending on your use case. It can be quite useful to have a team of agents working together with different techniques to solve complex problems.</p><p class="framer-text framer-styles-preset-ivdbsg">For example, if you ask your bot a question in Slack that has nothing to do with your specific infrastructure (maybe you just want to know who won the World Series in 1995), you could have an Agent built on zero-shot prompting to simply act as a ChatGPT that’s integrated with your Slack (or wherever you have it).</p><p class="framer-text framer-styles-preset-ivdbsg">But if your question or need is complex, it would be useful to have a team of Agents that basically act as your little research team, gathering and analyzing data from disparate sources in an intelligent way.</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> Aptible AI uses a multi-agent approach, starting with a broker Agent that determines what type of question or task needs to be addressed.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> It’s easier to refactor into a multi-agent approach than out of it! So make sure you need it before you start building your Agent that way.</p></blockquote><img alt width="1000" height="929" src="https://framerusercontent.com/images/VFZrvog7mFucnRUX999AqYx60.png?width=2000&height=1858" srcset="https://framerusercontent.com/images/VFZrvog7mFucnRUX999AqYx60.png?scale-down-to=512&width=2000&height=1858 512w,https://framerusercontent.com/images/VFZrvog7mFucnRUX999AqYx60.png?scale-down-to=1024&width=2000&height=1858 1024w,https://framerusercontent.com/images/VFZrvog7mFucnRUX999AqYx60.png?width=2000&height=1858 2000w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2000 / 1858" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol start="7" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">What do you want your bot to specialize in?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> In the same vein as making your agent fast, you also have to consider exactly how you want to customize it. This not only means determining how much information it needs to have access to (which will affect speed and quality), but it also means giving it a personality if you’d like 😄</p><p class="framer-text framer-styles-preset-ivdbsg">Just like a human, your Agent has to specialize in the problem(s) you're trying to solve in order to be most useful and avoid spewing incorrect or misleading information. This means adjusting the system prompt. What you need to consider here is: how strict do you want your system prompt? Do you want your Agent to try to answer as many questions as possible? Or would you prefer that it outright denies any questions that it won’t be certain about? You could even design it to express uncertainty so that it doesn’t sound too authoritative and lead to confusion or distrust among your engineering teams.</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> Our system prompt is very strict — Aptible AI outright refuses to answer questions that aren't related to incidents in order to mitigate the possibility for wrong answers or hallucinations.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">💡 Pro tip:</strong></p><p class="framer-text framer-styles-preset-ivdbsg">If your implementation is sophisticated enough, you could consider customizing your Agent’s behavior based on who it’s talking to (i.e., you can ask it to be more socratic and verbose for junior team members to help them learn, or to be more terse and to-the-point for senior engineers, etc).</p></blockquote><p class="framer-text framer-styles-preset-ivdbsg">Ok, let’s jump in! Below, you’ll add speed and personality to your Agent, giving it the ability to type in real-time and testing out a few system prompts to show how your prompt engineering affects how the Agent responds.</p><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol start="8" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms"> What information should my Agent have access to?</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg">🤔 <strong class="framer-text">Considerations:</strong> Before you start feeding your LLM all of the information you can possibly find, think strategically about what exactly you want the Agent to be able to do and what its purpose is. Later on in the guide, we’ll show you how to give your bot the ability to search a collection of files effectively. Just keep in mind that the more information you give it, the trickier it can become to maintain high levels of accuracy.</p><p class="framer-text framer-styles-preset-ivdbsg">🛠️ <strong class="framer-text">What we built:</strong> Aptible AI was built for the very specific purpose of helping our SRE team and on-call engineers to debug and resolve production issues faster. So we’ve given it access to our runbooks and other documentation and we’ve designed it to gather and surface as much relevant information about the incident as it can. It then returns the data to Slack in a dynamic dashboard for our engineers to then investigate.</p><p class="framer-text framer-styles-preset-ivdbsg">We’ve found that by simply surfacing the information quickly, the Agent provides instant and consistent value versus trying to answer every single question (thereby increasing the likelihood of AI hallucination).</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong></p><p class="framer-text framer-styles-preset-ivdbsg">Resist the temptation to just plug the LLM into your existing search infrastructure! It may seem simpler, but LLMs perform better if you allow them to search semantically (i.e. using vectors) instead of trying to come up with keywords it thinks might be relevant.</p></blockquote><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol start="9" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">Can’t forget about security…</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> Here’s a topic that comes up a lot when we chat with Aptible AI early users. Most engineering teams eventually have to face their security team when it comes to implementing new tools, and it’s critical to ensure that the data is safe (especially if you’re working in a highly regulated industry). So the first thing you have to do is to know your organization’s AI security policy, then there are a few things you can do to protect against potential data leaks or external threats.</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> For starters, we use a model that doesn’t train on our data. We're still doing a lot of discovery around what customers need regarding security, whether that's self-hosting or something else! Stay tuned.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">💡 Pro tip:</strong> <br class="framer-text"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://gandalf.lakera.ai/gandalf" rel="noopener">Be careful with the data you give your AI access to or include in prompts</a><!--/$-->, especially if that data shouldn’t be shared with the end user! If you need to include unpredictable data like logs, consider using at tool like <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://help.nightfall.ai/nightfall-firewall-for-ai/nightfall-use-cases/genai-content-filtering-how-to-prevent-exposure-of-sensitive-data" rel="noopener">Nightfall</a><!--/$--> to ensure what’s passed to the LLM and end users is sanitized</p></blockquote><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="10" class="framer-text"><li data-preset-tag="h4" class="framer-text framer-styles-preset-1tjgqms"><h4 class="framer-text framer-styles-preset-1tjgqms">Oh, and of course, it needs to be usable!</h4></li></ol><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🤔 Considerations:</strong> How do you plan to use your Agent? Does it need to have a UI? Will it be used across the organization?</p><p class="framer-text framer-styles-preset-ivdbsg">You likely don’t need to spend time reinventing the wheel when it comes to the UX around your bot. Frameworks like <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://chainlit.io/" rel="noopener">Chainlit</a><!--/$-->, Gradio, and Streamlit give you out-of-the-box tools for building user interfaces and/or integrating with your other workflow tools like Slack. Use one of these tools to start so that you can focus on getting good answers out of your Agent!</p><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">⚒️ What we built:</strong> Because our Agent was built specifically for incident response — and because we handle incidents within Slack — we mainly use Slack as our UI. It has its limitations, though, so we do our best to work around them (i.e. instead of showing that the Agent is responding by mimicking typing as seen in ChatGPT, the bot instead react to the question in Slack with an 👀 emoji). We also designed a web UI for configuration, reporting, auditing, and analytics.</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">💡 <strong class="framer-text">Pro tip:</strong> <br class="framer-text">Be sure to keep your LLM code as decoupled as you can, so that you can easily refactor away into another UX if the need arises.</p></blockquote><p class="framer-text framer-styles-preset-ivdbsg">Okay, let’s move on from the theoretical talk about models, techniques, and frameworks! Time to get your hands dirty and start building your own Agent.</p><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><h2 class="framer-text framer-styles-preset-1pyagxk">Hands-on lab: A step-by-step guide on how to build your own Agent for SRE Teams</h2><p class="framer-text framer-styles-preset-ivdbsg">In the following lab, we'll show you how to:</p><p class="framer-text framer-styles-preset-ivdbsg">1. Set up an Agent in Chainlit</p><p class="framer-text framer-styles-preset-ivdbsg">2. Connect your Agent to an LLM (in this case, gpt-4o)</p><p class="framer-text framer-styles-preset-ivdbsg">3. Make your Agent faster and add real-time “typing” (similar to ChatGPT)</p><p class="framer-text framer-styles-preset-ivdbsg">4. Give your Agent a personality and specialization</p><p class="framer-text framer-styles-preset-ivdbsg">5. Give your Agent the ability to search a collection of files</p><p class="framer-text framer-styles-preset-ivdbsg">6. Integrate your Agent with external tools (in this case, PagerDuty)</p><p class="framer-text framer-styles-preset-ivdbsg">Let's dive right in.</p><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><ol class="framer-text"><li data-preset-tag="h3" class="framer-text"><h3 class="framer-text">Set up a Chainlit application</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">Before we go delving into the endless rabbit hole of building AI, we’re going to set ourselves up for success by setting up <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://chainlit.io/" rel="noopener"><strong class="framer-text">Chainlit</strong></a><!--/$-->, a popular framework for building conversational assistant interfaces.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Why Chainlit?</h5><p class="framer-text framer-styles-preset-ivdbsg">Chainlit provides an opinionated set of building blocks for modeling conversational interactions — like threads, messages, and steps — as well as a ChatGPT-like user interface for interacting with the LLM.</p><p class="framer-text framer-styles-preset-ivdbsg">It also offers out-of-the-box integrations with popular chat tools like Slack and Teams, as well as libraries for interfacing with popular tooling like React and FastAPI, so you can build it into a larger application, if you want.</p><p class="framer-text framer-styles-preset-ivdbsg">In short: Chainlit is going to eliminate a lot of the scaffolding and grunt work for us so that we can focus on developing our AI assistant and getting feedback from our users, instead of fiddling with UI and configuration.</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">By the end of this lab, you’ll have a working Chainlit application that will simply echo back what you say. We’ll jump into the AI integration in the next article.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Prerequisites</h5><p class="framer-text framer-styles-preset-ivdbsg">Before we get started, you’ll need to get set up with a few things:</p><ol class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">A working Python 3.12+ environment. We recommend using <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://github.com/pyenv/pyenv" rel="noopener">pyenv</a><!--/$-->.</p></li><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">A Python package manager. We’ll be using <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://python-poetry.org/" rel="noopener">Poetry</a><!--/$-->, but you can use whatever you’re comfortable with.</p></li></ol><p class="framer-text framer-styles-preset-ivdbsg">Once you’re set up, continue on.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Project Setup</h5><p class="framer-text framer-styles-preset-ivdbsg">First, set up your project, and add <code class="framer-text framer-styles-preset-1hyt0we">chainlit</code> as a dependency:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1ktktaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1ktktaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-plain">mkdir </span><span class="sp-syntax-plain">roger</span>
|
||
<span class="sp-syntax-plain">cd </span><span class="sp-syntax-plain">roger</span>
|
||
<span class="sp-syntax-plain">poetry </span><span class="sp-syntax-plain">init</span> --<span class="sp-syntax-plain">no</span>-<span class="sp-syntax-plain">interaction</span>
|
||
<span class="sp-syntax-plain">poetry </span><span class="sp-syntax-plain">add </span><span class="sp-syntax-plain">chainlit</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Chainlit Boilerplate</h5><p class="framer-text framer-styles-preset-ivdbsg">Next, create an <code class="framer-text framer-styles-preset-1hyt0we">app.py</code> file in the root of your project with the following content:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1kuctaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1kuctaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">chainlit</span> <span class="sp-syntax-plain">as </span><span class="sp-syntax-plain">cl</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-plain">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span><span class="sp-syntax-punctuation">:</span> cl.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">None</span><span class="sp-syntax-punctuation">:</span>
|
||
# <span class="sp-syntax-plain">Echo </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">message </span><span class="sp-syntax-plain">back </span><span class="sp-syntax-plain">to </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">user</span>.
|
||
<span class="sp-syntax-property">await</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">f</span><span class="sp-syntax-string">"Received: {message.content}"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">The code above is registering the <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function with Chainlit, so that any time a message is received, this function will run.</p><p class="framer-text framer-styles-preset-ivdbsg">For the moment, our function simply echoes the message back to the user, prefixed with “Received: ”.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Try it out</h5><p class="framer-text framer-styles-preset-ivdbsg">Finally, spin it up! You can use <code class="framer-text framer-styles-preset-1hyt0we">--watch</code> to hot-reload your code when you make changes.</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1kvktaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1kvktaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-plain">poetry </span><span class="sp-syntax-plain">run </span><span class="sp-syntax-plain">chainlit </span><span class="sp-syntax-plain">run </span><span class="sp-syntax-plain">app</span>.<span class="sp-syntax-property">py</span> --<span class="sp-syntax-plain">watch</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Running this command will start your Chainlit app and open your browser to its UI, where you can send a message and get a response back:</p><img alt width="1264" height="970" src="https://framerusercontent.com/images/dRQJB0Bu73iF4tzW3JAl42k.png?width=2528&height=1940" srcset="https://framerusercontent.com/images/dRQJB0Bu73iF4tzW3JAl42k.png?scale-down-to=512&width=2528&height=1940 512w,https://framerusercontent.com/images/dRQJB0Bu73iF4tzW3JAl42k.png?scale-down-to=1024&width=2528&height=1940 1024w,https://framerusercontent.com/images/dRQJB0Bu73iF4tzW3JAl42k.png?scale-down-to=2048&width=2528&height=1940 2048w,https://framerusercontent.com/images/dRQJB0Bu73iF4tzW3JAl42k.png?width=2528&height=1940 2528w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2528 / 1940" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><h2 class="framer-text framer-styles-preset-1pyagxk"><br class="framer-text trailing-break"></h2><ol start="2" class="framer-text"><li data-preset-tag="h3" class="framer-text"><h3 class="framer-text">Make your application smarter by connecting an LLM</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">With our Chainlit app scaffolded, we can connect it to an LLM so that we can talk to it and get a human-like response.</p><p class="framer-text framer-styles-preset-ivdbsg">We’ll use OpenAI’s hosted <code class="framer-text framer-styles-preset-1hyt0we">gpt-4o</code> model for simplicity, but using another provider is just a matter of syntax.</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">By the end of this article, you’ll be able to prompt the <code class="framer-text framer-styles-preset-1hyt0we">gpt-4o</code> model and get a response, similar to how you’d interact with ChatGPT. We’ll also make sure that the bot maintains conversation context so that you can ask follow-up questions.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Prerequisites</h5><p class="framer-text framer-styles-preset-ivdbsg">Before you get started, you’ll need:</p><ol class="framer-text"><li data-preset-tag="p" class="framer-text framer-styles-preset-ivdbsg"><p class="framer-text framer-styles-preset-ivdbsg">An OpenAI account and an <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key" rel="noopener">API key</a><!--/$--></p></li></ol><h5 class="framer-text framer-styles-preset-1pof2ao">Configure an OpenAI API client</h5><p class="framer-text framer-styles-preset-ivdbsg">First, we’ll configure an API client to interface with OpenAI’s APIs. Add the following code to the top of your <code class="framer-text framer-styles-preset-1hyt0we">app.py</code>:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1l34taop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1l34taop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">os</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-string">openai import AsyncOpenAI
|
||
</span>
|
||
##
|
||
# <span class="sp-syntax-plain">Settings</span>
|
||
#
|
||
<span class="sp-syntax-keyword">try</span><span class="sp-syntax-punctuation">:</span>
|
||
<span class="sp-syntax-plain">OPENAI_API_KEY</span> = <span class="sp-syntax-plain">os</span>.<span class="sp-syntax-property">environ</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"OPENAI_API_KEY"</span><span class="sp-syntax-punctuation">]</span>
|
||
<span class="sp-syntax-plain">except </span><span class="sp-syntax-plain">KeyError</span> <span class="sp-syntax-keyword">as</span> ex<span class="sp-syntax-punctuation">:</span>
|
||
<span class="sp-syntax-plain">raise </span><span class="sp-syntax-definition">LookupError</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">f</span><span class="sp-syntax-string">"Missing required environment variable: {ex}"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
<span class="sp-syntax-plain">client</span> = <span class="sp-syntax-definition">AsyncOpenAI</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">api_key</span>=<span class="sp-syntax-plain">OPENAI_API_KEY</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
# <span class="sp-syntax-punctuation">...</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Send Messages to the LLM</h5><p class="framer-text framer-styles-preset-ivdbsg">Next, we’ll need to update our <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function to send the user’s message to OpenAI and get a response instead of just echoing it back. Replace your <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function with this one:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1l3staop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1l3staop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"># <span class="sp-syntax-punctuation">...</span>
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-plain">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span><span class="sp-syntax-punctuation">:</span> cl.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">None</span><span class="sp-syntax-punctuation">:</span>
|
||
# <span class="sp-syntax-plain">Retrieve </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">response </span><span class="sp-syntax-plain">from </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">LLM</span>
|
||
<span class="sp-syntax-plain">response</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">chat</span>.<span class="sp-syntax-property">completions</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">messages</span>=<span class="sp-syntax-punctuation">[</span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"content"</span><span class="sp-syntax-punctuation">:</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-string">"role"</span><span class="sp-syntax-punctuation">:</span> <span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">response</span>.<span class="sp-syntax-property">choices</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-static">0</span><span class="sp-syntax-punctuation">]</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Try it out</h5><p class="framer-text framer-styles-preset-ivdbsg">Now, if you run your application (or if you left it running with the <code class="framer-text framer-styles-preset-1hyt0we">--watch</code> flag), you’ll be able to ask a question and get a response.</p><img alt width="1266" height="1072" src="https://framerusercontent.com/images/QlEPzKotxH7ID1xWkXLrW780uHM.png?width=2532&height=2144" srcset="https://framerusercontent.com/images/QlEPzKotxH7ID1xWkXLrW780uHM.png?scale-down-to=512&width=2532&height=2144 512w,https://framerusercontent.com/images/QlEPzKotxH7ID1xWkXLrW780uHM.png?scale-down-to=1024&width=2532&height=2144 1024w,https://framerusercontent.com/images/QlEPzKotxH7ID1xWkXLrW780uHM.png?scale-down-to=2048&width=2532&height=2144 2048w,https://framerusercontent.com/images/QlEPzKotxH7ID1xWkXLrW780uHM.png?width=2532&height=2144 2532w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2532 / 2144" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><h5 class="framer-text framer-styles-preset-1pof2ao">Curing Amnesia</h5><p class="framer-text framer-styles-preset-ivdbsg">If you’ve played around a bit and asked follow-up questions, you may have noticed that the bot doesn’t “remember” anything you’ve talked about. For example:</p><img alt width="1266" height="1072" src="https://framerusercontent.com/images/t4xyTIklRIMTJd6I52tN2HbFNjY.png?width=2532&height=2144" srcset="https://framerusercontent.com/images/t4xyTIklRIMTJd6I52tN2HbFNjY.png?scale-down-to=512&width=2532&height=2144 512w,https://framerusercontent.com/images/t4xyTIklRIMTJd6I52tN2HbFNjY.png?scale-down-to=1024&width=2532&height=2144 1024w,https://framerusercontent.com/images/t4xyTIklRIMTJd6I52tN2HbFNjY.png?scale-down-to=2048&width=2532&height=2144 2048w,https://framerusercontent.com/images/t4xyTIklRIMTJd6I52tN2HbFNjY.png?width=2532&height=2144 2532w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2532 / 2144" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><p class="framer-text framer-styles-preset-ivdbsg">This is happening because every time we send a message, we’re sending <em class="framer-text">only</em> that one message to the LLM, which has no notion of the “conversation” by default.</p><p class="framer-text framer-styles-preset-ivdbsg">To cure this amnesia, we’ll need to send <em class="framer-text">all</em> of the messages in the conversation every time we send a new one.</p><p class="framer-text framer-styles-preset-ivdbsg">Chainlit makes this easy for us by providing a <code class="framer-text framer-styles-preset-1hyt0we">cl.chat_context.to_openai()</code> helper, which gives us all of the messages exchanged so far, conveniently in the format that OpenAI (and most other providers) expects.</p><p class="framer-text framer-styles-preset-ivdbsg">Update your <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function to prepend historical messages before the latest one:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.jsx-:R1l6ktaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.jsx-:R1l6ktaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-javascript sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"># <span class="sp-syntax-punctuation">...</span>
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-plain">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span><span class="sp-syntax-punctuation">:</span> cl.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">None</span><span class="sp-syntax-punctuation">:</span>
|
||
# <span class="sp-syntax-plain">Retrieve </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">response </span><span class="sp-syntax-plain">from </span><span class="sp-syntax-plain">the </span><span class="sp-syntax-plain">LLM</span>
|
||
<span class="sp-syntax-plain">response</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">chat</span>.<span class="sp-syntax-property">completions</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">messages</span>=<span class="sp-syntax-punctuation">[</span>
|
||
# <span class="sp-syntax-plain">Prepend </span>all<span class="sp-syntax-plain"> </span>previous<span class="sp-syntax-plain"> </span>messages<span class="sp-syntax-plain"> </span>to<span class="sp-syntax-plain"> </span>maintain<span class="sp-syntax-plain"> </span>the<span class="sp-syntax-plain"> </span>conversation.
|
||
*<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">chat_context</span>.<span class="sp-syntax-property">to_openai</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"content"</span><span class="sp-syntax-punctuation">:</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-string">"role"</span><span class="sp-syntax-punctuation">:</span> <span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">response</span>.<span class="sp-syntax-property">choices</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-static">0</span><span class="sp-syntax-punctuation">]</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Now we can ask follow-up questions!</p><img alt width="1266" height="1072" src="https://framerusercontent.com/images/7R4WW6cJTza8wXfDY1VkpwvIW0.png?width=2532&height=2144" srcset="https://framerusercontent.com/images/7R4WW6cJTza8wXfDY1VkpwvIW0.png?scale-down-to=512&width=2532&height=2144 512w,https://framerusercontent.com/images/7R4WW6cJTza8wXfDY1VkpwvIW0.png?scale-down-to=1024&width=2532&height=2144 1024w,https://framerusercontent.com/images/7R4WW6cJTza8wXfDY1VkpwvIW0.png?scale-down-to=2048&width=2532&height=2144 2048w,https://framerusercontent.com/images/7R4WW6cJTza8wXfDY1VkpwvIW0.png?width=2532&height=2144 2532w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2532 / 2144" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><ol start="3" class="framer-text"><li data-preset-tag="h3" class="framer-text"><h3 class="framer-text">Faster feedback 🏎️</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">After completing the first few steps, you may have noticed that when you ask questions that require a long response, there’s a delay before you see anything. This can make for a poor user experience (especially later in step 6, when we start adding long-running tool calls) so let’s fix that.</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">At the end of this step, you’ll be able to see your bot “type” in real-time, similar to ChatGPT.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Stream it</h5><p class="framer-text framer-styles-preset-ivdbsg">To get real-time message updates, we need to update our implementation to use a “stream”. Basically, whenever we receive a message, we’ll respond immediately with an <em class="framer-text">empty</em> message, start a stream with the LLM, and update our empty message every time we receive a new chunk of the response from the stream.</p><p class="framer-text framer-styles-preset-ivdbsg">This might sound complicated, but it’s surprisingly easy! Update your <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function as follows:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1l94taop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1l94taop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-comment"># ...</span>
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Send an empty initial message that we can update with a streaming</span>
|
||
<span class="sp-syntax-comment"># response.</span>
|
||
<span class="sp-syntax-plain">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream the response from the LLM</span>
|
||
<span class="sp-syntax-plain">stream</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">chat</span>.<span class="sp-syntax-property">completions</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">messages</span>=<span class="sp-syntax-punctuation">[</span>
|
||
<span class="sp-syntax-comment"># Prepend all previous messages to maintain the conversation.</span>
|
||
<span class="sp-syntax-keyword">*</span><span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">chat_context</span>.<span class="sp-syntax-property">to_openai</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"content"</span>: <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-string">"role"</span>: <span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">stream</span>=<span class="sp-syntax-static">True</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-comment"># Update the existing (initially-empty) message with new content</span>
|
||
<span class="sp-syntax-comment"># from each "chunk" in the stream.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">chunk</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">token</span> := <span class="sp-syntax-plain">chunk</span>.<span class="sp-syntax-property">choices</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-static">0</span><span class="sp-syntax-punctuation">]</span>.<span class="sp-syntax-property">delta</span>.<span class="sp-syntax-property">content</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">token</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-comment"># Send a final update to let the message know it's complete.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🧑💻 So, here's the complete code so far:</strong></p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1l9ktaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1l9ktaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">os</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">chainlit</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">cl</span>
|
||
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AsyncOpenAI</span>
|
||
|
||
<span class="sp-syntax-comment">##</span>
|
||
<span class="sp-syntax-comment"># Settings</span>
|
||
<span class="sp-syntax-comment">#</span>
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-plain">OPENAI_API_KEY</span> = <span class="sp-syntax-plain">os</span>.<span class="sp-syntax-property">environ</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"OPENAI_API_KEY"</span><span class="sp-syntax-punctuation">]</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">KeyError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-keyword">raise</span> <span class="sp-syntax-definition">LookupError</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Missing required environment variable: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-plain">client</span> = <span class="sp-syntax-definition">AsyncOpenAI</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">api_key</span>=<span class="sp-syntax-plain">OPENAI_API_KEY</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Send an empty initial message that we can update with a streaming</span>
|
||
<span class="sp-syntax-comment"># response.</span>
|
||
<span class="sp-syntax-plain">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream the response from the LLM</span>
|
||
<span class="sp-syntax-plain">stream</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">chat</span>.<span class="sp-syntax-property">completions</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">messages</span>=<span class="sp-syntax-punctuation">[</span>
|
||
<span class="sp-syntax-comment"># Prepend all previous messages to maintain the conversation.</span>
|
||
<span class="sp-syntax-keyword">*</span><span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">chat_context</span>.<span class="sp-syntax-property">to_openai</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"content"</span>: <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-string">"role"</span>: <span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">stream</span>=<span class="sp-syntax-static">True</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Update the existing (initially-empty) message with the content</span>
|
||
<span class="sp-syntax-comment"># from each "chunk" in the stream.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">chunk</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">token</span> := <span class="sp-syntax-plain">chunk</span>.<span class="sp-syntax-property">choices</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-static">0</span><span class="sp-syntax-punctuation">]</span>.<span class="sp-syntax-property">delta</span>.<span class="sp-syntax-property">content</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-string">""</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">token</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Send a final update to let the message know it's complete.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Try it out</h5><p class="framer-text framer-styles-preset-ivdbsg">Now, when you ask a question, you should see your bot “typing” in real-time!</p><img alt width="576" height="425" src="https://framerusercontent.com/images/oiqGcKBiE976ok8jDX0pyKNa80E.gif?width=1152&height=850" srcset="https://framerusercontent.com/images/oiqGcKBiE976ok8jDX0pyKNa80E.gif?scale-down-to=512&width=1152&height=850 512w,https://framerusercontent.com/images/oiqGcKBiE976ok8jDX0pyKNa80E.gif?scale-down-to=1024&width=1152&height=850 1024w,https://framerusercontent.com/images/oiqGcKBiE976ok8jDX0pyKNa80E.gif?width=1152&height=850 1152w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:1152 / 850" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><h3 class="framer-text"><br class="framer-text trailing-break"></h3><ol start="4" class="framer-text"><li data-preset-tag="h3" class="framer-text"><h3 class="framer-text">Give your Agent a specialization and personality 💁♂️</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">By this point, we’ve built a lightweight clone of ChatGPT. That’s cool and all, but what we really want is an assistant that will help us perform a specific task: in this case, we want it to troubleshoot incidents, like an SRE would.</p><p class="framer-text framer-styles-preset-ivdbsg">To get there, we’ll start by refactoring our agent into a custom OpenAI Assistant, which will give us control over the system prompt (as well as the ability to give the LLM access to tools like file search and function calls, which we’ll cover later).</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">By the end of this step, you’ll have refactored your bot into a custom “Assistant” and customize its system prompt to give it its own “personality”. Your code will also use a “thread”, which will persist messages using the OpenAI API, instead of having to send all messages every time we receive a new one.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Create an Assistant & Thread</h5><p class="framer-text framer-styles-preset-ivdbsg">Creating an Assistant is straightforward: we just need to call the OpenAI Assistants API. However, we only want to do this once when the application starts up, so we can’t put that API call in the <code class="framer-text framer-styles-preset-1hyt0we">handle_message</code> function.</p><p class="framer-text framer-styles-preset-ivdbsg">Instead, we’ll use another Chainlit hook — <code class="framer-text framer-styles-preset-1hyt0we">on_chat_start</code>, which will only run once when the application first starts — to set up our assistant.</p><p class="framer-text framer-styles-preset-ivdbsg">Add this to your <code class="framer-text framer-styles-preset-1hyt0we">app.py</code>:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1ld4taop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1ld4taop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-comment"># ...</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant a name.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span> = <span class="sp-syntax-string">"roger"</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant some custom instructions.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span> = <span class="sp-syntax-string">"""
|
||
You are an expert Site Reliability Engineer, tasked with helping
|
||
the SRE team respond to and resolve incidents.
|
||
|
||
If you are presented with a question that does not seem like it
|
||
could be related to infrastructure, begin your response with a polite
|
||
reminder that your primary responsibilities are to help with incident
|
||
response, before fully answering the question to the best of your ability.
|
||
"""</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_chat_start</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_chat_start</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">str</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing assistant so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_assistant</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_assistant</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">existing_assistant</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create an assistant if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">assistant</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Update the assistant so that it always has the latest instructions</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a thread for the conversation</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the assistant and the new thread to the user session so that</span>
|
||
<span class="sp-syntax-comment"># we can reference it in other handlers.</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">assistant</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">thread</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># ...</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg"><br class="framer-text trailing-break"></p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">Note:</strong> it’s technically possible to give the assistant a custom system prompt by providing an initial system-type message in the message history in handle_message. However, we’re refactoring into an assistant with custom instructions because it unlocks several other capabilities that we’ll use in the near future.</p></blockquote><h5 class="framer-text framer-styles-preset-1pof2ao">Refactor Message Handling to use the Assistant</h5><p class="framer-text framer-styles-preset-ivdbsg">Now that we have an Assistant and a Thread for the conversation, we can refactor our message handler to use them.</p><p class="framer-text framer-styles-preset-ivdbsg">First, we need an <code class="framer-text framer-styles-preset-1hyt0we">AssistantEventHandler</code> to tell our new <code class="framer-text framer-styles-preset-1hyt0we">Assistant</code> object how to handle the various events that occur during a conversation.</p><p class="framer-text framer-styles-preset-ivdbsg">Add the following to your <code class="framer-text framer-styles-preset-1hyt0we">app.py</code>:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1lestaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1lestaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">typing</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AsyncAssistantEventHandler</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">Text</span>
|
||
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
|
||
<span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-string">"""An event handler for updating a Chainlit message while streaming an OpenAI response."""</span>
|
||
|
||
<span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">text</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new message so that we can update it."""</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_delta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">delta</span>: <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">snapshot</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the latest text delta streamed to us."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">delta</span>.<span class="sp-syntax-property">value</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_message_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the final text when the stream completes."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Now, we just need to adjust our handle_message function to use all of our new toys! Update your handle_message function to the following:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1lfctaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1lfctaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-comment"># ...</span>
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Retrieve our Assistant and Thread from our user session.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the latest message to the thread.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">messages</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">role</span>=<span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream a response to the Thread (called a "Run") using our Assistant.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Use our custom message handler.</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🧑💻 Now here's the complete code so far:</strong></p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:R1lfstaop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:R1lfstaop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">os</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">typing</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">chainlit</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">cl</span>
|
||
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AsyncOpenAI</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">AsyncAssistantEventHandler</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">Text</span>
|
||
|
||
|
||
<span class="sp-syntax-comment">##</span>
|
||
<span class="sp-syntax-comment"># Settings</span>
|
||
<span class="sp-syntax-comment">#</span>
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-plain">OPENAI_API_KEY</span> = <span class="sp-syntax-plain">os</span>.<span class="sp-syntax-property">environ</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"OPENAI_API_KEY"</span><span class="sp-syntax-punctuation">]</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">KeyError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-keyword">raise</span> <span class="sp-syntax-definition">LookupError</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Missing required environment variable: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant a name.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span> = <span class="sp-syntax-string">"roger"</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant some custom instructions.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span> = <span class="sp-syntax-string">"""
|
||
You are an expert Site Reliability Engineer, tasked with helping
|
||
the SRE team respond to and resolve incidents.
|
||
|
||
If you are presented with a question that does not seem like it
|
||
could be related to infrastructure, begin your response with a polite
|
||
reminder that your primary responsibilities are to help with incident
|
||
response, before fully answering the question to the best of your ability.
|
||
"""</span>
|
||
|
||
|
||
<span class="sp-syntax-plain">client</span> = <span class="sp-syntax-definition">AsyncOpenAI</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">api_key</span>=<span class="sp-syntax-plain">OPENAI_API_KEY</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
<span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-string">"""An event handler for updating a Chainlit message while streaming an OpenAI response."""</span>
|
||
|
||
<span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">text</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new message so that we can update it."""</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_delta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">delta</span>: <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">snapshot</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the latest text delta streamed to us."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">delta</span>.<span class="sp-syntax-property">value</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_message_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the final text when the stream completes."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_chat_start</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_chat_start</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">str</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing assistant so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_assistant</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_assistant</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">existing_assistant</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create an assistant if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">assistant</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Update the assistant so that it always has the latest instructions</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a thread for the conversation</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the assistant and the new thread to the user session so that</span>
|
||
<span class="sp-syntax-comment"># we can reference it in other handlers.</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">assistant</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">thread</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Retrieve our Assistant and Thread from our user session.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the latest message to the thread.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">messages</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">role</span>=<span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream a response to the Thread (called a "Run") using our Assistant.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Use our custom message handler.</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div></div><div class="framer-1dl4bqz" data-framer-name="Content 2" data-framer-component-type="RichTextContainer" style="transform:none"><ol start="5" class="framer-text"><li data-preset-tag="h3" class="framer-text framer-styles-preset-wnzxc5"><h3 class="framer-text framer-styles-preset-wnzxc5">Give your Agent access to your documentation</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">Now that we’re using Assistants and Threads, we can start customizing behavior. To start, we’ll give our Assistant access to some of our internal documentation so that it can provide responses that are more tailored to our use case.</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">By the end of this section, you’ll have given your bot the ability to search a collection of files (e.g., your SRE runbooks and other internal documentation) when responding to prompts.</p><p class="framer-text framer-styles-preset-ivdbsg">For simplicity, we’ll implement this as a folder full of files that get uploaded into a vector store and provided to our Assistant.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Creating a Vector Store</h5><p class="framer-text framer-styles-preset-ivdbsg">The first thing we need to do is create a vector store and provide it to our Assistant.</p><p class="framer-text framer-styles-preset-ivdbsg">First, update the beginning of our <code class="framer-text framer-styles-preset-1hyt0we">handle_chat_start</code> function to include the following:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rd2d5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rd2d5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-comment"># ...</span>
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_chat_start</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_chat_start</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">str</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-keyword">None</span>
|
||
<span class="sp-syntax-comment"># Try to find an existing vector store so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_vector_store</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_vector_store</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">existing_vector_store</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
<span class="sp-syntax-comment"># Create a vector store if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">vector_store</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">None</span>
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
<span class="sp-syntax-comment"># ...</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Next, update the call to <code class="framer-text framer-styles-preset-1hyt0we">client.beta.assistants.update()</code> to give the assistant access to the vector store and enable the <code class="framer-text framer-styles-preset-1hyt0we">file_search</code> tool.</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rd2t5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rd2t5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tools</span>=<span class="sp-syntax-punctuation">[</span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"file_search"</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_resources</span>=<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"file_search"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"vector_store_ids"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Upload Documentation</h5><p class="framer-text framer-styles-preset-ivdbsg">Finally, we’ll need to upload our documentation that we want our assistant to reference when answering prompts.</p><p class="framer-text framer-styles-preset-ivdbsg">First, we’ll need to create a folder where we’ll put our documents:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.sh-:Rd3t5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.sh-:Rd3t5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-shell sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-plain">mkdir</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Next, we’ll collect our documentation and put it in that folder. For testing purposes, I’ve added the following fake document to my folder:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.md-:Rd4d5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.md-:Rd4d5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-markdown sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)">---
|
||
title: SRE Runbook
|
||
url: <span class="sp-syntax-string"><https://internal.aptible.com/docs/runbooks/sre></span>
|
||
|
||
</code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Finally, we’ll update our <code class="framer-text framer-styles-preset-1hyt0we">handle_chat_start</code> function to automatically upload our documents to the vector store we created earlier. Add the following code just after where we create the vector store:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rd4t5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rd4t5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">documents</span> := <span class="sp-syntax-definition">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-definition">Path</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"./docs"</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">glob</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"**/*.md"</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">file_batches</span>.<span class="sp-syntax-property">upload_and_poll</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">vector_store_id</span>=<span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">files</span>=<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">f</span>.<span class="sp-syntax-property">open</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"rb"</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">f</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">documents</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg">ℹ️ Note:</p><p class="framer-text framer-styles-preset-ivdbsg">For now, we’ll only support <code class="framer-text framer-styles-preset-1hyt0we">.md</code> files, but <!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="https://platform.openai.com/docs/assistants/tools/file-search/supported-files" rel="noopener">OpenAI supports lots of different file types</a><!--/$-->, so feel free to update the glob pattern to whatever makes sense for your use case!</p></blockquote><p class="framer-text framer-styles-preset-ivdbsg">This will automatically upload all of the files in the <code class="framer-text framer-styles-preset-1hyt0we">./docs</code> folder and add them to our vector store.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Add an indicator</h5><p class="framer-text framer-styles-preset-ivdbsg">File search can sometimes take a while, especially for larger datasets. In those cases, you’ll probably want to let the user know what’s going on so they don’t get frustrated.</p><p class="framer-text framer-styles-preset-ivdbsg">Luckily, Chainlit makes this easy by providing a <code class="framer-text framer-styles-preset-1hyt0we">Step</code> class that we can use to tell the user that something’s happening in the background. We can use the <code class="framer-text framer-styles-preset-1hyt0we">Step</code> class in conjunction with the <code class="framer-text framer-styles-preset-1hyt0we">MessageEventHandler</code> we built earlier, and add an indicator any time a tool is called.</p><p class="framer-text framer-styles-preset-ivdbsg">Add the following to your <code class="framer-text framer-styles-preset-1hyt0we">MessageEventHandler</code>:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rd6l5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rd6l5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_tool_call_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">tool_call</span>: <span class="sp-syntax-plain">ToolCall</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new step in the conversation to indicate that a tool is being used."""</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Step</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">tool_call</span>.<span class="sp-syntax-property">type</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">step</span>:
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">step</span> = <span class="sp-syntax-plain">step</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Try it out</h5><p class="framer-text framer-styles-preset-ivdbsg">Now that you’ve uploaded some of your own documentation, try asking some questions that are more specific to your use case, and see what you get!</p><p class="framer-text framer-styles-preset-ivdbsg">For our test case, it correctly referenced our runbook when asked about high CPU utilization on a customer database:</p><img alt width="1266" height="964" src="https://framerusercontent.com/images/JITkvpUefbGXDSjFFrNKPTmujrc.png?width=2532&height=1928" srcset="https://framerusercontent.com/images/JITkvpUefbGXDSjFFrNKPTmujrc.png?scale-down-to=512&width=2532&height=1928 512w,https://framerusercontent.com/images/JITkvpUefbGXDSjFFrNKPTmujrc.png?scale-down-to=1024&width=2532&height=1928 1024w,https://framerusercontent.com/images/JITkvpUefbGXDSjFFrNKPTmujrc.png?scale-down-to=2048&width=2532&height=1928 2048w,https://framerusercontent.com/images/JITkvpUefbGXDSjFFrNKPTmujrc.png?width=2532&height=1928 2532w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:2532 / 1928" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🧑💻 For reference, here's the complete code so far:</strong></p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rd855aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rd855aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">os</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">pathlib</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Path</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">typing</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">chainlit</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">cl</span>
|
||
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AsyncOpenAI</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">AsyncAssistantEventHandler</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">Text</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span>.<span class="sp-syntax-plain">runs</span>.<span class="sp-syntax-plain">tool_call</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">ToolCall</span>
|
||
|
||
<span class="sp-syntax-comment">##</span>
|
||
<span class="sp-syntax-comment"># Settings</span>
|
||
<span class="sp-syntax-comment">#</span>
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-plain">OPENAI_API_KEY</span> = <span class="sp-syntax-plain">os</span>.<span class="sp-syntax-property">environ</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"OPENAI_API_KEY"</span><span class="sp-syntax-punctuation">]</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">KeyError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-keyword">raise</span> <span class="sp-syntax-definition">LookupError</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Missing required environment variable: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant a name.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span> = <span class="sp-syntax-string">"roger"</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant some custom instructions.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span> = <span class="sp-syntax-string">"""
|
||
You are an expert Site Reliability Engineer, tasked with helping
|
||
the SRE team respond to and resolve incidents.
|
||
|
||
If you are presented with a question that does not seem like it
|
||
could be related to infrastructure, begin your response with a polite
|
||
reminder that your primary responsibilities are to help with incident
|
||
response, before fully answering the question to the best of your ability.
|
||
"""</span>
|
||
|
||
<span class="sp-syntax-plain">client</span> = <span class="sp-syntax-definition">AsyncOpenAI</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">api_key</span>=<span class="sp-syntax-plain">OPENAI_API_KEY</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-string">"""An event handler for updating a Chainlit message while streaming an OpenAI response."""</span>
|
||
|
||
<span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">text</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new message so that we can update it."""</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_delta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">delta</span>: <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">snapshot</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the latest text delta streamed to us."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">delta</span>.<span class="sp-syntax-property">value</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_message_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the final text when the stream completes."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_tool_call_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">tool_call</span>: <span class="sp-syntax-plain">ToolCall</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new step in the conversation to indicate that a tool is being used."""</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Step</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">tool_call</span>.<span class="sp-syntax-property">type</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">step</span>:
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">step</span> = <span class="sp-syntax-plain">step</span>
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_chat_start</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_chat_start</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">str</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing vector store so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_vector_store</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_vector_store</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">existing_vector_store</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a vector store if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">vector_store</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">documents</span> := <span class="sp-syntax-definition">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-definition">Path</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"./docs"</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">glob</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"**/*.md"</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">file_batches</span>.<span class="sp-syntax-property">upload_and_poll</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">vector_store_id</span>=<span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">files</span>=<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">f</span>.<span class="sp-syntax-property">open</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"rb"</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">f</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">documents</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing assistant so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_assistant</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_assistant</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">existing_assistant</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create an assistant if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">assistant</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Update the assistant so that it always has the latest instructions</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tools</span>=<span class="sp-syntax-punctuation">[</span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"file_search"</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_resources</span>=<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"file_search"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"vector_store_ids"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a thread for the conversation</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the assistant and the new thread to the user session so that</span>
|
||
<span class="sp-syntax-comment"># we can reference it in other handlers.</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">assistant</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">thread</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Retrieve our Assistant and Thread from our user session.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the latest message to the thread.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">messages</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">role</span>=<span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream a response to the Thread (called a "Run") using our Assistant.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Use our custom message handler.</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h4 class="framer-text framer-styles-preset-1mbxhs3"><br class="framer-text trailing-break"></h4><ol start="6" class="framer-text"><li data-preset-tag="h3" class="framer-text framer-styles-preset-wnzxc5"><h3 class="framer-text framer-styles-preset-wnzxc5">Integrate your agent with external tools</h3></li></ol><p class="framer-text framer-styles-preset-ivdbsg">Our Agent is now able to retrieve data from our curated internal documentation, which is useful if you have good documentation. However, a lot of time in incident management is often spent investigating things that aren’t covered by the docs: scanning alerts, reading logs, interpreting metrics, etc.</p><p class="framer-text framer-styles-preset-ivdbsg">For those things, we want to give our assistant the ability to call external APIs — and more broadly, execute functions that we define — so that it can gather more context on an as-needed basis.</p><p class="framer-text framer-styles-preset-ivdbsg">To do this, we’ll leverage the “function calling” capabilities of the model to execute functions that we define.</p><h5 class="framer-text framer-styles-preset-1pof2ao">The Goal</h5><p class="framer-text framer-styles-preset-ivdbsg">By the end of this step, you’ll have given your bot the ability to use an external tool (a fake PagerDuty tool) to retrieve information when answering prompts.</p><h5 class="framer-text framer-styles-preset-1pof2ao">Define the (fake) Tool</h5><p class="framer-text framer-styles-preset-ivdbsg">First, let’s add a new function to our <code class="framer-text framer-styles-preset-1hyt0we"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" href="http://app.py/" rel="noopener">app.py</a><!--/$--></code> called <code class="framer-text framer-styles-preset-1hyt0we">get_pagerduty_alert_details</code>.</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rdal5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rdal5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">json</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">datetime</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">datetime</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">timedelta</span>
|
||
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">get_pagerduty_alert_details</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">pagerduty_alert_url</span>: <span class="sp-syntax-plain">str</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">dict</span>:
|
||
<span class="sp-syntax-string">"""Return the details of the PagerDuty alert at the given URL."""</span>
|
||
<span class="sp-syntax-comment"># TODO: Make this implementation real!</span>
|
||
<span class="sp-syntax-keyword">return</span> <span class="sp-syntax-plain">json</span>.<span class="sp-syntax-property">dumps</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"alert"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"id"</span>: <span class="sp-syntax-string">"PT4KHLK"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"alert"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"summary"</span>: <span class="sp-syntax-string">"A customer database is experiencing high CPU usage."</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"self"</span>: <span class="sp-syntax-plain">pagerduty_alert_url</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"html_url"</span>: <span class="sp-syntax-plain">pagerduty_alert_url</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"created_at"</span>: <span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">datetime</span>.<span class="sp-syntax-property">now</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> - <span class="sp-syntax-definition">timedelta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">minutes</span>=<span class="sp-syntax-static">5</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">isoformat</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"status"</span>: <span class="sp-syntax-string">"resolved"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"alert_key"</span>: <span class="sp-syntax-string">"baf7cf21b1da41b4b0221008339ff357"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"suppressed"</span>: <span class="sp-syntax-static">False</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"severity"</span>: <span class="sp-syntax-string">"critical"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Tell the LLM how to use it</h5><p class="framer-text framer-styles-preset-ivdbsg">Next, we need to tell the LLM how to call our tool. OpenAI expects tool definitions in JSONSchema format.</p><p class="framer-text framer-styles-preset-ivdbsg">Update your call to <code class="framer-text framer-styles-preset-1hyt0we">client.beta.assistants.update()</code> to include a new tool definition after the <code class="framer-text framer-styles-preset-1hyt0we">file_search</code> tool that we already have.</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rdbl5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rdbl5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tools</span>=<span class="sp-syntax-punctuation">[</span>
|
||
<span class="sp-syntax-comment"># Our existing file search tool</span>
|
||
<span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"file_search"</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Our new pagerduty alert tool</span>
|
||
<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"function"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"function"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"name"</span>: <span class="sp-syntax-plain">get_pagerduty_alert_details</span>.<span class="sp-syntax-property">__name__</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"description"</span>: <span class="sp-syntax-plain">get_pagerduty_alert_details</span>.<span class="sp-syntax-property">__doc__</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"parameters"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"object"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"properties"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"pagerduty_alert_url"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"string"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"format"</span>: <span class="sp-syntax-string">"uri"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"description"</span>: <span class="sp-syntax-string">"The PagerDuty alert URL. For example, '<https://example.pagerduty.com/alerts/Q3YDP8VKEZ9THL>'."</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"required"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"pagerduty_alert_url"</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"additionalProperties"</span>: <span class="sp-syntax-static">False</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_resources</span>=<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"file_search"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"vector_store_ids"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Update the message handler to handle tool calls</h5><p class="framer-text framer-styles-preset-ivdbsg">Our <code class="framer-text framer-styles-preset-1hyt0we">MessageEventHandler</code> currently handles back-and-forth message events, but calling tools requires some special handling.</p><p class="framer-text framer-styles-preset-ivdbsg">When responding to your prompt, the LLM will decide which tools it should call (if any) and return to you one or more “tool call” definitions in the response payload, and tell you that the response “requires action”. In order to actually execute the function, we need to handle these “requires action” responses.</p><p class="framer-text framer-styles-preset-ivdbsg">We can do this by updating our <code class="framer-text framer-styles-preset-1hyt0we">MessageEventHandler</code> class to implement the <code class="framer-text framer-styles-preset-1hyt0we">on_event</code> method, along with a new <code class="framer-text framer-styles-preset-1hyt0we">handle_requires_action</code> method for executing our function call and adding the result to the running thread:</p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rdct5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rdct5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AssistantStreamEvent</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Run</span>
|
||
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
|
||
<span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-comment"># ...</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_event</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">event</span>: <span class="sp-syntax-plain">AssistantStreamEvent</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Handle specific events."""</span>
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">event</span> == <span class="sp-syntax-string">"thread.run.requires_action"</span>:
|
||
<span class="sp-syntax-plain">run_id</span> = <span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">data</span>.<span class="sp-syntax-property">id</span> <span class="sp-syntax-comment"># Retrieve the run ID from the event data</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">current_run</span>.<span class="sp-syntax-property">id</span> = <span class="sp-syntax-plain">run_id</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">handle_requires_action</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">data</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run_id</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_requires_action</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run</span>: <span class="sp-syntax-plain">Run</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run_id</span>: <span class="sp-syntax-plain">str</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Handle events that require an action to be taken, like tool calls."""</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span> = <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-punctuation">]</span>
|
||
|
||
<span class="sp-syntax-comment"># Execute each tool call and collect the result.</span>
|
||
<span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">tool</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">run</span>.<span class="sp-syntax-property">required_action</span>.<span class="sp-syntax-property">submit_tool_outputs</span>.<span class="sp-syntax-property">tool_calls</span>:
|
||
<span class="sp-syntax-plain">func_name</span> = <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">function</span>.<span class="sp-syntax-property">name</span>
|
||
<span class="sp-syntax-plain">func_args</span> = <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">function</span>.<span class="sp-syntax-property">arguments</span>
|
||
|
||
<span class="sp-syntax-comment"># TODO: Build a function map with a decorator instead of looking up</span>
|
||
<span class="sp-syntax-comment"># functions in globals.</span>
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">func_to_call</span> := <span class="sp-syntax-definition">globals</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-punctuation">]</span>:
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-comment"># Parse the func_args JSON string to a dictionary</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span>.<span class="sp-syntax-property">append</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"tool_call_id"</span>: <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"output"</span>: <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-definition">func_to_call</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-keyword">**</span><span class="sp-syntax-plain">json</span>.<span class="sp-syntax-property">loads</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">func_args</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">TypeError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-definition">print</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Error calling function </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-keyword">!r</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-definition">str</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">else</span>:
|
||
<span class="sp-syntax-definition">print</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Function </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-keyword">!r</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string"> not found"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Submit tool outputs to the conversation thread.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">submit_tool_outputs_stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">current_run</span>.<span class="sp-syntax-property">thread_id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">run_id</span>=<span class="sp-syntax-plain">run_id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span>=<span class="sp-syntax-plain">tool_outputs</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># ...</span></code></pre></div></div></div></div></div></div><h5 class="framer-text framer-styles-preset-1pof2ao">Tweak the prompt</h5><p class="framer-text framer-styles-preset-ivdbsg">It can often be helpful to remind the LLM that it should attempt to use the tools you’ve provided when applicable. Add a line like this one to the end of your prompt:</p><blockquote class="framer-text framer-styles-preset-go1021"><p class="framer-text framer-styles-preset-ivdbsg"><em class="framer-text">Use the provided tools to gather additional context about the incident, if applicable.</em></p></blockquote><h5 class="framer-text framer-styles-preset-1pof2ao">Try it out</h5><p class="framer-text framer-styles-preset-ivdbsg">With your tools configured, you’ll be able to include PagerDuty links in your prompts, and the LLM will use those tools to gather context before answering:</p><img alt width="587" height="439" src="https://framerusercontent.com/images/jQpybpHTdl6R8W5l1KiKl4JqLk.gif?width=1175&height=879" srcset="https://framerusercontent.com/images/jQpybpHTdl6R8W5l1KiKl4JqLk.gif?scale-down-to=512&width=1175&height=879 512w,https://framerusercontent.com/images/jQpybpHTdl6R8W5l1KiKl4JqLk.gif?scale-down-to=1024&width=1175&height=879 1024w,https://framerusercontent.com/images/jQpybpHTdl6R8W5l1KiKl4JqLk.gif?width=1175&height=879 1175w" class="framer-text framer-image framer-styles-preset-17grp1i" style="aspect-ratio:1175 / 879" data-framer-original-sizes sizes="(min-width: 1440px) 100vw, (min-width: 1200px) and (max-width: 1439.98px) 100vw, (min-width: 810px) and (max-width: 1199.98px) 100vw, (max-width: 809.98px) 100vw"><p class="framer-text framer-styles-preset-ivdbsg"><strong class="framer-text">🧑💻 Here's the complete code:</strong></p><div class="framer-text framer-text-module" style="width:100%;height:auto"><div class="framer-cb" style="--cb-colors-surface1-light:#161820;--cb-colors-surface2-light:#252525;--cb-colors-surface3-light:#2f2f2f;--cb-colors-clickable-light:#999999;--cb-colors-base-light:#d92121;--cb-colors-disabled-light:#4d4d4d;--cb-colors-hover-light:#c5c5c5;--cb-colors-accent-light:#0099ff;--cb-colors-error-light:#ff3366;--cb-colors-errorSurface-light:#ffe0e8;--cb-syntax-color-plain-light:#eeeeee;--cb-syntax-color-comment-light:#666666;--cb-syntax-fontStyle-comment-light:italic;--cb-syntax-color-keyword-light:#00BBFF;--cb-syntax-color-tag-light:#00BBFF;--cb-syntax-color-punctuation-light:#999999;--cb-syntax-color-definition-light:#ffcc66;--cb-syntax-color-property-light:#77dddd;--cb-syntax-color-static-light:#ff8866;--cb-syntax-color-string-light:#bb88ff;--cb-color-scheme-light:dark;--cb-colors-surface1-dark:#161820;--cb-colors-surface2-dark:#252525;--cb-colors-surface3-dark:#2f2f2f;--cb-colors-clickable-dark:#999999;--cb-colors-base-dark:#d92121;--cb-colors-disabled-dark:#4d4d4d;--cb-colors-hover-dark:#c5c5c5;--cb-colors-accent-dark:#0099ff;--cb-colors-error-dark:#ff3366;--cb-colors-errorSurface-dark:#ffe0e8;--cb-syntax-color-plain-dark:#eeeeee;--cb-syntax-color-comment-dark:#666666;--cb-syntax-fontStyle-comment-dark:italic;--cb-syntax-color-keyword-dark:#00BBFF;--cb-syntax-color-tag-dark:#00BBFF;--cb-syntax-color-punctuation-dark:#999999;--cb-syntax-color-definition-dark:#ffcc66;--cb-syntax-color-property-dark:#77dddd;--cb-syntax-color-static-dark:#ff8866;--cb-syntax-color-string-dark:#bb88ff;--cb-color-scheme-dark:dark;position:relative;width:100%;height:100%"><div style="display:contents" data-framer-css-ssr="true"><style>--sxs{--sxs:1 sp-k-eyOShd sp-k-iOHdLQ}@media{@keyframes sp-k-eyOShd{0%{opacity:0}100%{opacity:1}}@keyframes sp-k-iOHdLQ{0%{transform:rotateX(-25.5deg) rotateY(45deg)}100%{transform:rotateX(-25.5deg) rotateY(405deg)}}}--sxs{--sxs:2 sp-c-gMfcns sp-c-bxeRRt sp-c-jKPvnt sp-c-fWymNx sp-c-euXojQ sp-c-bpmgvy sp-c-PJLV}@media{.sp-c-gMfcns svg{margin:auto}.sp-c-bxeRRt{-webkit-appearance:none;appearance:none;outline:none;display:flex;align-items:center;font-size:inherit;font-family:inherit;background-color:transparent;transition:color var(--sp-transitions-default), background var(--sp-transitions-default);cursor:pointer;color:var(--sp-colors-clickable);border:0;text-decoration:none}.sp-c-bxeRRt:disabled{color:var(--sp-colors-disabled)}.sp-c-bxeRRt:hover:not(:disabled,[data-active='true']){color:var(--sp-colors-hover)}.sp-c-bxeRRt[data-active="true"]{color:var(--sp-colors-accent)}.sp-c-bxeRRt svg{min-width:var(--sp-space-4);width:var(--sp-space-4);height:var(--sp-space-4)}.sp-c-bxeRRt.sp-c-gMfcns{padding:var(--sp-space-1);height:var(--sp-space-7);display:flex}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:not(:has(span)){width:var(--sp-space-7)}.sp-c-bxeRRt.sp-c-gMfcns.sp-c-bxeRRt:has(svg + span){padding-right:var(--sp-space-3);padding-left:var(--sp-space-2);gap:var(--sp-space-1)}.sp-c-jKPvnt{padding:0 var(--sp-space-1) 0 var(--sp-space-1);border-radius:var(--sp-border-radius);margin-left:var(--sp-space-1);width:var(--sp-space-5);visibility:hidden;cursor:pointer;position:absolute;right:0px}.sp-c-jKPvnt svg{width:var(--sp-space-3);height:var(--sp-space-3);display:block;position:relative;top:1px}.sp-c-fWymNx{margin:0;display:block;font-family:var(--sp-font-mono);font-size:var(--sp-font-size);color:var(--sp-syntax-color-plain);line-height:var(--sp-font-lineHeight)}.sp-c-euXojQ{display:flex;flex-direction:column;width:100%;position:relative;background-color:var(--sp-colors-surface1);gap:1px}.sp-c-euXojQ:has(.sp-stack){background-color:var(--sp-colors-surface2)}.sp-c-bpmgvy{transform:translate(-4px, 9px) scale(0.13, 0.13)}.sp-c-bpmgvy *{position:absolute;width:96px;height:96px}}--sxs{--sxs:3 sp-c-PJLV-kCOVwI-status-pass sp-c-PJLV-kEzYsr-status-fail sp-c-PJLV-gHAhSA-status-skip sp-c-PJLV-jgnHyR-status-title sp-c-PJLV-iCgxLS-status-run sp-c-PJLV-bnDZSy-status-pass sp-c-PJLV-eYuGwt-status-fail}@media{.sp-c-PJLV-kCOVwI-status-pass{color:var(--test-pass)}.sp-c-PJLV-kEzYsr-status-fail{color:var(--test-fail)}.sp-c-PJLV-gHAhSA-status-skip{color:var(--test-skip)}.sp-c-PJLV-jgnHyR-status-title{color:var(--test-title)}.sp-c-PJLV-iCgxLS-status-run{background:var(--test-run);color:var(--sp-colors-surface1)}.sp-c-PJLV-bnDZSy-status-pass{background:var(--test-pass);color:var(--sp-colors-surface1)}.sp-c-PJLV-eYuGwt-status-fail{background:var(--test-fail);color:var(--sp-colors-surface1)}}</style></div><div class="sp-919074184 sp-c-fVPbOs sp-c-fVPbOs-LrWkf-variant-dark sp-wrapper" style="height:100%"><div class="sp-c-ikJbEZ sp-layout" style="height:100%;--sp-layout-height:100%;--cb-padding:30px;border-style:none;border-color:unset;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;border-right-width:0px;background-color:var(--sp-colors-surface1);border-radius:15px;transform:unset;overflow:hidden"><div class="sp-c-euXojQ sp-editor sp-stack" style="letter-spacing:0em;font-style:normal;font-weight:400"><div aria-labelledby="/example.py-:Rdet5aop:-tab" class="sp-c-gtcpyq sp-code-editor cb-code-editor" id="/example.py-:Rdet5aop:-tab-panel" role="tabpanel"><pre class="sp-pristine sp-python sp-c-jOWzsE sp-c-jkvvao sp-cm" translate="no"><code class="sp-c-fWymNx sp-pre-placeholder" style="margin-left:var(--sp-space-4)"><span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">datetime</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">datetime</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">timedelta</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">json</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">os</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">pathlib</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Path</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">typing</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">chainlit</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">cl</span>
|
||
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AsyncOpenAI</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">AsyncAssistantEventHandler</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">Text</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">AssistantStreamEvent</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span>.<span class="sp-syntax-plain">runs</span>.<span class="sp-syntax-plain">tool_call</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">ToolCall</span>
|
||
<span class="sp-syntax-keyword">from</span> <span class="sp-syntax-plain">openai</span>.<span class="sp-syntax-plain">types</span>.<span class="sp-syntax-plain">beta</span>.<span class="sp-syntax-plain">threads</span> <span class="sp-syntax-keyword">import</span> <span class="sp-syntax-plain">Run</span>
|
||
|
||
|
||
<span class="sp-syntax-comment">##</span>
|
||
<span class="sp-syntax-comment"># Settings</span>
|
||
<span class="sp-syntax-comment">#</span>
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-plain">OPENAI_API_KEY</span> = <span class="sp-syntax-plain">os</span>.<span class="sp-syntax-property">environ</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"OPENAI_API_KEY"</span><span class="sp-syntax-punctuation">]</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">KeyError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-keyword">raise</span> <span class="sp-syntax-definition">LookupError</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Missing required environment variable: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant a name.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span> = <span class="sp-syntax-string">"roger"</span>
|
||
|
||
<span class="sp-syntax-comment"># Give your assistant some custom instructions.</span>
|
||
<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span> = <span class="sp-syntax-string">"""
|
||
You are an expert Site Reliability Engineer, tasked with helping
|
||
the SRE team respond to and resolve incidents.
|
||
|
||
If you are presented with a question that does not seem like it
|
||
could be related to infrastructure, begin your response with a polite
|
||
reminder that your primary responsibilities are to help with incident
|
||
response, before fully answering the question to the best of your ability.
|
||
|
||
Use the provided tools to gather additional context about the incident, if
|
||
applicable.
|
||
"""</span>
|
||
|
||
|
||
<span class="sp-syntax-plain">client</span> = <span class="sp-syntax-definition">AsyncOpenAI</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">api_key</span>=<span class="sp-syntax-plain">OPENAI_API_KEY</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
<span class="sp-syntax-keyword">class</span> MessageEventHandler<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">AsyncAssistantEventHandler</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-string">"""An event handler for updating a Chainlit message while streaming an OpenAI response."""</span>
|
||
|
||
<span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">text</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new message so that we can update it."""</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">content</span>=<span class="sp-syntax-string">""</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">send</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_text_delta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">delta</span>: <span class="sp-syntax-plain">TextDelta</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">snapshot</span>: <span class="sp-syntax-plain">Text</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the latest text delta streamed to us."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">stream_token</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">delta</span>.<span class="sp-syntax-property">value</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_message_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Update the message with the final text when the stream completes."""</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">message</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_tool_call_created</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">tool_call</span>: <span class="sp-syntax-plain">ToolCall</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Create a new step in the conversation to indicate that a tool is being used."""</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Step</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">tool_call</span>.<span class="sp-syntax-property">type</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">step</span>:
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">step</span> = <span class="sp-syntax-plain">step</span>
|
||
|
||
@<span class="sp-syntax-plain">override</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">on_event</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">event</span>: <span class="sp-syntax-plain">AssistantStreamEvent</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Handle specific events."""</span>
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">event</span> == <span class="sp-syntax-string">"thread.run.requires_action"</span>:
|
||
<span class="sp-syntax-plain">run_id</span> = <span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">data</span>.<span class="sp-syntax-property">id</span> <span class="sp-syntax-comment"># Retrieve the run ID from the event data</span>
|
||
<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">current_run</span>.<span class="sp-syntax-property">id</span> = <span class="sp-syntax-plain">run_id</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">handle_requires_action</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">event</span>.<span class="sp-syntax-property">data</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run_id</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_requires_action</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">self</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run</span>: <span class="sp-syntax-plain">Run</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">run_id</span>: <span class="sp-syntax-plain">str</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-string">"""Handle events that require an action to be taken, like tool calls."""</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span> = <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-punctuation">]</span>
|
||
|
||
<span class="sp-syntax-comment"># Execute each tool call and collect the result.</span>
|
||
<span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">tool</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">run</span>.<span class="sp-syntax-property">required_action</span>.<span class="sp-syntax-property">submit_tool_outputs</span>.<span class="sp-syntax-property">tool_calls</span>:
|
||
<span class="sp-syntax-plain">func_name</span> = <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">function</span>.<span class="sp-syntax-property">name</span>
|
||
<span class="sp-syntax-plain">func_args</span> = <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">function</span>.<span class="sp-syntax-property">arguments</span>
|
||
|
||
<span class="sp-syntax-comment"># TODO: Build a function map with a decorator instead of looking up</span>
|
||
<span class="sp-syntax-comment"># functions in globals.</span>
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">func_to_call</span> := <span class="sp-syntax-definition">globals</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-punctuation">]</span>:
|
||
<span class="sp-syntax-keyword">try</span>:
|
||
<span class="sp-syntax-comment"># Parse the func_args JSON string to a dictionary</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span>.<span class="sp-syntax-property">append</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"tool_call_id"</span>: <span class="sp-syntax-plain">tool</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"output"</span>: <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-definition">func_to_call</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-keyword">**</span><span class="sp-syntax-plain">json</span>.<span class="sp-syntax-property">loads</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">func_args</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">except</span> <span class="sp-syntax-plain">TypeError</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">ex</span>:
|
||
<span class="sp-syntax-definition">print</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Error calling function </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-keyword">!r</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">: </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-definition">str</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">ex</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string">"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-keyword">else</span>:
|
||
<span class="sp-syntax-definition">print</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">f"Function </span><span class="sp-syntax-punctuation">{</span><span class="sp-syntax-plain">func_name</span><span class="sp-syntax-keyword">!r</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-string"> not found"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Submit tool outputs to the conversation thread.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">submit_tool_outputs_stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">self</span>.<span class="sp-syntax-property">current_run</span>.<span class="sp-syntax-property">thread_id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">run_id</span>=<span class="sp-syntax-plain">run_id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_outputs</span>=<span class="sp-syntax-plain">tool_outputs</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_chat_start</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_chat_start</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">str</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing vector store so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_vector_store</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_vector_store</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">existing_vector_store</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a vector store if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">vector_store</span> = <span class="sp-syntax-plain">vector_store</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">documents</span> := <span class="sp-syntax-definition">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-definition">Path</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"./docs"</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">glob</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"**/*.md"</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">vector_stores</span>.<span class="sp-syntax-property">file_batches</span>.<span class="sp-syntax-property">upload_and_poll</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">vector_store_id</span>=<span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">files</span>=<span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">f</span>.<span class="sp-syntax-property">open</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"rb"</span><span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">f</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-plain">documents</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">None</span>
|
||
|
||
<span class="sp-syntax-comment"># Try to find an existing assistant so we don't create duplicates.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">for</span> <span class="sp-syntax-plain">existing_assistant</span> <span class="sp-syntax-keyword">in</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">list</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>:
|
||
<span class="sp-syntax-keyword">if</span> <span class="sp-syntax-plain">existing_assistant</span>.<span class="sp-syntax-property">name</span> == <span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span>:
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">existing_assistant</span>
|
||
<span class="sp-syntax-keyword">break</span>
|
||
|
||
<span class="sp-syntax-comment"># Create an assistant if we didn't find an existing one.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">assistant</span> <span class="sp-syntax-keyword">or</span> <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">name</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_NAME</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">model</span>=<span class="sp-syntax-string">"gpt-4o"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Update the assistant so that it always has the latest instructions</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">assistants</span>.<span class="sp-syntax-property">update</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">instructions</span>=<span class="sp-syntax-plain">OPENAI_ASSISTANT_INSTRUCTIONS</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tools</span>=<span class="sp-syntax-punctuation">[</span>
|
||
<span class="sp-syntax-comment"># Our existing file search tool</span>
|
||
<span class="sp-syntax-punctuation">{</span><span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"file_search"</span><span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Our new pagerduty alert tool</span>
|
||
<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"function"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"function"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"name"</span>: <span class="sp-syntax-plain">get_pagerduty_alert_details</span>.<span class="sp-syntax-property">__name__</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"description"</span>: <span class="sp-syntax-plain">get_pagerduty_alert_details</span>.<span class="sp-syntax-property">__doc__</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"parameters"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"object"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"properties"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"pagerduty_alert_url"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"string"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"format"</span>: <span class="sp-syntax-string">"uri"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"description"</span>: <span class="sp-syntax-string">"The PagerDuty alert URL. For example, 'https://example.pagerduty.com/alerts/Q3YDP8VKEZ9THL'."</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"required"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-string">"pagerduty_alert_url"</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"additionalProperties"</span>: <span class="sp-syntax-static">False</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">tool_resources</span>=<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"file_search"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"vector_store_ids"</span>: <span class="sp-syntax-punctuation">[</span><span class="sp-syntax-plain">vector_store</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">]</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Create a thread for the conversation</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the assistant and the new thread to the user session so that</span>
|
||
<span class="sp-syntax-comment"># we can reference it in other handlers.</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">assistant</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">set</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">,</span> <span class="sp-syntax-plain">thread</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
@<span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-plain">on_message</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">handle_message</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">message</span>: <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">Message</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-keyword">None</span>:
|
||
<span class="sp-syntax-comment"># Retrieve our Assistant and Thread from our user session.</span>
|
||
<span class="sp-syntax-plain">assistant</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"assistant"</span><span class="sp-syntax-punctuation">)</span>
|
||
<span class="sp-syntax-plain">thread</span> = <span class="sp-syntax-plain">cl</span>.<span class="sp-syntax-property">user_session</span>.<span class="sp-syntax-property">get</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-string">"thread"</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Add the latest message to the thread.</span>
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">messages</span>.<span class="sp-syntax-property">create</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">role</span>=<span class="sp-syntax-string">"user"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">content</span>=<span class="sp-syntax-plain">message</span>.<span class="sp-syntax-property">content</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span>
|
||
|
||
<span class="sp-syntax-comment"># Stream a response to the Thread (called a "Run") using our Assistant.</span>
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">with</span> <span class="sp-syntax-plain">client</span>.<span class="sp-syntax-property">beta</span>.<span class="sp-syntax-property">threads</span>.<span class="sp-syntax-property">runs</span>.<span class="sp-syntax-property">stream</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-plain">assistant_id</span>=<span class="sp-syntax-plain">assistant</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-plain">thread_id</span>=<span class="sp-syntax-plain">thread</span>.<span class="sp-syntax-property">id</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-comment"># Use our custom message handler.</span>
|
||
<span class="sp-syntax-plain">event_handler</span>=<span class="sp-syntax-definition">MessageEventHandler</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">)</span> <span class="sp-syntax-keyword">as</span> <span class="sp-syntax-plain">stream</span>:
|
||
<span class="sp-syntax-keyword">await</span> <span class="sp-syntax-plain">stream</span>.<span class="sp-syntax-property">until_done</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span>
|
||
|
||
|
||
<span class="sp-syntax-keyword">async</span> <span class="sp-syntax-keyword">def</span> <span class="sp-syntax-definition">get_pagerduty_alert_details</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">pagerduty_alert_url</span>: <span class="sp-syntax-plain">str</span><span class="sp-syntax-punctuation">)</span> -> <span class="sp-syntax-plain">dict</span>:
|
||
<span class="sp-syntax-string">"""Return the details of the PagerDuty alert at the given URL."""</span>
|
||
<span class="sp-syntax-comment"># TODO: Make this implementation real!</span>
|
||
<span class="sp-syntax-keyword">return</span> <span class="sp-syntax-plain">json</span>.<span class="sp-syntax-property">dumps</span><span class="sp-syntax-punctuation">(</span>
|
||
<span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"alert"</span>: <span class="sp-syntax-punctuation">{</span>
|
||
<span class="sp-syntax-string">"id"</span>: <span class="sp-syntax-string">"PT4KHLK"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"type"</span>: <span class="sp-syntax-string">"alert"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"summary"</span>: <span class="sp-syntax-string">"A customer database is experiencing high CPU usage."</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"self"</span>: <span class="sp-syntax-plain">pagerduty_alert_url</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"html_url"</span>: <span class="sp-syntax-plain">pagerduty_alert_url</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"created_at"</span>: <span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">datetime</span>.<span class="sp-syntax-property">now</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span> - <span class="sp-syntax-definition">timedelta</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-plain">minutes</span>=<span class="sp-syntax-static">5</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">)</span>.<span class="sp-syntax-property">isoformat</span><span class="sp-syntax-punctuation">(</span><span class="sp-syntax-punctuation">)</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"status"</span>: <span class="sp-syntax-string">"resolved"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"alert_key"</span>: <span class="sp-syntax-string">"baf7cf21b1da41b4b0221008339ff357"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"suppressed"</span>: <span class="sp-syntax-static">False</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-string">"severity"</span>: <span class="sp-syntax-string">"critical"</span><span class="sp-syntax-punctuation">,</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">}</span>
|
||
<span class="sp-syntax-punctuation">)</span></code></pre></div></div></div></div></div></div><p class="framer-text framer-styles-preset-ivdbsg">Now you're all set to build a useful AI Agent for your SRE team! If you have any questions about anything we've covered in this guide, please reach out, and we'll be happy to help. In the meantime, if there is anything missing or any other AI Agent-related thing you'd like to learn, let us know!</p></div><div class="framer-1ntbddf"><div class="framer-1lyp7e4" data-framer-name="Previous" data-framer-component-type="RichTextContainer" style="transform:none"><p class="framer-text framer-styles-preset-ivdbsg" data-styles-preset="uDP1R0D66"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" data-styles-preset="XllKqo48X" href="./alert-management-how-to-reduce-noisy-alerts-and-fight-alert-fatigue">‹ The Ultimate Guide to Alert Management</a><!--/$--></p></div><div class="framer-gf8tl1" data-framer-name="Spacer"></div><div class="framer-r1lrfl" data-framer-name="Next" data-framer-component-type="RichTextContainer" style="transform:none"><p class="framer-text framer-styles-preset-ivdbsg" data-styles-preset="uDP1R0D66"><!--$--><a class="framer-text framer-styles-preset-wtpq7e" data-styles-preset="XllKqo48X" href="./how-to-build-a-bot-for-microsoft-teams">How to build a bot for Microsoft Teams ›</a><!--/$--></p></div></div></div></div></div><div id="overlay"></div><!--/$--></div><script>(()=>{function u(){function n(t,e,i){let r=document.createElement("a");r.href=t,r.target=i,r.rel=e,document.body.appendChild(r),r.click(),r.remove()}function o(t){if(this.dataset.hydrated){this.removeEventListener("click",o);return}t.preventDefault(),t.stopPropagation();let e=this.getAttribute("href");if(!e)return;if(/Mac|iPod|iPhone|iPad/u.test(navigator.userAgent)?t.metaKey:t.ctrlKey)return n(e,"","_blank");let r=this.getAttribute("rel")??"",c=this.getAttribute("target")??"";n(e,r,c)}function a(t){if(this.dataset.hydrated){this.removeEventListener("auxclick",o);return}t.preventDefault(),t.stopPropagation();let e=this.getAttribute("href");e&&n(e,"","_blank")}function s(t){if(this.dataset.hydrated){this.removeEventListener("keydown",s);return}if(t.key!=="Enter")return;t.preventDefault(),t.stopPropagation();let e=this.getAttribute("href");if(!e)return;let i=this.getAttribute("rel")??"",r=this.getAttribute("target")??"";n(e,i,r)}document.querySelectorAll("[data-nested-link]").forEach(t=>{t instanceof HTMLElement&&(t.addEventListener("click",o),t.addEventListener("auxclick",a),t.addEventListener("keydown",s))})}return u})()()</script><script>(()=>{function i(){for(let e of document.querySelectorAll("[data-framer-original-sizes]")){let t=e.getAttribute("data-framer-original-sizes");t===""?e.removeAttribute("sizes"):e.setAttribute("sizes",t),e.removeAttribute("data-framer-original-sizes")}}function a(){window.__framer_onRewriteBreakpoints=i}return a})()()</script>
|
||
<script>!function(){var w="framer_variant";function u(a,r){let n=r.indexOf("#"),t=n===-1?r:r.substring(0,n),i=n===-1?"":r.substring(n),e=t.indexOf("?"),h=e===-1?t:t.substring(0,e),d=e===-1?"":t.substring(e),s=new URLSearchParams(d),g=new URLSearchParams(a);for(let[o,l]of g)s.has(o)||o!==w&&s.append(o,l);let c=s.toString();return c===""?t+i:h+"?"+c+i}var p='div#main a[href^="#"],div#main a[href^="/"],div#main a[href^="."]',f="div#main a[data-framer-preserve-params]",m,S=(m=document.currentScript)==null?void 0:m.hasAttribute("data-preserve-internal-params");if(window.location.search&&!/bot|-google|google-|yandex|ia_archiver|crawl|spider/iu.test(navigator.userAgent)){let a=document.querySelectorAll(S?`${p},${f}`:f);for(let r of a){let n=u(window.location.search,r.href);r.setAttribute("href",n)}}
|
||
}()</script>
|
||
|
||
|
||
<script data-framer-appear-animation="no-preference"></script><script>(()=>{function d(e){let t=Date.prototype.toLocaleString,o=Date.prototype.toLocaleDateString;t&&(Date.prototype.toLocaleString=function(r,n){let i=s(this,r,n);return u(e.current.Date.toLocaleString,i,()=>t.call(this,r,n))}),o&&(Date.prototype.toLocaleDateString=function(r,n){let i=s(this,r,n);return u(e.current.Date.toLocaleDateString,i,()=>o.call(this,r,n))});let a=Object.getOwnPropertyDescriptors(Intl.DateTimeFormat.prototype).format.get,c=Intl.DateTimeFormat.prototype.formatRange,b=Intl.DateTimeFormat.prototype.formatToParts,D=Intl.DateTimeFormat.prototype.formatRangeToParts;a&&Object.defineProperty(Intl.DateTimeFormat.prototype,"format",{get(){function m(r){let n=p(this),i=s(r,n);return u(e.current.DateTimeFormat.format,i,()=>a.call(this)(r))}return m.bind(this)}}),c&&(Intl.DateTimeFormat.prototype.formatRange=function(r,n){let i=p(this),l=s(r,n,i);return u(e.current.DateTimeFormat.formatRange,l,()=>c.call(this,r,n))}),b&&(Intl.DateTimeFormat.prototype.formatToParts=function(r){let n=p(this),i=s(r,n);return u(e.current.DateTimeFormat.formatToParts,i,()=>b.call(this,r)).map(g)}),D&&(Intl.DateTimeFormat.prototype.formatRangeToParts=function(r,n){let i=p(this),l=s(r,n,i);return u(e.current.DateTimeFormat.formatRangeToParts,l,()=>D.call(this,r,n)).map(g)});let y=Number.prototype.toLocaleString;y&&(Number.prototype.toLocaleString=function(r,n){let i=s(this,r,n);return u(e.current.Number.toLocaleString,i,()=>y.call(this,r,n))});let h=Object.getOwnPropertyDescriptors(Intl.NumberFormat.prototype).format.get,F=Intl.NumberFormat.prototype.formatRange,T=Intl.NumberFormat.prototype.formatToParts,I=Intl.NumberFormat.prototype.formatRangeToParts;h&&Object.defineProperty(Intl.NumberFormat.prototype,"format",{get(){function m(r){let n=f(this),i=s(r,n);return u(e.current.NumberFormat.format,i,()=>h.call(this)(r))}return m.bind(this)}}),F&&(Intl.NumberFormat.prototype.formatRange=function(r,n){let i=f(this),l=s(r,n,i);return u(e.current.NumberFormat.formatRange,l,()=>F.call(this,r,n))}),T&&(Intl.NumberFormat.prototype.formatToParts=function(r){let n=f(this),i=s(r,n);return u(e.current.NumberFormat.formatToParts,i,()=>T.call(this,r)).map(g)}),I&&(Intl.NumberFormat.prototype.formatRangeToParts=function(r,n){let i=f(this),l=s(r,n,i);return u(e.current.NumberFormat.formatRangeToParts,l,()=>I.call(this,r,n)).map(g)})}function P(e,t){return typeof t=="bigint"?`${t}n`:t instanceof Date?t.getTime():t}function s(...e){let t=JSON.stringify(e,P),o=0;for(let a=0;a<t.length;a++)o+=t.charCodeAt(a),o+=o<<10,o^=o>>6;return o+=o<<3,o^=o>>11,o+=o<<15,o>>>0}function u(e,t,o){let a=e[t];if(typeof a<"u")return a;let c=o();return e[t]=c,c}function g(e){return{...e}}function p(e){let t=e.resolvedOptions(),o={locale:t.locale,calendar:t.calendar,numberingSystem:t.numberingSystem,timeZone:t.timeZone,hour12:t.hour12,weekday:t.weekday,era:t.era,year:t.year,month:t.month,day:t.day,hour:t.hour,minute:t.minute,second:t.second,timeZoneName:t.timeZoneName};for(let a in t)a in o||(o[a]=t[a]);return o}function f(e){let t=e.resolvedOptions(),o={locale:t.locale,numberingSystem:t.numberingSystem,style:t.style,currency:t.currency,currencyDisplay:t.currencyDisplay,currencySign:t.currencySign,unit:t.unit,unitDisplay:t.unitDisplay,minimumIntegerDigits:t.minimumIntegerDigits,minimumFractionDigits:t.minimumFractionDigits,maximumFractionDigits:t.maximumFractionDigits,minimumSignificantDigits:t.minimumSignificantDigits,maximumSignificantDigits:t.maximumSignificantDigits,useGrouping:t.useGrouping===!0?"auto":t.useGrouping,notation:t.notation,compactDisplay:t.compactDisplay,signDisplay:t.signDisplay,roundingIncrement:t.roundingIncrement??1,roundingMode:t.roundingMode??"halfExpand",roundingPriority:t.roundingPriority??"auto",trailingZeroDisplay:t.trailingZeroDisplay??"auto"};for(let a in t)a in o||(o[a]=t[a]);return o}return d})()({current:{"Date":{"toLocaleString":{"636152570":"May 23, 2025"},"toLocaleDateString":{}},"DateTimeFormat":{"format":{},"formatRange":{},"formatToParts":{},"formatRangeToParts":{}},"Number":{"toLocaleString":{}},"NumberFormat":{"format":{},"formatRange":{},"formatToParts":{},"formatRangeToParts":{}}}})</script>
|
||
<script>typeof document<"u"&&(window.process={...window.process,env:{...window.process?.env,NODE_ENV:"production"}});</script>
|
||
<link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/react.C5mz_aSL.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/rolldown-runtime.DynScBln.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/framer.CHpbOiPD.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/motion.QhTwL8Ov.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/rO9GQ2WdNVprL0dx7j7QfyXjj0fICu3tJkh_8c19xec.BSV9PEy1.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/shared-lib.DtvJj0Dz.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/sneE9th9z.BxXcyfWu.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/Yn_iLPXYZ.VNPURiVz.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/tQEIs45iS.GuDLWJZd.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/WJXO4kI7d.C-mG-_fh.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/pi9Zxi98b.BIwUXVqh.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/cWIWYByGH.B15bdNon.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/nOYGr05C8.ZqV6j3-o.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/t_S5mf8KG.hV7-xEUa.mjs"><link rel="modulepreload" fetchpriority="low" href="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/script_main.D--JjWtM.mjs"><script type="module" async data-framer-bundle="main" fetchpriority="low" src="https://framerusercontent.com/sites/1NRKQ4149Y51UA8BchLJTY/script_main.D--JjWtM.mjs"></script><div id="svg-templates" style="position: absolute; overflow: hidden; bottom: 0; left: 0; width: 0; height: 0; z-index: 0; contain: strict" aria-hidden="true">
|
||
</div>
|
||
<!-- Start of bodyEnd -->
|
||
<script>
|
||
!function(key) {
|
||
if (window.reb2b) return;
|
||
window.reb2b = {loaded: true};
|
||
var s = document.createElement("script");
|
||
s.async = true;
|
||
s.src = "https://b2bjsstore.s3.us-west-2.amazonaws.com/b/" + key + "/" + key + ".js.gz";
|
||
document.getElementsByTagName("script")[0].parentNode.insertBefore(s, document.getElementsByTagName("script")[0]);
|
||
}("Q1N5W0HZG3O5");
|
||
</script>
|
||
<!-- End of bodyEnd -->
|
||
</body>
|
||
</html>
|