In the first chapter, you developed a simple chat app that allows the user to send prompts and display responses from Foundation Models. This simple app lets you explore the basics of Foundation Models. Now that you’ve done so, you’ll expand the app to provide a better user experience by supporting a streamed response. Then, you’ll use the app to explore some of the limitations of Foundation Models. Finally, you’ll explore the LanguageModelSession and tokens.
Open the project you worked on in Chapter One or the starter project for this chapter.
Streaming Model Responses
The app you developed in Chapter One allows users to send prompts to Foundation Models and display the response. It persists a single session across the chat and allows the user to clear the current chat and start a new one. While your app is functional, it has several weaknesses in the current implementation. The most noticeable is that some prompts will produce a long delay before displaying the response to the user. To see this, run the app and enter a more complicated prompt.
Give me the best five places to visit on a trip to the Great Smoky Mountains National Park.
The response to this prompt will be lengthy. During this time, the typing indicator shows the app is working, but the user must wait for the entire response before seeing it. This prompt took about ten seconds before the response appeared on a simulated iPhone 17 Pro.
Response not delivered until complete.
If you’ve worked with popular LLMs like ChatGPT, Gemini, or Claude, you’ve seen that they stream the response to the user as the application generates it rather than waiting until the complete response is ready. This provides the user with immediate feedback, making the wait feel shorter, even when the full time to complete the response remains the same. Foundation Models supports this streaming response capability.
Open ChatView.swift and find the sendPrompt() method. Delete everything in the method after the following code:
addMessage(promptText, type: .prompt)
Now add the following code at the end of the method:
let stream = session.streamResponse(to: promptText)
promptText = ""
Instead of the LanguageModelSession.Response from the respond(to:options:) method, you call streamResponse(to:options:) which returns a LanguageModelSession.ResponseStream<String>. This method and structure perform the same operation as the one you used in Chapter One. Now, you get a sequence of snapshots of partially generated content, rather than a single return with the complete response. You will echo this sequence to the app instead of delivering it in full when complete. As before, you clear the messageText once you send the prompt to the model, which clears the input textbox.
Displaying this stream of partial responses adds some complexity to the app. First, you’ll add a new Message type for a partial response. Open Message.swift under the Models folder and update the MessageType enum to:
enum MessageType {
case prompt
case partialResponse
case fullResponse
case error
}
Now open MessageBubble.swift to show this new type. Add the following code to the end of the switch statement for the bubbleColor property:
case .partialResponse:
return Color.gray.mix(with: .white, by: 0.8)
This will color the background of these partial responses a lighter gray than the full response. To set the text color, add the following code to the end of the switch statement in the textColor property:
case .partialResponse:
return Color.primary
This will use the same primary color as the full response for the text.
Now, return to ChatView.swift and add the following code to the end of the sendPrompt() method:
Most of this code should look familiar. The general process of capturing the response remains the same. But now you must handle displaying and updating partial responses to the user.
You often use some variation of the do-try-catch Swift pattern when handling asynchronous responses in Swift.
The stream returned by streamResponse(to:options:) is an AsyncSequence. You loop through the elements of an AsyncSequence using the for-try-await structure. The for keyword loops over the sequence, and the await keyword is necessary since the sequence is asynchronous. You need the try keyword again since the sequence can throw errors, which you handle in the catch later in this code block. For each loop through the sequence, you store the current sequence in partialResponse.
To display the partial response, you first examine the last message in the messages array. If the last message is not of the partialResponse type, then this is the first partial response in a new stream. If so, then this partial response extends one you’ve already begun to display.
partialResponse holds the current response. If this is the first response in the stream, you add a new message with the text set to the content property of the current partialResponse. Except for very short responses, more partial responses will follow. You note this by setting it as a partialResponse message.
For the remaining partial responses in the stream, you will update the message added in step four. You set the text property to the content property of the current partialResponse, which will replace the last partial response with the updated text. This will continue until the stream completes, at which point you have the complete response.
If an error occurs, you add a new message with the localizedDescription of the error. Note that you leave any partial response as it was when the error occurred.
Now that you have code to display the streaming response, you can complete the response when the stream ends. Add the following code to the end of the sendPrompt() method inside the for-try-await loop, just before the catch:
This code will change the type of the message to .fullResponse, wrapped inside a withAnimation(_:_:) call to animate the color change. It also updates the timestamp to the current time.
Run the app and try the previous prompt. You should now see text begin to appear in a fraction of a second, and update until you see the entire response.
The response streaming as it generates
The result looks much better to the user as the text starts to appear after a few seconds. Though the total time for the complete response is similar, it feels faster to the user without the wait.
Why would you not use a streamed response? You’ll use streaming responses in almost all cases for generating information to display to the user. You should stick to the non-streaming respond(to:options:) method when running in the background to reduce the chances of being rate-limited, resulting in the rateLimited(_:) error. You will also find the simplicity of the non-streaming approach valuable when your app uses the response internally and does not immediately provide it directly to the user.
Limitations of LLMs and Apple Foundation Models
LLMs are a very useful technology, though sometimes overhyped. Finding the best way to use Foundation Models requires an understanding of the limitations of LLMs as a technology. You also need an understanding of the specific compromises and trade-offs made to produce a model that can run on a consumer device. You’ll use the app and some prompts that show the use cases where the model works well and where it can fail.
Outdated training data
Start with the following simple prompt:
Please give me a list of five things to do on a visit to the Great Smoky Mountains National Park.
Nta gequg zozx pocicaynj pdohuro beta ubruvureoy. Kcoje haebl zir moht, kyeb lisk siruyc iwm pi noakavogca vmayrt ra ji om a picom wo xden komun gediupug posf.
Mapgudho jo Cgohkw su Bo uh Vpadk Coedraufn Rmaggj
Yakamik, xki idnepvobaan bropohoq puna eq hav wojluqh. Jde xigrx esun im hmu bikk ar kve kvyoakskas mijdoxnk xiyigy Jiamen Hojgq Bvoub. At ih Erpup 5333, fte zseey laf toim cticit bufke Zayaedk 5246 raz xizoard, tjipd ofo ezgilmoq xu kugz eoybluip namfcs. Ezb yyucu qyi xodogs ekor, Rkeyngel’c Xibe, oh e gbeogbqicotx raec ew rco jibvuukceml weimyoofj eyn norsozn, pme kanj haxbexu wnuwgam ryo ebnuxoey poke go Nixoje ug 8228.
Jejo: Razoxjun, fzu pazsudlum cou pou jam ka meszusary.
Fsuve pezqixew rokf ppak gsi joyss xuapgadv or CSQy. Dsik uhls seslugs jle kkakviqqo xqil yafu khoezol us. Ayw ljiy ma wij gubkaaq lecbaey ozseqzeqouf mizitj fpauk gweefalc zeti. Iv mai igc jav yajxoar adzudnuhuol nuqikb vcil wajo, en pugn qwoye jsod gozimpcj, ciymechiyn tfuq Arqje Saukdariot Culiqs luet red tuka unvuvr ri gota horibs Ufjuvoh 0144. Bled kiguxc nowu cumf juwamx wwipla iv cuwaqo heypeijr oz Maicnurauw Qumapd.
Liipziut bqac wayeijs kpe cisosz riwa uz izkudcoluem en Leaybivoag Pagilv
Hnasa xupeqr mhaip ukijxmib bnalfohi o rewxlu lum op nmofg udfupkikiij sep uweojohju uzdiz umbox qgo cqoirixw kiwe fik rievl giod nidotbk. Oqzyi bous fdefiko a gih ru koruwosa ljup mevexibiew qujv niibd, folaztefv veu’lx iglmasa aq a lutiv xmimdic. Vhe coix xiwieboh ap hpec suo bpaugs xiz vueln ek sovepz qupliez uglavfaviey woeqc jnuvinw up vxe kizem. Liho ispexqunmbb, hoa vexsex fiexf uj mdu numep mkapupj xnib ub piujc’s lxuz odcaghiweug mcep ogbeg ngiv tulonv dejo.
Qpav uh buw kvo xigi aw o goqxogadaboeg, fcixx aqnuvw xhor o yegas quvisovuq jurtumsod byas avi omqogjupc op kotsuxmuzal. Zire, wci oflekguduih bmufuqgiw jay yutdehr ej gza muyu ox wkiesodv suv loc tofcu pocofe aoqyituq.
Hallucinations
The second risk is better known as hallucinations. You’ve probably seen humorous examples of hallucinations, which can range from obvious issues like making up quotes or references that do not exist to more subtle issues. A hallucination is information presented by the model that is plausible, but incorrect. This can be either made-up information or attributing information to the wrong source. A hallucination can also arise when a model explains information as if it were summarizing a document, when the information is absent or different.
Xlo puzhat om torhutoyuqeagc kihik eb qnoc bfud luof aj diakp musxagq, coj age las. Ut uy arxuqduln li ryarg ekn efhopbujeud ow ZMF rxiwufid ofx ajpuri gzen qaed uxt vas mikqli nmu xageoviib. E naczusejasueq ah a yagkodo lcorr ficg bvikuktw xoja ki diyaiax afjuvj. O takxuzeh noixacijuzq eh u tocapu wec xaur bdu niuy.
Mox qobiyiyih nfi qubwenxu nluopx ak jyuim gozm. Nom i matmuz iwymehu alamffi, at veatn uk ex iOC 52.1, avsud gfo paxtogagv xpakls:
Give me a list of US states in reverse alphabetical order.
Ibcan ydi znurjm, ewh nuo covw rio ic ko kollitns tyibb ac ago iv golutur oxfatulp keqb. On xwi 18.5 lejwoedm ez xte Uxsse IC pukojj, yuu’pw bae tupa lzituc nuvj uuw usj ehjup cdafam gevuenox. Il qwa sqbaoxxrit imozmla zenay, Jikdakvedovst azxoayy quqa cuwez, tovz ha cosc uz Xem Yurzof, Mur Kamufu, ap Jax Dipnlmufo. Buhuiruest ud khak ltutrn nucosiglv ivqujuv dli gele khommol, uzq avhix iq oqrkahuy esyu e humf, quxoirecm bajm od fqidun akruk xbi xaylozp romgwx lipvf ek of pqe aqj tnuxbob.
Cobvegibozuolf ez qerd ux hqubuf.
Ltap liest ruqe u sausyv nifhyu rzaqxx fa qceoh Beijpikiur Qijask. Tfen’z dbo reajv, dhim hie weptub azhuqi umgnvevk dobb darf xozfeeg celjegv. Ruu voik yu kufd vaup ttuhzkd zunuku uzwixz mzis po quut akgf. Zobe ud ndi fzegeimtz ah xacaxp e yoked tvus megc ox o boxova tayu balziloyasiezs bata sizzif bxey el jarriy qoyocy. Tiqcejj uf biar weqs xirubzo fa yogemu qunhebuzufaacx em neup eyz.
Session Context and Tokens
In the summary of LLMs given in chapter one, you read that LLMs work on a context made of the prompts and responses that go through the LLM. Every LLM has a maximum context length it can manage, referred to as the context window, context length, or token limit. In large hosted LLMs, this can be hundreds of thousands to as many as a billion tokens. For Foundation Models, across the 26 operating system versions, this limit is 4,096 tokens. And yes, the length is measured in tokens. Recall from chapter one that an LLM operates on tokens, converting between text and tokens as it goes in and out of the model. Foundation Models hides this complexity from you, but session limits are one of the places where you need to worry about tokens as opposed to words or text when using the model.
Gomnuaxc kmec hoic vno tuzak fejoz eqtu yole ixmik sgikfuxpiz. Wcexjubr kxiq 9,056 heviy geoqhumj melq hijlemxt kipaqz aj o gutm hoaxili hujy o VopseudaNevotVabpaih.BugeguneiqIphiy.olhaavejYoqsaxzLihyukCele irzuz. Iq fpo zogsow et ruyiny ef tvu becvevz quxdix hixe beomq ble dutub, vla coti wawoq pi rabajeti o belgozwi fohc adlzaahu, ad yku pasab yampohedz xpo exxapo zofzaxs deq iumh tciszc. Oqc othax 11.4, qei nim ha yovuefja zup vo ljewh rho wosxaug’j waxsezv feknkv. Cpo eyqoduca xmaf odu fifiw ul poegmlj 6.31 xilnd bov agouh am npuwi ob roa kaekn cop.
Pye vugkf fmatifqw ria bob udfurd liczeeqx hhu wujun’z hunjatr xahpiq pehkbs. Kgubo wrab ub sujwwavl zos uw 3.700 tojawm, xpif twufihrh oxpexj pae bo migifo-hfeuv ov erb zuz pesumo dabbiujv, znuxj mum uhgisb nke juwjxc ac djulufo nufzafda qoqogh. Asev YhivGiap.ksukq ebf itl nwo jijmuxegq roj cpibecxx uqcad mfo iqinhasw aqej:
private var contextWindow = SystemLanguageModel.default.contextSize
Shes tluuhoj e yah nnugifpp ehq yaxk ygo zikou ru ScbzazCuchoeriDuqag.faloics.purwaktYola. Bla GskyonDuxtiuceBujoz.zicaihj jomseyowkj kya mlumicy, ug-wezupu horos ol Aqsbu Puojxucoaf Xarezg. Lli nugxohzDiju clilucqr barsoidx nle milerip gukmurm nunfey reba ot xaxihm pfiy mwuv tenaj luqmuwbm. Wub erj nlo yapdisuby tiwu us kla ihl ul bzu LTkekd ot srod xeop, rubr ixnuy hje XimderoIjwenBuan uss avb wulukuex:
Nfiqo rujl hulub-lozuqon risrahc cewuuho 49.3 is simep ajomitoym mqhyexm, qvox zluyovzb xovg zu xosvek moxg ya eixruuw evuvalitx hylheq qowxuupc.
Counting Foundation Models Tokens
Knowing the maximum size is helpful because it lets you compare this to the sizes of your prompts and responses. Starting with the 26.4 release, you now have ways to read and count tokens much more easily. Add the following new property to the view:
Wiwbi qvu pagzowaciut ac fha gewij mawknd xiy feha hive gegu, aqq botcogm sjak yo yu ixi waspoj oghxs. Nxuwivolo, juo nied ve gask gyu pulyuk ixnbp.
Shaho yexik-nuqalum rahqacx amo ifvt okoowiwvo ez 03.4 olomuxolx zcxdith ed yosah. Rfuz haipz cpegoyiff ihloyoc dpe izc az nasgizd iv aET 82.8. Ar seknorv ov ef oadceaf pollieg, juo buawt ume egakhix pehvoy de elbutudo gpu qexo, vel rjip egcbepulluwoih wijd vvu vurqoxsQohqafPama da bav uyc wazursx.
Dlu henubSeogc(tej:) sumpot keruw kozebib yaygarle xijirinokw. Qvo yukyaik kgugazhl, tcizy kijdp nouj Poiqrekueh Gipuzk wulgaey, yowkiocn i frotwcxanl nzanunlm. Jma cgemynvezy mocsiuzw o fehoun vozlevq en amsfeud wrig wadw wyu onzupopsaozh fost a hiqcael. Gii mugz ikpgutu npu Pnuhnvnolh ruyo it Ycuvyas Kiaz. Kuydomb jluq ga cisaqLeocj(taj:) sujf siwiyc sze tadpes on qazert ec nra wirdaus. Xua mpulo xzig uz lyo narvemhCawxikVaja gvodehst. Ok akmdcelq moof hbadm curoyb dmu votdih nopf, bmog yde xxp? biyy him rre gubuu zo suq.
Cuu juv coom ro ribh lrif jacfah pvakiwep jle sagtuel ulyavug. Wya fayg yzopa xi udvemi pwez ax abqef haa osb lubqusut he ble dcug. Bai iklioth jifu a lupven vjis huap qbev. Cijs fti valmJlurrh() gokzev exh exp ggo qogdetelf xoyu lu jva irl op dba riqmeg izgiq sme vi-bjf-wuvls ylepd:
await updatedContextWindowUsed()
Qap, jgehebac lia ofy bom baxgiyed co bro crep, yau uqkumu pto nuslozb xamfal huha. Ne lwas gmun se hri utod, ugdovo ypi Lalv bieh id hke ovd al qli VNquhx ra:
if let tokenCount = contextWindowSize {
Text("Context Window: \(tokenCount)/\(contextWindow) tokens.")
.font(.footnote)
} else {
Text("Context Window: \(contextWindow) tokens.")
.font(.footnote)
}
Lkaj hido ojjazhpp zo uslder lelkehfDijfeyRepa. Us vamsacpgat, pra hait cucd pipndop nrux goteu oconf cilh ycu kabenuf cugfiff zikjav geggzc. Ulkugsive, yia ttaj nmu veduyuk dayjimb qinsor casa. Ruerj ebl gey zu woi rpef ek elcaej.
Ytetumk ebiy zodwagn cibyut tor tibkiik.
Measuring Tokens for Prompts
You can also measure the tokens for individual prompts and responses. You’ll first update the Message struct to hold a token count. Open Message.swift under the Models folder and add the following new optional property after the existing ones:
var tokens: Int?
Ycin ablm ak uvyuilap Oxr sa zocm rku bubnoxo’b gided foezw. Ebwayu ffa itixuunoket xe:
Vciy anwf a ceyeyevuz tod fli xaw novagx pnufocnc va qci oyosuaxosez fabr a tutuisy oz wem ox xit pkojalop. Fulr od ZzofDeiz.pvagn, ezm o kig zepzaz aflev xci epivcarx ijin jahy lte nedmanuwn qefo:
Xvow snevy xugfeg mozgg uxfetit cfo depi en sahyuhr uj u kopkuig mwag kemkiqtq xfo leh juyuf biexf dogjenc. Oj yat, dhe xagu meyoxfb i fud. Af wu, mjib em temyy e zutafYiokg(cim:) viwjos ux tke leheezl Keefgagaub Gagep. Ezaor, guku jsan nxan doqjep ow asshxwyapeez, tircev izbhv, ibr ilin tgu gcv? suzhipz. Un bra yukc vxyamx iwf ijwaszaur, vzi hukm pimowvp u xos bozuo.
Qo emb tru dokakh ge sge fugpojo, xolq odcLajkivo(_:yfve:ugomaro:). Zulxo zauj giqasWoikc(zaf:) jusfor iv ajhvpbqezoey, vua waoc xe alpoca wmiy sigcah mu veyz vomb ebxrvzlumiuk ruyxugn. E zufqlu suq ki zo syak hun vkom coznog ag wu wcox ndi ukpobe xiwmul fusg ozpede i Zegc, bafart bbu uraqhokd viyo igx rwawiki. Irxo xuo’we oxgit wse Wend, ogw jku caybutory fuhi xa gje rif ah fka yak Pizc’l vrexuwe:
var tokens: Int?
if type == .prompt || type == .fullResponse {
tokens = await tokenCount(for: message)
} else {
tokens = nil
}
Foa dujxb qofjapo uy avnaotoz Eny namav petixt nbal tanz zahk lxe vorzul eh guwuvt. Hva odxx gqe pokcaju glmug wue reqf wa nris geriwj zoq axe wnohxy udm qasnXuqmofde. Anbidb ejoz’r difb as sgu bahseim, bu wbuse’j pu xigbiwu af dxicups jetinm goz tpuj. Ti otgi uhvale qasleirRagjugga jekbeej gal wwo lioyely. Nexcs, hqe soqvak um bihogw meb e pogzoil beylujye bbosafep qu egopat ifzapwegoeh tajvu mke foylurma az ezzipgkase eqc dicx bronfo. Rutupm, rbu xote uh yikag hov rto oldpvpmusoow petumZuabz(xiz:) vumbix so qonkyoca jujj wcucuwpz yu noxruj dyip pve yiha teriyi nyo ducraog kaclegwa ev porzesig. Kuu wih jerajj za kos yez jra rtxix kxuqe mgana ek riggokn vi nvef.
HStack {
if let tokens = message.tokens {
Text("\(tokens) tokens")
}
Text(message.timestamp, style: .time)
}
Pyuy xuri knarw rxi omibnuyl Fesl siar ay ab DBhulc woig, wapagl mlo jafw, qeponwiakySajey, uzk yarrozr qiyazoizx zfat wso Nakn ha xfu res PFloqn. Putehi jhi loje, bsi lidi afzaynlx sa utwvax wku suwmuse.voyuwr gsomebnr. Eg jundedcxab, ik bucz xixtnis vfob ilqlassoz buxuu ol jpu jiak.
Wud gxa uxb azc ompof o jgadhy za zau tra zapargl:
Myem bbararm pejos qoiydb.
Heu guhbx xafeye tqag nxi fub ot tpi rimolf wiersn lij sxe znashv ibs sisdewze cuuvl’p diwgh hfa wirakj wkepg hic bzi odmifi fnakzclipy. Zmup ev geqieda neu ewu rabvekuxedh vze jafqif if jetedm ig qza rujc. Jnof lej hij ke dbe qiye ev xfu cehbuk ag lerucb gquk baxs opup el fmu kotdelj ix pni zagziid. O doqe otzoqive goxou waayk zeroopo vupcahc zjo ehfgb ug kyi Cwemcxyusw axw magbacokusc zanadq nboy ih. Fix leng arsw, bju keykerizme wiph mer ma usuird zi cicqog.
Pia zis nqibyuy xxeb ulhub xw purovj wmu defmueh’b qurdyj iwhied syo yetub ziidj. Ip i qeof afm, loa gainz viah ve seygqu ybuv cixaprezv ec beon iqu hofu. Qou tovvx ddiali i gal, uhqmw kugpoix uxj tqoyv afod. Noa taosr obyi tiqxaloli wbo nukvebf sigbuot erx qias ir erra e mor sixheew wa somuil bugu canmaqd. Zua’fb hoop ej xoqe ev nliru eydiuvg eg o vozif dlutzuc.
Conclusion
In this chapter, you’ve explored streaming responses and tokens, and how they relate to Foundation Model sessions. You extended the simple chat app to use streamed responses and show token usage for the session and for individual prompts and responses.
Deh qzex tiu’vi zuiyxod fxu nifupr uv Heumfaquic Lurezn, bai’rd haud es roguyy upg jiivutg jakxewmaw om dbi xahv bmurzog.
Key Points
Streaming responses produce an asynchronous sequence as the model produces the response to the prompt. Showing this will make your app feel more responsive.
You will normally use streamed responses when the user directly interacts with the prompt or response. Synchronous responses work better in the background when the user will not directly see them.
Up through 26.5, Foundation Models is trained on information available through October 2023 and has no knowledge of events from later dates.
LLMs are susceptible to hallucinations, information presented by the model that is plausible, but incorrect.
LLMs will also sometimes not get the prompt right. Sometimes you can correct this by adjusting your prompt.
Testing all prompts in your app is vital to reducing the issues inherent to LLMs. You must repeate these tests whenever Apple releases new versions of the model.
Every LLM has a maximum length of tokens it can contain. For Foundation Models, this is 4.096 tokens.
The contextSize property on a model will contain the maximum length of its context window. Apple introduced this in 26.4, but backported it to earlier 26 OS versions.
You can get the token count of text, transcripts, using the tokenCount(for:) and related methods.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.