David, a Welsh Microsoft Guy
Nôl i'r Blog
18 March 2026

Adeiladu Sgwrs AI NLWeb ar Azure Static Web Apps

azure
azure-static-web-apps
azure-functions
github-models
nlweb
next-js
typescript
ci-cd
github-actions
content-model
knowledge-graph
architecture-decisions
Adeiladu Sgwrs AI NLWeb ar Azure Static Web Apps

Rwyf newydd orffen ailadeiladu fy ngwefan bersonol - blog, podlediad, graff gwybodaeth, a nodwedd sgwrs AI gan ddefnyddio protocol NLWeb sy'n dod i'r amlwg gan Microsoft. Y syniad yw syml: gall ymwelwyr ofyn cwestiwn iaith naturiol a chael yn ôl ateb sgwrsiol wedi'i seilio ar y cynnwys mwyaf perthnasol ar y safle - yn hytrach na rhestr safonol o ganlyniadau chwilio.

Yr hyn a ddilynodd oedd un o'r sesiynau datrys problemau mwyaf addysgiadol rwyf wedi'i gael ers tro. Roedd y penderfynau pensaernïol yn ddiddorol. Roedd y model cynnwys yn gofyn am feddwl go iawn. Fe gymerodd lawer mwy o ddyfalbarhad na'r disgwyl i gael y sgwrs AI i weithio ar Azure Static Web Apps.

Dyma'r stori lawn - o'r penderfyniad pensaernïol cyntaf i'r eiliad y gweithiodd y cyfan - er mwyn i chi beidio â gorfod ailadrodd unrhyw ran ohono.

Dechrau gyda Phenderfyniadau, Nid Côd

Y peth cyntaf a wneuthum oedd ysgrifennu manylebau, nid côd. Cyn creu unrhyw ffeil, cefais atebion i gwestiynau a fyddai'n achosi ailwaith costus nes ymlaen.

Gwasanaeth: Azure Static Web Apps. Haen am ddim, CI/CD GitHub Actions wedi'i integreiddio, amgylcheddau rhagolwg PR, CDN byd-eang, rhedeg Functions rheoledig - a dyma lle rwy'n treulio fy nyddiau'n broffesiynol felly rwy'n ei adnabod yn dda. Yr unig ddewis arall go iawn y bu i mi ei ystyried oedd Vercel, ond mae SWA yn ennill ar gost a'r stori Azure-frodorol.

Fframwaith: Next.js 15 gyda output: 'export'. Mae cynhyrchu safle statig yn rhoi URLs go iawn i mi (/blog/fy-mhostiad), rheolaeth lawn dros fetatdata SEO, a dim gweinydd i'w reoli. Mae'r faner allforio yn cynhyrchu cyfeiriadur out/ plaen o HTML, CSS a JavaScript y mae SWA yn ei weini'n uniongyrchol.

Awduro cynnwys: MDX wedi'i seilio ar Git, dim panel gweinyddu. Mae pob postiad yn byw yn content/blog/<slug>/index.mdx a phob pennod yn content/tell-your-story/<slug>/index.mdx. Mae blaen-fater yn cario'r holl ddata strwythuredig; y corff yw Markdown. Mae panel gweinyddu wedi'i ohirio i v2 - cael y llif awduro wedi'i ddilysu yn gyntaf, yna'i awtomeiddio.

Sgwrs AI: chwilio allweddeiriau yn v1, GitHub Models yn v2. Yn fwriadol, anfonais y gydran NLInterface wedi'i gysylltu â mynegfa chwilio leol yn gyntaf. Roedd hyn yn golygu bod y rhyngwyneb yn gweithio ac wedi'i brofi cyn i mi gyffwrdd ag unrhyw seilwaith AI. Yna rhoddwyd yr haen AI y tu ôl i'r un rhyngwyneb /api/ask. Os bydd yr alwad AI yn methu, mae'r UI yn cwympo'n awtomatig i chwilio allweddeiriau - felly nid yw byth yn farw.

Fe wnaeth y penderfynau hyn, wedi'u hysgrifennu cyn unrhyw gôd, arbed ailwaith sylweddol i mi.

Y Model Cynnwys

Gyda MDX fel y fformat awduro, roeddwn i angen cynllun blaen-fater strwythuredig a fyddai'n gwasanaethu popeth: y gwasanaeth cynnwys, y graff gwybodaeth, chwilio, metatdata SEO, a chatalog anogaeth AI yn y pen draw.

Y meysydd sylfaenol y mae pob darn o gynnwys yn eu cario:

id: "blog-2026-03-fy-mhostiad" # Dynodiad unigryw sefydlog contentType: "blogPost" # Gwahaniadur title: "Teitl Fy Mhostiad" slug: "fy-mhostiad" # Diogel i URLs, anghyfnewidadwy status: "published" # draft | review | scheduled | published | archived publishDate: "2026-03-18" shortDescription: "Un neu ddwy frawddeg ar gyfer cardiau a thagiau meta." primaryTheme: "azure" # Yn gyrru lliwio graff themes: ["azure", "ai"] # Pob thema gan gynnwys y brif tags: ["azure", "functions"] # Rhydd, ar gyfer hidlo

Mae postiadau blog yn ymestyn hwn gyda articleFormat, technicalLevel, entitiesTechnologies, entitiesOrganisations, a migratedFrom (ar gyfer yr erthyglau LinkedIn a fewnforiais). Mae penodau podlediad yn ymestyn gyda guestName, guestTitle, guestBio, duration, a transcriptPath.

Mae'r model unffurf yn golygu y gall gwasanaeth cynnwys sengl drin y ddau fath - a gall y graff gwybodaeth dynnu ymylon ar eu traws heb achosion arbennig.

Pedair Sgript Adeiladu

Awduro cynnwys yw'r ffynhonnell wirionedd; yr arteffactiau a gynhyrchwyd yw'r allbwn y gellir ei ddefnyddio. Adeiladais bedair sgript TypeScript, wedi'u rhedeg trwy tsx:

validate-content.ts - yn rhedeg ar bob PR trwy lif gwaith GitHub Actions. Mae'n gwirio meysydd gofynnol, unigrywedd ID, fformat slug ([a-z0-9-]+), dilysrwydd dyddiad ISO, aelodaeth primaryTheme yn themes, croesgyda-gyfeiriadau relatedContentIds, a phresenoldeb guestName ar benodau. Eir allan gyda chôd 1 ar unrhyw wall. Dyma'r giatws sy'n cadw'r mynegfa cynnwys yn lân.

build-content-index.ts - yn darllen pob content/**/*.mdx, yn hidlo i eitemau gyda status: published neu status: scheduled gyda publishDate yn y gorffennol, yn tynnu blaen-fater o'r corff, ac yn ysgrifennu public/generated/content-index.json. Dyma'r prif ffeil data y mae popeth arall yn deillio ohono.

build-graph.ts - yn darllen y mynegfa cynnwys ac yn adeiladu graff gwybodaeth. Mae eitemau cynnwys yn dod yn nodau; mae themâu yn dod yn nodau; mae endidau (technolegau, sefydliadau, pobl) sy'n ymddangos mewn dau neu fwy o ddarnau cynnwys yn dod yn nodau. Mae ymylon yn cario math (theme, entity, related) a phwysau. Mae'r graff yn gyrru'r dudalen /explore.

build-search.ts - yn cynhyrchu public/generated/search-index.json ysgafn yn cynnwys dim ond y meysydd sydd eu hangen ar gyfer chwilio a rendro canlyniad (dim cynnwys corff). Dyma'r ffeil y mae swyddogaeth AI yn ei nôl.

npm run build:all-content # yn clymu'r pedair yn eu trefn

Mae'r ffeiliau a gynhyrchwyd yn gorffen yn public/generated/, y mae allforio statig Next.js yn ei gopïo air am air i out/generated/. Yna cânt eu gweini fel asedau statig plaen yn /generated/*.json.

Y Patrwm Gwasanaeth Cynnwys

Yn y modd allforio statig Next.js nid oes haen ddata gweinyddol - mae popeth naill ai'n statig neu'n cael ei nôl yn amser rhedeg o'r cleient. Ysgrifennais src/lib/contentService.ts fel modiwl gyda phatrwm nôl-a-chachea syml:

let contentIndexCache: ContentItem[] | null = null; async function fetchContentIndex(): Promise<ContentItem[]> { if (contentIndexCache) return contentIndexCache; const res = await fetch('/generated/content-index.json'); if (!res.ok) throw new Error(`Failed to fetch content index: ${res.status}`); contentIndexCache = await res.json(); return contentIndexCache; }

Mae swyddogaethau cyhoeddus fel getArticles(), getEpisodes(), a searchContent() i gyd yn galw trwy'r cache hwn. Mae'r cache yn gyflwr lefel modiwl - mae'n parhau am oes sesiwn y porwr ac fe'i gliriwyd rhwng ailgychwyn gweinyddol mewn datblygiad. Yn ymarferol mae hyn yn golygu bod y JSON yn cael ei nôl unwaith ar ddefnydd cyntaf ac yna'n cael ei weini o'r cof.

Ar gyfer cynhyrchu tudalen adeg adeiladu, mae'r gwasanaeth cynnwys hefyd yn rhedeg yn ystod next build (lle mae fetch yn cael ei bolyfillo gan Next.js), felly gellir rendro tudalennau'n statig gyda chynnwys go iawn.

Y Graff Gwybodaeth

Mae'r dudalen /explore yn rendro efelychiad grym D3 rhyngweithiol o'r graff cynnwys. Roeddwn yn arbennig o falch o sut y daeth y model graff o'r model cynnwys yn naturiol - unwaith y mae pob darn o gynnwys yn datgan ei themâu ac endidau, mae'r graff bron yn adeiladu ei hun.

Mae gan y graff dri math o nod:

  • Nodau cynnwys - un yr eitem gyhoeddedig, wedi'i fesur yn ôl cyfrif cysylltiad
  • Nodau thema - un yr llinyn thema unigryw ar draws pob cynnwys
  • Nodau endid - un yr dechnoleg/sefydliad/person sy'n ymddangos mewn 2+ o eitemau

A thri math o ymyl gyda phwysau gwahanol:

  • theme (cynnwys → thema, pwysau 0.9)
  • entity (cynnwys → endid, pwysau 0.6)
  • related (cynnwys → cynnwys, pwysau 0.7, dwyochrog a didyblyg)

Un penderfyniad bwriadol: dim ymylon tagu yn v1. Cymhorthion hidlo arddangos yn unig yw tagiau. Byddai eu cynnwys yn y graff yn creu gormod o sŵn - bron bob postiad sydd â thagiau azure a microsoft, a fyddai'n cynhyrchu pelen drwchus wedi'i chysylltu'n ddwys yn hytrach na thopografi ystyrlon.

Mae'r dudalen graff yn cynnwys toglo golwg graff/rhestr, chwilio, hidlwr math cynnwys, zoom/sbandrel, awgrymiadau hofran, a chlicio-i-lywio ar gyfer nodau cynnwys. Pob un wedi'i facio gan yr un asedyn statig graph.json.

Y Sgwrs AI: v1 Allweddeiriau, v2 AI

Adeiladwyd y gydran NLInterface mewn dau gam yn fwriadol.

Cysylltodd v1 y rhyngwyneb sgwrs â searchContent() o'r gwasanaeth cynnwys - paru allweddeiriau pur ar draws teitl, disgrifiad, dyfyniad, tagiau a themâu. Dim galwadau API, dim tocynnau am gost, yn gweithio all-lein. Adeiladwyd y rhyngwyneb - haenlen gwaelod-dde arnofiol, hanes neges sgwrs, cardiau canlyniad, sglodion cais arfaethedig, hygyrchedd bysellfwrdd - i gyd a'i brofi yn erbyn hwn.

Bu v2 yn disodli'r ffynhonnell ddata gyda galwad i GET /api/ask?query=.... Mae'r gydran yn rhoi cynnig ar yr API yn gyntaf; os yw'n cael unrhyw beth heblaw 200 mae'n cwympo'n ôl i searchContent() yn ddistaw. Mae'r defnyddiwr bob amser yn cael canlyniad.

Roedd yr agwedd raddol hon yn golygu bod gennyf ryngwyneb gweithredol y gellid ei brofi cyn ysgrifennu un llinell o gôd Azure Functions - ac roedd yr ymddygiad wrth gefn yn ganlyniad naturiol o'r dyluniad dau-gam, nid yn ôl-sylw.

Swyddogaeth API NLWeb

Mae'r swyddogaeth yn byw yn api/src/functions/ask.ts - sbardun HTTP TypeScript Azure Functions v4:

app.http('ask', { methods: ['GET', 'POST'], authLevel: 'anonymous', route: 'ask', handler: async (request, context) => { ... } });

Y trinaethwr:

  1. Yn darllen ?query= o'r cais
  2. Yn llwytho search-index.json trwy nôl SITE_URL + /generated/search-index.json
  3. Yn adeiladu anogaeth system yn cynnwys y catalog cynnwys llawn (teitl, themâu, tagiau, disgrifiad fesul eitem)
  4. Yn galw GitHub Models gpt-4o-mini trwy'r pwynt terfyn sy'n cydweddu ag OpenAI
  5. Yn dosrannu'r ymateb JSON ({ message, relevant: [3, 7, 1] })
  6. Yn mapio'r mynegeion 1-seiliedig yn ôl i wrthgychau canlyniad llawn
  7. Yn dychwelyd ymateb wedi'i lunio fel NLWeb

Mae'r paramedr ymholiad prev yn cario rhestr wedi'i gwahanu gan gomaos o ymholiadau blaenorol ar gyfer cyd-destun sgwrsiol - mae'r swyddogaeth yn eu chwistrellu fel troion blaenorol ffug er mwyn i'r model allu dad-gyd-destunoli cwestiynau dilynol.

Sefydlu CI/CD

Mae lif gwaith GitHub Action SWA yn cael ei greu'n awtomatig gan Azure pan fyddwch chi'n creu'r adnodd. Roedd angen i mi ei addasu ar gyfer y bibell gynnwys. Y ffurfweddiad terfynol sy'n gweithio:

- name: Deploy to Azure Static Web Apps uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PROUD_PEBBLE_0F32C4110 }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: 'upload' app_location: '/' api_location: 'api' app_build_command: 'npm run build:all-content && npm run build' api_build_command: 'npm install && npm run build' output_location: 'out'

Cymerodd lawer o ailadroddiadau i gyrraedd yma. Dyma bob un o'r trwsiadau.

Trwsiad 1: Rhaid i output_location gyfateb i gyfeiriadur allforio eich fframwaith

Mae Next.js 15 gyda output: 'export' yn ysgrifennu i out/ yn ddiofyn. Roedd gan y lif gwaith a grëwyd gan y porth output_location: "build" - sef rhagosodiad CRA React. Newidiais ef i "out" a dechreuodd y defnyddiad ddod o hyd i'r safle wedi'i adeiladu.

Trwsiad 2: Peidiwch â defnyddio skip_app_build: true - defnyddiwch app_build_command

Fy ngreddf gyntaf i gyflymu pethau oedd rhag-adeiladu mewn cam lif gwaith a phasio skip_app_build: true i gynllun SWA. Y broblem: pan gaiff Oryx (system adeiladu SWA) ei hepgor nid yw'n gwybod ble i ddod o hyd i allbwn yr ap, gan gynhyrchu gwall "Failed to find default file".

Yn lle hynny, cadwch Oryx yn cymryd rhan a dywedwch wrtho beth i'w redeg trwy app_build_command. Mae Oryx yn trin canfod amser rhedeg; mae eich gorchymyn arferol yn rhedeg y tu mewn i'r cyd-destun hwnnw.

Trwsiad 3: Mae'r un peth yn berthnasol i skip_api_build

Mae skip_api_build: true yn cynhyrchu "Function language info isn't provided" - mae Oryx angen archwilio'r ffolder api/ i ganfod yr amser rhedeg Node.js. Defnyddiwch api_build_command i addasu heb hepgor.

Trwsiad 4: Mae Azure Functions v4 angen pwynt mynediad main yn package.json

Tynodd Azure Functions v4 ffeiliau function.json fesul swyddogaeth o blaid cofrestru wedi'i seilio ar gôd. Ond mae angen i chi hefyd ddweud wrth yr amser rhedeg ble mae'r allbwn sydd wedi'i lunio:

{ "main": "dist/functions/*.js" }

Heb hyn, mae'r defnyddiad yn llwyddo ond mae pob llwybr /api/* yn dychwelyd 404.

Trwsiad 5: Angorwch reol dist/ .gitignore i wreiddyn yr ystorfa

Roedd gennyf dist/ yn y .gitignore gwraidd. Heb flaen-slaes, mae hwn yn cyfateb i dist/ unrhyw le yn yr ystorfa - a oedd yn eithrio api/dist/ yn ddistaw o git. Nid oedd allbwn y swyddogaeth wedi'i lunio byth wedi'i ymrwymo na'i ddefnyddio.

Y trwsiad: defnyddiwch /dist/ (gyda'r flaen-slaes) i angor y rheol i wreiddyn yr ystorfa yn unig.

Trwsiad 6: Gwiriwch bob amser eich enw gwesteiwr SWA gwirioneddol gyda'r CLI

Pan redais az staticwebapp show darganfyddais fod yr enw gwesteiwr yn proud-pebble-0f32c4110.1.azurestaticapps.net - nid proud-pebble-0f32c4110.azurestaticapps.net fel roeddwn i wedi tybio. Mae'r segment is-barth .1. yn dynodwr slot rhanbarthol nad yw'r porth Azure yn ei wneud yn amlwg. Roedd pob prawf a rhedais yn taro gwesteiwr nad oedd yn bodoli.

az staticwebapp show --name <app> --resource-group <rg> --query "defaultHostname" -o tsv

Gwnewch hyn bob amser cyn datrys problemau ymatebion API.

Trwsiad 7: Ymrwymwch eich ffeiliau a gynhyrchwyd

Mae'r swyddogaeth yn nôl SITE_URL + /generated/search-index.json. Roedd gennyf public/generated/*.json yn .gitignore - penderfyniad pensaernïol cynnar y dylid cynhyrchu'r rhain yn amser rhedeg, nid eu hymrwymo. Ond gyda model gwasanaeth SWA rheoledig, gweini ffeiliau statig yw'r hyn sy'n bwysig: os nad yw'r ffeil yn out/ nid yw'n cael ei gweini.

Sylwais ar y rheol gitignore a ymrwymo'r ffeiliau a gynhyrchwyd. Mae CI yn eu hadfywio ar bob defnyddiad trwy app_build_command i'w cadw'n gyfredol. Diflannodd y gwall "Failed to load content index".

Trwsiad 8: Tynnwch response_format: { type: 'json_object' } ar gyfer GitHub Models

Mae response_format: { type: 'json_object' } SDK Node.js OpenAI yn gweithio ar Azure OpenAI ac API OpenAI. Nid yw ddim yn gweithio ar bwynt terfyn GitHub Models - mae'n taflu 400 ac mae'r cwblhad cyfan yn methu.

Gorfodwch JSON trwy eich anogaeth system yn unig, a ychwanegwch god amddiffynnol i dynnu ffensys côd:

const cleaned = raw .replace(/^```(?:json)?\s*/i, '') .replace(/\s*```$/, '') .trim(); parsed = JSON.parse(cleaned);

Trwsiad 9: Mae PAT manylion GitHub angen caniatâd cyfrifon Models

Mae PAT heb y sgôp Models yn cael 401 - The 'models' permission is required to access this endpoint. Mae'n Ganiatâd Cyfrifon, nid caniatâd ystorfa, felly mae'n hawdd ei golli.

Wrth greu eich tocyn manwl: Gosodiadau → Gosodiadau datblygwr → Tocynnau manwl → Caniatâd Cyfrifon → Models → Darllenwch yn unig.

Y Llif o Ben i Waelod

Ar ôl yr holl honno, mae'r llif cyfan yn gweithio:

  1. Mae ymwelydd yn teipio cwestiwn yn y rhyngwyneb sgwrs
  2. Mae NLInterface.tsx yn galw GET /api/ask?query=...
  3. Mae'r Swyddogaeth Azure yn nôl ac yn cachea search-index.json
  4. Mae'n adeiladu anogaeth system yn cynnwys y catalog cynnwys llawn
  5. Mae GitHub Models (gpt-4o-mini) yn dychwelyd { message, relevant: [3, 7, 1] }
  6. Mae'r swyddogaeth yn mapio mynegeion yn ôl i wrthgychau canlyniad llawn
  7. Dychwelir yr ymateb wedi'i lunio fel NLWeb a'i rendro fel neges sgwrs a cardiau cynnwys
  8. Os bydd yr alwad API yn methu ar unrhyw bwynt, mae'r UI yn cwympo'n ôl i chwilio allweddeiriau

Mae'r cyfan - model cynnwys, bibell, graff gwybodaeth, sgwrs AI, CI/CD - wedi'i dagio v1.0.0-mvp yn yr ystorfa.

Crynodeb o'r Holl Wersi

#MaesGwers
1PensaernïaethClowch benderfyniadau ar westeiaeth, fframwaith a model awduro cyn ysgrifennu côd
2PensaernïaethAnfonwch v1 gweithredol (chwilio allweddeiriau) cyn gwifradu AI - mae'n rhoi wrth gefn wedi'i brofi
3Model cynnwysMae cynllun blaen-fater unffurf ar draws pob math cynnwys yn talu ar ei ganfed yn y graff ac anogaeth AI
4Bibell gynnwysMae sgript validate:content fel giatws PR yn cadw'r mynegfa cynnwys yn lân
5CI/CDCyfatebwch output_location i gyfeiriadur allbwn gwirioneddol eich fframwaith (out/ nid build/)
6CI/CDPeidiwch â defnyddio skip_app_build - defnyddiwch app_build_command yn lle
7CI/CDPeidiwch â defnyddio skip_api_build - defnyddiwch api_build_command yn lle
8Azure FunctionsMae v4 angen "main": "dist/functions/*.js" yn api/package.json
9GitDefnyddiwch /dist/ (wedi'i angor) nid dist/ yn .gitignore gwraidd
10AzureGwiriwch bob amser enw gwesteiwr SWA gyda az staticwebapp show - efallai y bydd .1. ynddo
11DefnyddioYmrwymwch ffeiliau a gynhyrchwyd, neu sicrhewch fod app_build_command yn eu cynhyrchu
12GitHub ModelsTynnwch response_format: json_object - nid yw'n cael ei gefnogi ar y pwynt terfyn hwn
13GitHub ModelsMae angen y sgôp caniatâd Models (Darllenwch yn unig) ar Docyn Mynediad Manwl (PAT) - yng Nghaniatâd Cyfrifon, nid ystorfa

Os ydych chi'n adeiladu rhywbeth tebyg - safle personol gyda chwilio AI wedi'i seilio ar Azure Static Web Apps - gobeithio y bydd hyn yn arbed ychydig o oriau i chi. Roedd y rhan fwyaf o'r rhain yn amlwg mewn ôl-olwg. Nid oedd yr un ohonynt wedi'i ddogfennu yn unrhyw le y cefais hyd iddynt ar y pryd.

Parhau i archwilio

Archwilio graff y pynciau