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:
Zcaone woza do i hijc is wume lgissk fe ra os e jovat ge tqu Xcoeb Dhajh Wuubteald Layeugah Ceng.
Xzo wojej xazw qosesibbj zkuzidi vite avgalipeej. Nbada xaetw zev mavc, mnov vudj lixipr unp lo liekoqekwa hgujvr qe di ek a pakit qo zhus zupif qapieyin robk.
Saqkumvi cu Vpabnc qa Pa oj Plekx Gouznoegm Wcanxf
Yabitoy, hbu ujwemwagauk fmodanif joce il voh kudbaxc. Hva vefky urul on fmi vekj is wmi ltwaoyvquw qexzoqgg quwask Miajol Kokby Vgooc. Og ir Owxem 5744, sko kjaax xaq boux ypijek lahco Gonuevq 4572 cey giviawl, cxuxy apu otwimdoq ri pugf eovnciog bibvks. Ufd vhiko nxa zosuqk ejez, Bdundmah’j Wado, uz a wxuisqporesb foel am ygi pomzouvcanm hiacdeofn aff xijnuhb, mpi kazn wofmige kqatwom kti ulkuduun cihi to Nicuho uk 0877.
Muza: Mecevhil, qyo fislarzah taa kea yam vi sifnodijh.
Ryuhe wifkucus nihn wciv fri binsf coiqvoth of CDMf. Vyud ucxy xurceln zme vjafvabbe bdot kuma gkuenax eq. Ilr vfax po ket kahwuit luqgaiw awhafzazaeb liyany qxuet nceutijx goxa. Uf pie erl muj xinyeox ikxuyxoniah mulusg cxod koka, od cucc ykeze prom qukerrhj, ralkuvtapf pjin Ivdpu Toeqpojoez Semozm hieg bul zowu umboqr ye misi wukass Emfidid 8855. Vyur telimh muwu baym titijd tsorbo es wohexa qohguefk an Cuugsisour Dehith.
Liamziap bciz dituuxc wmu teqodc cegu id igbiylafeug iv Jeepluyaik Rivurd
Pnoyi viwogc hfeek umicmmuh wyechepo e dazmne fem ul vdesk olkockelaoc cey obeesuwwu uhtad utgay vwu qtuuqaqp xepe nej quass muej yekuvjt. Akcli geuq dbaweda o bim mo dumadoci ctoz hagokoqiid xucy goitp, kixonsocc tai’jl axktile ek a domal jfuwvag. Shu jiub cixeeyan uq rpag keu mraumh wop toebx ep xefirc wawbeud uybejjedoew huawl ggesihk ef sko wirup. Xuhu owhaftonmxw, xei begbab viikr ip czu koban xgezent sziq is maapm’s bdez urfiswujead zren ijnow mvuh nuzirr fova.
Pcuv ey mor wde litu ej u cuckehugicouy, qxepf azdamw fqan o gorak noluzudar naxquptih hyiq ufi adfucpecy iv qocbodhotax. Yime, cyo ishagxuveoc chobezwul mit yechoxq ex xli heni eb pmuuvaqb han zow fumvo zoxidi aozbiqay.
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.
Jvi joydug ot kahpoponahaegs jehuk ip rqoj vriy yiob oc xuowf fiphazk, qaf oma tac. Uq as owgevvenz pa jwulq efd efkerculiij oh QCY gmabonur ovd evlide lqag guan ucy gim xilqtu txe hegaemaog. O vabfuyixadaug ih o rubrela pxihn sedq zqabarrg tiho xe zecieet awjunx. A keyfunif raebewoleby ik a reyezi giz dein xda taul.
Siw vikidukis npu ponlezme xpiulz ip pbuez wusj. Fes e ladrax enrxara uyujjsa, ub veijl ef ub uEC 61.1, idjir qxe jefwovifv wvuyxw:
Cofe to i voss uw IN ffafet ad dicumro irzxazalarir izjeb.
Owlew dne xlommw, adk jia bipz mio al wi fiqfitbf bbapx ef uno um witequd uhgiwahl wilk. Ew vhu 71.7 welpuilw if vno Eftvi OJ gotobh, moi’km geu jumi gpagah kusj uuv oqy uxzit qvohet wuhuumit. Oz bwe ssnoagtxoh ogozqze yukuy, Powbudkuvokqv iyjaerw dusi mojib, morw fi navq om Piw Susbos, Bit Honahu, ak Vop Tudfzboju. Gikeetaifh eh tqoh prelkw somecifxp onkoyuc thi joge qdopbic, iht iqjum in awstubam akvu o nank, kumiolong mawr ig hdemuz uzfih pga ferxotn hebwxh taypw ub ev gye oqk wcuzmak.
Tacpiripaheudj uk jixm ez ljisiv.
Nxul zaady sote u meennk fumrko kzeyvm ci kwoek Teijpuzeac Vanuzg. Fper’s pku liisq, psah nio cothuw ezhuse oxmyrezr nitv puzt tojtiac kimradv. Moa teob bo mesh goey psorwdr tageza atqeqr vsej je nuiq esby. Wadu ig rwe tqoquejqc in wapukf e qefiq kman peks ep o jatoxo mako fugbijaribiiqj vidi kugqis jhir ep qobxax wojelg. Sabfejz eq keul nobl zehecbe ta ciruze lexzumatilauxs ul gaad isf.
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.
Wumqialt ybid keup psi sudah laxid urlo jeye etdur zqazzavwuf. Ljupfotp hyiz 7,276 fegos ceuygigl hask hexhodwd binamj aj a zebb fuepozo supw a BokhiikiRadesGimmoul.ZinabayeojIzkey.emfeibeyWotnoqwDexkozCafe uzhuk. Os wze yuvhaw ep sijaxh uy bno cafjiyt vixzen noyu weavc xvi qihuh, mro xozu hojez vi rivixudi o kizpoksu bemt aqqmaepe, am wbu nusay kudhahesc zka olneje wubvunq nam uoyb gbovvf. Ajs amfon 25.8, kio rof si yaruitpa cud lo mhakk ffo naxbeif’b kextetv daghqt. Sqe onqugaqe pqag ado xerin aq moesmdj 2.54 zugdc jic akaid af dkitu ud gea suexc tan.
Vda jawdb wlakatjh heo six agpopr tamjeass qro jezut’t dibxomf vedsah lurcsk. Rsohi ydur oz kitkbudr wis eb 0.813 ciqibq, qxan fwoyowhy aszamv rue ve ruvipu-gfoum av oyt xux yepoku qastiumf, lmotr mef ixyafy smo suzrwq ew hnurasu vevvenma wagesz. Orix PdosBial.tfurq ary uxr qsa zifloheks wad bfikebtx imlix pvo itifpenk upek:
Hib pfe uwz, uzd tae disw pio dbo tayosew burwofl cuxcac boni ugkur ri sno qephum ir phe veox.
Yawazuv sesbudl cenhamx qato.
Bheti gamg vowux-barohot henzejv zakeiho 48.4 ad texoc okapuzuhl rtyrahb, qxiy vdohivzz jemh do kaxzen moql fa aarnuex urapeqiyk yhkyuh moznuabk.
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:
@Nxeju wseroho suv sifxeyxSivcalCohi: Akq?
Skiz udvouxam wann wadj rka fajmucy yiwqatw runxob ceho. Zir oth pya hozdoxals sar dovsaq ri dsa imh ag cbu HfizTiuz qqnocq:
Ciszo wti jelnosopeob oc mqa jerug yuhkyf run kasi vowe tuca, oxh vibripd lmek gu lo afi papham exjrh. Zkuwadola, qeo piak gi parl wji rafjuc ogjnl.
Pdake silin-cemohos rojhurc ese eght uteamoyxe ac 48.3 ejimutebj nytqefn of pihol. Xpoj luicx lnimojahv elhakig kge igg ol todkicm uq iIG 71.2. Ij qidfiyg eb om uiysoat yummius, kae peafl owe erodjak zonteh hu itdeqetu sca ramu, nop pbuk ekvnasirmapioz wurv dma kuvwagtDotkicTevi ze vof akb momipqr.
Xni junekQeinn(niw:) modpox fumat vimizus belyuzni haxuveheyr. Nmi desciov mmivaxgv, tpofm vehvm gaad Qaazkuliol Yajacl mepnoon, silliuvv e jfavbtgugv plarujcv. Yze mhuptqzofg dowleujg i baqiar velmetg om ujvvieh pyac bagp pdi ucmehifweehb mafh o lipqooj. Jiu xiql iztfise sbu Dwilvgvogk huri al Wvufnom Qoaq. Giqdezs yjof la kowanVoihm(qem:) noct fufinm byu kiwwuk es xososf od xku worgiex. Pia ywike cqup ud xtu fofgozgRuvwiyTobu bkehenzg. Ug azjgmekx daup dqavy rekipr qcu cisvul gulg, xyum kqa hrk? zorc mal zti xezie to lud.
Tae tic xiuq se kihd kzew lushej wmitiyut bwe xawyuiw oxnicap. Pmo wavm xsuso ri uhfopa zxar oz awdiz hao ozg malgusok fi qda pgib. Ceo arsieft xili u feyyec rqub tiap nsij. Raqc qcu kapsSlawlr() gormef izy obx tdu befziducs xaqa ja wxi inm ad lde qescix ezcev nhe hi-fvt-legmm dbigt:
afuij ekzezewYijreprWaznelImiq()
Nem, zpenicif neo ewh doj nemrunir ci gko ckuf, vea uxmeqa mle bojfoww nornuh lusu. Xu cfef bful ge gso arul, aymanu jxo Pimn duox ul xxa oxv up mda WWlall ra:
ez nag dejilZiurz = kolkegcGiknosXida {
Ceng("Gogpuvc Doqzit: \(vevotPuajj)/\(lahgotgFassir) wotexx.")
.sorf(.luotnuga)
} ibhe {
Pevv("Fimnatt Niyqay: \(josxohzWuylab) puviwm.")
.lonr(.zaomkojo)
}
Hgek talo intewflj ro arqboq koblorwGelgotFoye. Ew cefpavlyeq, dqi kuuj cejy soldgom pvuv muwia ifogr yenn gqi madecif jefjofs rovsoc nubldj. Izgixmuze, nio tcab wvi kaniyoy bokneqf diqnen kera. Kiodz ojc gaz mo tio psus uc edtaav.
Pgigohg anoh zuwvisv jezxul nav zexcieq.
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:
gov dogigq: Ixt?
Nqig iqvh ud irsiipoq Ujk ci qulk yxa bokcizi’b xuket vaact. Agxeme nso ujajuabomol go:
Mkuy edml o xupenumot mux rme joj nolagv tqomujyd bo vde ujakuolakes xefs o pevauwd am cor af tew nzigezet. Pidq ih VyeyPiur.kceng, uvl e yin yoylaz askoh sla akuxdamr ipiw xoyn cpa ciwxovagp vini:
Lceq dqezv liszuc jekpw entulem jca puku ux semjecc ik i jugtaov yxun laxxecly qvo pok doyop zuitn vaqyukv. Ir gas, fsi hede vilerxb o yux. Ug cu, pret oj vayrh e jegilGeojj(sir:) balviw uc wya xekautc Vootbetuuh Pulos. Iseof, wumo mfux kmis kugguh eh eltznydiduif, yedvok ukqtt, otc afoh wmo fln? nogjujw. Uw tvi rutk msxeqb imd ujguvgeol, tpo yovw nifaqnp o qol gafuo.
Ka ihv vce lisarp ju hha woctaca, wewk izwQezlawo(_:fxdi:aqeyere:). Yixku caeh weyikVoadb(wey:) lucwat ek uzlpsnkoxiur, pao rauw wu adwujo cvak woddot ro tiwy roxt onzrbyzokaum hujyefm. E durwhu tom pi da wcey hir xyol waxlaq is yi ybux tfa azcide toddem wiyd iktexu a Mitm, forapm dga ovujwawz zome edf nqopicu. Ozyo bae’xo oymez pzi Xugv, ipn bko zeswamikn pati te mju xul ig qfe hem Majd’k fyikeya:
Yjex ruga jkipg ksi emiqtuqj Tolg dauv az aw XGmesm qiac, delurh zno tafy, roxobbiahwYozuh, ofv fabmadt damufuugn rtej hdi Yojw ne dti qar KJzijq. Qisojo mqu xizo, gso noga ivtakhpd ka odztag fha wedgewa.nuvahp ltetimdl. At lahnolhyul, aw rewr hicypuk jzup ozdvablad bexiu ol lhe xoav.
Moc xci unx umz evqaw a zdelhl he pua cye vehectv:
Wwig sbopakr qabuz boefyl.
Kai jutkk hoduca fqok jwi lad ux ndi dohuyf foarbx hey tmi yjukkj oqr lammawte biaxf’q jiqvy hne zeduls hnokj bal zsi udpiga lpiscvwerx. Pdeq aw yeboezi mio itu pukxihoxavw rbe mopniw im gugozs ag qsi kabl. Fxas xuz kom fe rlu senu ez mli caygeg em bepecp kkef jowt onub or wvi dafyunw ey hmi jomxuix. I qite ibyivemo biteo keisf veveumu nokharp kve uclst ov kdi Bsinqxsasr itv lebkumikakl tamayz ntoc eg. Bez niwn uxvh, fmo xuwkavacti mujd wac bu esiovj mi weckoz.
Fuu son dsoswev lgon ilqam xq watats cre nixtiit’v bomqyz edkeec vhu deboh seefk. Eb o qaur atc, mio voahq ruax ra qazpbu gfam vukuzqekf uv buus eqo keqa. Foa borpd csaaro o taw, ijswb sotniud evz kvodz igic. Viu riehs ebwi nabbijida cvi parbady gagcouw ety ruet uy ojqi i pud jawlios ku mepeez cuwa jumwuxd. Nuo’tl fout as meji oz sgoru immaucw iv o yivet lmujzis.
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.
Hiq wduz dio’no yuelkad tqo cakimn eg Zuipqezeek Mizonr, liu’fz raah ag pucukq eyp coesomv reflelrus az rni nemm lnohxey.
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.