애플 인텔리전스 써보기 - Foundation Models 프레임워크 심화

농담고미고미·2025년 6월 28일
0

프론트엔드

목록 보기
12/12
post-thumbnail

애플 공식 Foundation Models 프레임워크 영상 링크

Foundation Models 프레임워크는 온 디바이스로 작동하기 때문에 개인정보 유출 걱정 없이 연락처, 캘린더 정보 등을 받아와 그 정보를 바탕으로 AI가 텍스트를 생성할 수 있다. 저 영상 내용이 아주 흥미진진함!!

@Generable, @Guide 사용법, Dynamic Schema, 이전 세션의 정보를 일부 새 세션에게 전달하고 시작하는 법을 다룸.

import FoundationModels

func respond(userInput: String) async throw -> String {
	let session = LanguageModelSession(instructions: """
    	You are a friendly barista in a world full of pixels.
    """
    )
    let response = try await session.respond(to: userInput)
    return response.content
}

session.respond(to: "How long have you worked here?")

이게 기본 틀. 그런데 만약 많은 요청을 생성하거나 큰 프롬프트를 입력하거나 큰 출력을 받는 경우 컨텍스트가 한도에 도달할 수 있다.

해결방법은 1. 아예 새로운 세션을 시작한다. 그럼 이전 내용을 AI가 다 까먹는다.

// Handle context size errors

var session = LanguageModelSession()

do {
	let answer = try await session.respond(to: prompt)
    print(answer.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
	// New session, without any history from the previous session.
    session = LanguageModelSession()
}

해결방법은 2. 이전 세션의 정보를 일부 새 세션에게 전달하고 시작한다.

do {
	let answer = try await session.respond(to: prompt)
    print(answer.content)
} catch LanguageModelSession.GenerationError.exceededContextWindowSize {
	// New session, without some history from the previous session.
    session = newSession(previousSession: session)
}

private func newSession(previousSession: LanguageModelSession) -> LanguageModelSession {
	let allEntries = previousSession.transcript.entries
    var condensedEntries = [Transcript.Entry]()
    if let firstEntry = allEntries.first {
    	condensedEntries.append(firstEntry)
        if allEntries.count > 1, let lastEntry = allEntries.last {
        	condensedEntries.append(lastEntry)
        }
    }
    let condensedTranscript = Transcript(entries: condensedEntries)
    // Note: transcript includes instructions
    return LanguageModelSession(transcript: condensedTranscript)
}

위 코드에서 대화기록에서 첫번째와 마지막 entry만 추가한다. 추려낸 entry로 Transcript를 새로 생성한다.

아니면 자기가 summarizer를 만드는 방법도 있다.

파운데이션 모델은 기본적으로 특정 확률 범위 내에서 토큰을 고른다. 예를 들어 Ah, Well, I, Not ... 토큰이 있다면 각 토큰은 각 확률을 가지고 있다. 어떤 경우에는 Ah 토큰을 선택하고, 어떤 경우에는 Well 토큰을 선택한다. 이렇게 토큰을 선택하는 것을 샘플링이라고 한다.

그치만 매번 같은 토큰을 선택하게 하고 싶을 수도 있다. 그럴 때는 sampling: .greedy를 한다. 그럼 항상 확률이 제일 높은 토큰을 고른다.
temperature를 낮게 설정하면 낮은 무작위성이고, 높게 설정하면 높은 무작위성을 띈다.

// Deterministic output

let response = try await session.respond(
	to: prompt,
    options: GenerationOptions(sampling: .greedy)
)

// Low-variance output

let response = try await session.respond(
	to: prompt,
    options: GenerationOptions(temperature: 0.5)
)

만약 입력이 그리스어라면 당황스럽겠지... 한국에서 보통 그리스어를 지원하지는 않는다. 지원하지 않는 언어라면 unsupportedLanguageOrLocale 오류를 던진다.

var session = LanguageModelSession()

do {
	let answer = try await session.respond(to: userInput)
    print(answer.content)
} catch LanguageModelSession.GenerationError.unsupportedLanguageOrLocale {
	// Unsupported language in prompt.
}

let supportedLanguages = SystemLanguageModel.default.supportedLanguages
guard supportedLanguages.contains(Locale.current.language) else {
	// Show Message
    return
}

이제 @Generable을 이용해보자.

@Generable
struct NPC {
	let name: String
    let coffeeOrder: String
}

func makeNPC() async throws -> NPC {
	let session = LanguageModelSession(instructions: """어쩌고저쩌고"""
    let response = try await session.respond(generating: NPC.self) {
    	"Generate a character that orders a coffee"
    }
    return response.content
}

@Generable에 @Guide를 함께 쓰면 제약조건을 추가해서 더 편리하게 쓸 수 있다.

@Generable
struct NPC {
	@Guide(description: "A full name")
	let name: String
    @Guide(.range(1...10))
    let level: Int
    @Guide(.count(3))
    let attributes: [Attribute]
    let encounter: Encounter
    
    @Generable
    enum Attribute {
    	case sassy
        case tired
        case hungry
        ...
    }
    
    @Generable
    enum Encounter {
    	case orderCoffee(String)
        case wantToTalkToManager(complaint: String)
    }
}

이제 NPC에 풀네임이 생겼고, 레벨을 1~10 중에 설정하고, 성격을 3가지 고를 수 있다. 그리고 NPC와 마주쳤을 때 이벤트가 2개 중 1개가 발생한다.

@Guide에 정규식을 쓸 수도 있다.

@Generable
struct NPC {
	@Guide(/(Mr|Mrs)\. \w+/)
    let name: String
}

(생략)

session.respond(to: "Generate a fun NPC", generating: NPC.self)

그럼 Mrs. Brewster와 같이 생성된다.

Dynamic Schemas를 사용해보자...

@Generable
struct Riddle {
	let question: String
    let answers: [Answer]
    
    @Generable
    struct Answer {
    	let text: String
        let isCorrect: Bool
    }
}
struct LevelObjectCreator {
	var properties: [DynamicGenerationSchema.Property] = []
    
    mutating func addStringProperty(name: String) {
    	let property = DynamicGenerationSchema.Property(
        	name: name,
            schema: DynamicGenerationSchema(type: String.self)
        )
        properties.append(property)
    }
    
    mutating func addArrayProperty(name: String, customType: String) {
    	let property = DynamicGenerationSchema.Property(
        	name: name,
            schema: DynamicGenerationSchema(
            	arrayOf: DynamicGenerationSchema(referenceTo: customType)
            )
        )
        properties.append(property)
    }
}

var riddleBuilder = LevelObjectCreator(name: "Riddle")
riddleBuilder.addStringProperty(name: "question")
riddleBuilder.addArrayProperty(name: "answer", customType: "Answer")
var answerBuilder = LevelObjectCreator(name: "Answer")
answerBuilder.addStringProperty(name: "text")
answerBuilder.addBoolProperty(name: "isCorrect")

let riddleDynamicSchema = riddleBuilder.root
let answerDynamicSchema = answerBuilder.root

let schema = try GenerationSchema(
	root: riddleDynamicSchema,
    dependencies: [answerDynamicSchema]
)

let session = LanguageModelSession()
let response = try await session.respond(
	to: "Generate a fun riddle about coffee"
    schema: schema
)

let generatedContent = response.content
let question = try generatedContent.value(String.self, forProperty: "question")
let answers = try generatedContent.value([GeneratedContent].self, forProperty: "answers")

addArrayProperty: "이 속성은 배열인데, 배열 안 요소는 customType이라는 사용자 정의 타입입니다"라고 설정한다.

let schema = try GenerationSchema(
	root: riddleDynamicSchema,
    dependencies: [answerDynamicSchema]
)

: 이 두 구조를 하나의 AI 생성 스키마로 묶음. root는 최종 생성하고자 하는 데이터 타입 (Riddle). dependencies는 참조되는 하위 타입 (Answer)

let generatedContent = response.content
let question = try generatedContent.value(String.self, forProperty: "question")
let answers = try generatedContent.value([GeneratedContent].self, forProperty: "answers")

응답에서 question과 answers를 추출한다. question은 String. answers는 GeneratedContent 객체 배열 (→ 각각 Answer임)

profile
농담곰을 좋아해요 말랑곰탱이

0개의 댓글