
Foundation Models 프레임워크가 공개되면서 온디바이스로 간단한 대화형 AI 기능 구현이 가능해졌다. 모델이 30억 파라미터의 규모의 LLM이라고 함. 아 빨리 써보고 싶어!!!!
@Generable
struct SearchSuggestions {
@Guide(description: "A list of suggested search terms", .count(4))
var searchTerms: [String]
}
@Guide은 주석 같은 역할을 한다. count, maximumCount와 같은 속성을 설정할 수 있다.
// Responding with a Generable Type
let promt = """
Generate a list of suggested search terms for an app about visiting famous landmarks.
"""
let response = try await session.respond(
to: prompt,
generating: SearchSuggestions.self
)
print(response.content)
여기서 response에서 좀 더 안전하게 할려면
catch { LanguageModelSession.GenerationError.guardrailViolation { // Handle the safety error
}
를 한다. guardrails는 인풋 프롬프트랑 모델의 아웃풋을 체크한다.
그리고 앱에서 인간에게 직접적인 인풋을 받는 프롬프트는 위험성을 증가한다. 따라서 가능한 인풋 프롬프트를 사용자에게 직접 받는 걸 피하고 인풋이 뭔지 제대로 알 수 있게 boundary를 두는게 안전하다.
enum TopicOptions {
case family
case nature
case work
}
let topicChoice = TopicOptions.nature
let prompt = """
Generate a wholesome and empathetic journal prompt that helps \
this person reflect on \(topicChoice)
"""
혹은 프롬프트 인풋을 사용자에게 자유롭게 이용하게 하고 싶으면, 아웃풋에 바운더리를 거는 것도 좋은 방법이다.
@Generable
enum Breakfast {
case waffles
case pancakes
case bagels
case eggs
}
let session = LanguageModelSession()
let userInput = "I want something sweet."
let prompt = "Pick the ideal breakfast for request: \(userInput)"
let response = try await session.respond(to: prompt, generating: Breakfast.self)
위 코드에서 사용자가 자연어로 입력한 요청을 기반으로 AI는 breakfast enum 중 가장 적절한 항목을 직접 선택하여 반환한다. generating: Breakfast.self는 응답을 해당 enum 중 하나로 제한하는 역할을 한다.
텍스트는 토큰이라는 짧은 문자 단위로 생성된다. 스트리밍 출력 시 토큰은 델타라는 형태로 전달되지만 파운데이션 모델 프레임워크는 다른 방식을 사용한다.
파운데이션 모델 프레임워크는 구조적 출력을 이용한다. 원시 델타 대신 스냅샷을 스트리밍한다.
Snapshot: Message.PartiallyGenerated(greeting: nil)
Snapshot: Message.PartiallyGenerated(greeting: "")
Snapshot: Message.PartiallyGenerated(greeting: "Welcome")
Snapshot: Message.PartiallyGenerated(greeting: "Welcome to")
Snapshot: Message.PartiallyGenerated(greeting: "Welcome to Cup")
PartiallyGenerated은 모든 속성이 옵셔널이다.
struct Itinerary: Generable {
var name: String
var days: [Day]
nonisolated struct PartiallyGenerated: ConvertibleFromGeneratedContent {
var name: String?
var days: [Day.PartiallyGenerated]?
}
}
nonisolated는 actor 외부에서도 자유롭게 접근 가능한 메서드/속성/타입이 되게 한다.
PartiallyGenerated은 streamResponse 메서드 호출 시 사용된다.
// Streaming partial generations
let stream = session.streamResponse(
to: "Craft a 3-day itineray to Mt. Fuji.",
generating: Itinerary.self
)
스트림 응답은 비동기 시퀀스를 반환한다.
for try await partial in stream {
print(partial)
}
그럼
Itinerary.PartiallyGenerated(name: "Mt. Fuji", days: [Day.PartiallyGenerated(...)])
와 같이 출력된다. 보면 저 문장에서 여행지 이름과 날짜를 추론해서 뽑아낸 뒤 Itinerary 모델로 바꿔준다.
그럼 뷰에서는 어떻게 사용할까?
struct ItineraryView: View {
let session: LanguageModelSession
let dayCount: Int
let landmarkName: String
@State private var itinerary: Itinerary.PartiallyGenerated?
var body: some View {
// ...
Button("Start") {
Task {
do {
let prompt = """
Generate a \(dayCount) day itinerary \ to \(landmarkName).
"""
let stream = session.streamResponse(
to: prompt,
generating: Itinerary.self
)
for try await partial in stream {
self.itinerary = partial
}
} catch {
print(error)
}
}
}
}
}
AI 모델에게 프롬프트를 보낸다 (streamingResponse) -> 모델이 응답을 부분적으로 스트리밍한다. -> 그 스트림을 for try await로 반복하면서 각 파트를 하나씩 받아서 받아올 때마다 self.itinerary에 최신 상태로 업데이트한다.
로딩되는 동안 애니메이션을 이용한 전환을 하면 사용자 경험이 향상된다. 그리고 뷰에서 배열로 쓸 때 뷰의 식별자 관리에 신경 써야한다.
그리고 파라미터 순서대로 생성된다는 것도 기억하자. 퍼포먼스에 영향을 줄 수 있다.
@Generable
struct Itinerary {
@Guide(description: "Plans for each day")
var days: [DayPlan]
@Guide(description: "A brief summary")
var summary: String
}
이런 경우에 요약 파라미터가 DayPlan보다 뒤에 오는게 로딩 시간이 짧다.
Tool calling도 간단히 보자.
import FoundationModels
struct GetWeatherTool: Tool {
let name = "getWeather"
let description = "Retrieve the letest weather information for a city"
@Generable
struct Arguments {
@Guide(description: "The city to fetch the weather for")
var city: String
}
func call(arguments: Arguments) async throws -> ToolOutput {
let places = try await CLGeocoder().geocodeAddressString(arguments.city)
let weather = try await WeatherService.shared.weather(for: places.first!.location!)
let temperature = weather.currentWeather.temperature.value
let content = GeneratedContent(properties: ["temperature": temperture])
let output = ToolOutput(content)
return output
}
}
사용자가 입력한 도시이름을 좌표로 변환한ㄷ.
해당 위치의 날씨 정보를 가져온다.
현재 온도 값만 추출한다.
결과를 GeneratedContent로 감싸고 ToolOutput로 반환한다.
Tool 사용 예시는 아래와 같다.
let session = LanguageModelSession(
tools: [GetWeatherTool()],
instructions: "Help the user with weather forecasts."
)
let response = try await session.respond(
to: "What is the temperature in Cupertino?"
)
print(response.content)
// It's 71'F in Cupertino!
멀티턴 인터랙션도 가능하다. 프롬프트 흐름을 기억한다는 것.
// Multi-turn interactions
let session = LanguageModelSession()
let firstHaiku = try await session.respond(to: "Write a haiku about fishing")
print(firstHaiku.content)
let secondHaiku = try await session.respond(to: "Do another one about golf")
print(secondHaiku.content)
print(session.transcript)
위 코드에서 2번째 하이쿠를 적어달라고 하지 않고, 골프에 대해 하나 더 적어보라고 했다. 그러나 알아서 흐름을 인식해서 2번째 골프에 대한 하이쿠를 작성해준다. 그리고 transcript을 출력하면 각 프롬프트와 응답을 전부 출력한다.
@State private var session = LanguageModelSession()
@State private var haiku: String?
var body: some View {
if let haiku {
Text(haiku)
}
Button("Go!") {
Task {
haiku = try await session.respond(
to: "Write a haiku about something you havenn't yet"
).content
}
}
.disabled(session.isResponding)
}
여러 어댑터가 있지만 Content tagging adapter가 유용하다. 주제 감지를 기본적으로 지원한다.
// Content tagging use case
@Generable
struct Result {
let topics: [String]
}
let session = LanguageModelSession(model: SystemLanguageModel(useCase: .contentTagging))
let response = try await session.respond(to: ..., generating: Result.self)
위의 코드와 같이 주제를 뽑아내서 topics에 저장하고, Result 모델을 응답으로 반환한다. 응용하면 감정이나 행동을 input text에서 뽑아낼 수도 있다.
// Content tagging use case
@Generable
struct Top3ActionEmotionResult {
@Guide(.maximumCount(3))
let actions: [String]
@Guide(.maximumCount(3))
let emotions: [String]
}
let session = LanguageModelSession(
model: SystemLanguageModel(useCase: .contentTagging),
instructions: "Tag the 3 most important actions and emotions in the given input text."
)
let response = try await session.respond(to: ..., generating: Top3ActionEmotionResult.self)
이 Foundation Modeld은 apple intelligence를 지원하는 기기에서만 작동한다!! 사용 가능한지 뷰에서 컨트롤해줘야 함...
// Availability checking
struct AvailabilityExample: View {
private let model = SystemLanguageModel.default
var body: some View {
switch model.availability {
case .available:
Text("Model is available")
case .unavailable:
Text("Model is unavailable")
Text("Reason: \(reason)")
}
}
}