1: Flutter Korea 행사가 11월 7일에 열립니다. 응답 2: 행사명: Flutter Korea / 날짜: 2026.11.7 응답 3: {"event": "Flutter Korea", "when": "11/7"} 같은 요청에도 매번 다른 형식의 응답 → 프로그램에서 파싱 불가 7 / 43
string, "param": array} · 지금까지 생성된 출력 {"func": "add", ▌ 다음 토큰 후보 확률 스키마 "param" 0.52 ✓ 선택 가능 "note" 0.21 ✗ 없는 키 Here 0.15 ✗ JSON 아님 } 0.07 ✗ 필수 키 누락 매 토큰마다 후보를 걸러내서 출력 형식을 100% 지킬 수 있음 9 / 43
내장 모델 대표 예시 (iOS · Android · 공통) Apple Foundation Models Gemini Nano ML Kit Prompt API 플랫폼 추론 Core AI / Core ML 프레임워크 LiteRT-LM 추론 엔진 llama.cpp · ExecuTorch · MLX 쓸 수 있는 모델 OS 내장 모델 공개 모델 공개 모델 Flutter에서 platform channel flutter_edge_ai (builtin 엔진) flutter_edge_ai (구 flutter_gemma) llamadart · flutter_edge_ai OS 내장 모델은 간편하지만 모델 고정 / 추론 엔진은 원하는 공개 모델 실행 13 / 43
내장 모델 대표 예시 (iOS · Android · 공통) Apple Foundation Models Gemini Nano ML Kit Prompt API 플랫폼 추론 Core AI / Core ML 프레임워크 LiteRT-LM 추론 엔진 llama.cpp · ExecuTorch · MLX 쓸 수 있는 모델 OS 내장 모델 공개 모델 공개 모델 Flutter에서 platform channel flutter_edge_ai (builtin 엔진) flutter_edge_ai (구 flutter_gemma) llamadart · flutter_edge_ai OS 내장 모델은 간편하지만 모델 고정 / 추론 엔진은 원하는 공개 모델 실행 14 / 43
Opus 4.6 · GPT-5.2 2026.02 Tool Call 안정성 100%에 가까움 오픈 웨이트 모델 Qwen3 시리즈 Tool Call을 제대로 못하는 모델이 많음 1) 모델이 Tool Call 해야하는 상황인지 파악하지 못하거나, 2) Tool Call 포맷에 대한 학습이 부족하거나, 3) Tool Call 결과를 제대로 파싱하지 못하거나 17 / 43
Opus 4.6 · GPT-5.2 2026.02 Tool Call 안정성 100%에 가까움 오픈 웨이트 모델 Qwen3 시리즈 Tool Call을 제대로 못하는 모델이 많음 1) 모델이 Tool Call 해야하는 상황인지 파악하지 못하거나, 2) Tool Call 포맷에 대한 학습이 부족하거나, 3) Tool Call 결과를 제대로 파싱하지 못하거나 18 / 43
단계 담당 v0.5.1 코드 포맷 판별 ChatTemplateEngine detectChatFormat(templateSource) 도구 목록을 프롬프트에 렌더링 handler HermesHandler.render 출력 제약 (required일 때) handler HermesHandler.buildGrammar 토큰 생성 llama.cpp LlamaEngine.generate 텍스트 → 함수 이름과 인자 handler HermesHandler.parse 도구 실행과 결과 전달 앱 tool.invoke · LlamaToolResultContent template과 parser → handler 하나로 통합, 모델 1개에 handler 1개 대응 21 / 43
ChatSession.create user 메시지를 히스토리에 추가 // lib/src/core/engine/engine.dart · LlamaEngine.create final result = await chatTemplate(messages, tools: effectiveTools, toolChoice: toolChoice); LlamaEngine.create tools, toolChoice 전달 chatTemplate final tokenStream = generate(result.prompt, params: params.copyWith( GGUF의 chat template으로 프롬프트 생성 stopSequences: stops, grammar: effectiveGrammar)); await for (final token in tokenStream) { generate llama.cpp 토큰 생성, 내용 조각 yield ChatTemplateEngine.parse 생성 완료 후 전체 출력 파싱 buffer.write(token); // content / thinking 조각을 바로 yield } final parsed = ChatTemplateEngine.parse( result.format, buffer.toString()); if (parsed.hasToolCalls) { yield LlamaCompletionChunk(... delta: LlamaCompletionChunkDelta(toolCalls: parsed.toolCalls)); } 22 / 43
Call 렌더링 결과: Qwen2.5 시스템 프롬프트 일부 // handlers/hermes_handler.dart · render final template = Template(templateSource); // GGUF 안의 Jinja # Tools var prompt = template.render({ 'messages': messages.map((m) => m.toJson()).toList(), You may call one or more functions to assist with the 'add_generation_prompt': addAssistant, user query. 'tools': tools?.map((t) => t.toJson()).toList(), }); return LlamaChatTemplateResult( prompt: prompt, You are provided with function signatures within <tools></tools> XML tags: <tools> {"type": "function", "function": {"name": grammar: buildGrammar(tools), "get_weather", ...}} additionalStops: getStops(hasTools: hasTools), </tools> grammarTriggers: [ GrammarTrigger(type: 0, value: '<tool_call>')], For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags 24 / 43
Call // handlers/hermes_handler.dart · parse final toolCallRegex = RegExp( r'<tool_call>\s*(.*?)\s*</tool_call>', dotAll: true); 정규식으로 추출 <tool_call> 태그 사이 텍스트 for (var i = 0; i < matches.length; i++) { final match = matches.elementAt(i); JSON 디코딩 try { final json = jsonDecode(match.group(1)!) as Map<String, dynamic>; toolCalls.add(LlamaCompletionChunkToolCall( 마지막 chunk에 담아 전달 index: i, id: 'call_$i', type: 'function', 생성이 끝난 뒤 한 번만 파싱, tool call은 완성된 function: LlamaCompletionChunkFunction( 형태로 도착 name: json['name'], ...))); } catch (_) { 25 / 43
{...}}] 2) [THINK]...[/THINK][TOOL_CALLS]get_weather[ARGS]{"location": {"city": "Paris"}} 3) get_weather{"location": "Paris"} // 마커 없는 짧은 형식 기존 정규식이 중첩 객체를 자름 짧은 형식은 일반 문장도 오인 {"location": {"city": "Paris"}} ← 마지막 } 누락 hello{"key": "val"} → tool call? 괄호 깊이를 세고 문자열 안의 괄호는 건너뛰는 balanced [TOOL_CALLS] 같은 명시적 마커가 있을 때만 파싱 brace 파서로 교체 26 / 43
· In-app Copilot copilot_tools.dart 도구 하는 일 구분 ToolDefinition( name: 'add_expense', find_expenses 조건별 지출 조회 description: 'Records an expense.', 조회 parameters: [ spending_summary 기간별 지출 합계 조회 ToolParam.integer('amount', description: 'Whole won.', required: true), ToolParam.string('category', description: categories, required: true), budget_status 카테고리별 예산 조회 ToolParam.string('merchant', required: true), ToolParam.string('date', description: 'YYYY-MM-DD. Defaults to today.'), add_expense 지출 기록 변경 set_budget 월 예산 변경 변경 ], handler: (params) async { delete_expenses 지출 삭제 final amount = params.getRequiredInt('amount'); if (amount <= 0) throw ArgumentError('amount must be positive.'); 변경 · 승인 final expense = ledger.add(amount: amount, ...); // 기존 앱 함수 return {'added': expense.toJson()}; }, ), 앱에 원래 있던 함수들을 도구로 활용 37 / 43
· In-app Copilot 잘못된 인자 · copilot_tools.dart Category? _category(String? name) { if (name == null || name.trim().isEmpty) return null; return Category.parse(name) ?? (throw ArgumentError( 예외 메시지를 모델에게 반환 → 재시도 'Unknown category "$name". Use one of ' coffee 입력 시 카테고리 목록을 보고 다시 호출 '${Category.values.map((c) => c.name).join(', ')}.', category는 enum 대신 문자열로 받아 handler에서 검사 )); 사용자 승인 · delete_expenses handler if (matches.isEmpty) { throw ArgumentError('No expense has the ids ${wanted.join(', ')}.'); } 삭제 전 dialog로 승인 대기 if (!await confirm(question)) { return {'deleted': 0, 'note': 'The user declined.'}; 거절도 결과로 반환, 모델이 답변에 반영 } return {'deleted': [for (final e in ledger.delete(wanted)) e.id]}; 에러와 거절도 tool 결과로 반환 → 모델이 다음 행동을 결정 39 / 43
대한 처리 llamadart는 llama.cpp 엔진을 Dart로 지원하며 Tool Call 관련 구현을 쉽게 시작할 수 있음 02 llamadart를 분석하고 기여하면서 온디바이스 에이전트에 대한 이해를 얻을 수 있었음 모델마다 다른 Tool Call Format 대응 및 관련된 버그 수정만으로 Tool Call 정확도를 올릴 수 있음 03 llamadart는 지금도 계속 업데이트 중! DecisionEngine 지원 및 재밌는 수많은 예제 제공 중 42 / 43