Part A: LLM Structured Output 2026.05 베스트 프랙티스 (Gemini · OpenAI · Anthropic 비교 + Schema 7원칙 + 검증/retry). Part B: Smartlead · Reply.io · Saleshandy · Outreach · Salesloft · Instantly 6사 역기획 (워크플로 · UI · 데이터 모델 · AI · edge case). Part C: 린다 코드의 LLM 호출 지점 매트릭스 + 전환 우선순위 5건. Part D: C2 분기 + C4 학습 루프에 Structured Output 을 매핑한 통합 구현 설계.
Gemini responseSchema · OpenAI response_format=json_schema strict · Anthropic tool use + output_format (GA 2026)
의 핵심 차이. 린다 default 는 Gemini 3 Flash Preview (CLAUDE.md).
| 항목 | Gemini (2.5 / 3 / 3.1) | OpenAI (GPT-5.x) | Anthropic (Claude 4.5 / 4.6 / 4.7) |
|---|---|---|---|
| API | responseSchema (OpenAPI subset) + responseJsonSchema (full JSON Schema) + responseMimeType |
response_format={"type":"json_schema","json_schema":{"strict":true,...}} |
output_format (GA 2026) + tool use 패턴 (tool_choice + strict:true) |
| 강제 메커니즘 | 서버측 schema 강제, key order 보존 (2.5+) | Context-Free Grammar → invalid token masking, 디코더가 물리적으로 위반 불가 | Grammar-constrained decoding, 첫 호출 grammar 컴파일 latency, 24h 캐시 |
| 지원 키워드 | enum, anyOf, $ref, nullable, description, propertyOrdering, optionalProperties |
enum, oneOf, anyOf, nested objects/arrays, description. pattern/format 제한, recursive $ref 제한 | tool input_schema 는 JSON Schema 거의 풀 지원. strict 모드에서 일부 제약 |
| Nullable | "nullable": true 명시 필수 — 없으면 hallucination 위험 |
strict 모드는 모든 필드 required 가정 → ["string","null"] union 권장 |
optional 누락 가능. 명시적 null 시 ["type","null"] |
| Schema 토큰 OH | +60-100 input tok | +80-120 (strict grammar 컴파일은 서버측 캐시) | +150-300 (tool 정의가 가장 verbose) |
| Refusal | 별도 필드 없음. 빈 / 부분 JSON 가능 | message.refusal 별도 필드, 파싱 전 체크 필수 |
stop_reason: "refusal" + refusal 필드 (2026 추가) |
| Streaming partial | 지원하나 incomplete chunks unparseable → streaming JSON parser 필요 | 정식 지원. partial JSON valid prefix 보장 | tool use 스트리밍 OK. native output_format 도 partial 지원 |
| 복잡 schema | 깊은 중첩·긴 enum·optional 다수 → 400. flat 권장 | 깊이 5+, 100+ 프로퍼티 strict 거부 | tool input_schema 가 가장 관대 |
2단계 이상 nested 는 Gemini 에서 400 또는 정확도 하락. address.city 같은 dotted key 를 top-level 컬럼으로 풀어쓸 것.
분류 작업은 항상 enum. intent: "interested" | "rejected" | "ooo" | .... enum 이 100+ 면 hierarchy 로 분리.
LLM 이 description 을 prompt 처럼 읽는다. "return_date: 휴가/OOO 메일에서 발신자가 돌아오는 날짜. ISO-8601 (YYYY-MM-DD). 명시 안 됐으면 null." — example 까지 description 안에 넣을 것.
GSM8k 에서 reasoning 필드 추가만으로 4.5% → 95%. 항상 reasoning 을 result/intent 앞에 배치. JSON object key order 는 LLM 생성 순서와 일치하므로 chain-of-thought 효과.
한 호출당 12 초과 시 정확도 급락 (Gemini Flash 특히). 넘으면 두 호출로 분할 (extract → classify).
모르면 hallucinate. nullable: true + description 에 "확신 없으면 null" 명시. Gemini 는 특히 명시 안 하면 임의로 채운다.
confidence: number (0-1) 추가. threshold 미만 → 사람 큐 또는 더 큰 모델로 escalate.
Grammar 강제로 syntactic validity 보장. 가장 빠른 fail-fast.
schema 가 표현 못 하는 의미 검증. callAIObject 가 이미 Zod 통합.
exponential backoff (1s→2s→4s, max 3). 동일 위반 반복 시 더 큰 모델로 fallback (Flash Lite → Flash → Pro).
message.refusal 별도 필드 — 파싱 전 분기. Anthropic 은 stop_reason. Gemini 는 빈 객체 / finish_reason 로 감지.} 못 찍으면 invalid. 예상 길이 × 1.5 여유.generateObject + Zod, Python = Instructor + Pydantic. 둘 다 provider native strict mode wrap + 자동 retry-with-feedback.enum 은 모든 provider 가 grammar-level 강제. 분류는 reasoning 짧아 token cost 미미. reasoning → intent → confidence 순서.
nullable handling 이 핵심. 각 entity 마다 value + evidence_quote (원문 인용) + confidence. evidence_quote 강제로 hallucination 방어.
긴 자연어는 schema 가 톤·자연스러움 해친다. 구조 필요한 메타데이터 (subject, CTA, tone) 만 schema, 본문은 string.
{plan, steps:[{action, expected}], final_output}. plan/steps 가 final_output 앞에 와야 chain-of-thought 효과.
{
"type": "object",
"properties": {
"reasoning": { "type": "string", "description": "어떤 문구에서 복귀일을 추론했는지." },
"is_ooo": { "type": "boolean", "description": "OOO/휴가/부재중 자동응답인지." },
"return_date": { "type": "string", "nullable": true,
"description": "발신자 복귀일 ISO-8601 (YYYY-MM-DD). '다음 주' 같은 상대표현은 메일 수신일 기준 계산. 명시 안 됐으면 null." },
"evidence_quote": { "type": "string", "nullable": true,
"description": "복귀일을 추론한 원문 인용 (최대 100자)." },
"alternate_contact_email": { "type": "string", "nullable": true },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["reasoning","is_ooo","return_date","evidence_quote","alternate_contact_email","confidence"],
"propertyOrdering": ["reasoning","is_ooo","return_date","evidence_quote","alternate_contact_email","confidence"]
}
{
"type": "object",
"properties": {
"reasoning": { "type": "string" },
"intent": { "type": "string", "enum": [
"interested", // 미팅/제품 관심 표명
"request_more_info", // 질문/추가 자료 요청
"not_now_followup", // 지금은 아니지만 나중에
"not_interested", // 명시적 거절
"wrong_person", // 잘못된 수신자/리퍼럴
"ooo_autoreply", // OOO 자동응답
"unsubscribe" // 수신 거부 요청
] },
"sentiment": { "type": "string", "enum": ["positive","neutral","negative"] },
"urgency": { "type": "string", "enum": ["high","normal","low"] },
"suggested_followup_days": { "type": "integer", "nullable": true,
"description": "not_now_followup 일 때만 D+N 권장." },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["reasoning","intent","sentiment","urgency","suggested_followup_days","confidence"]
}
{
"type": "object",
"properties": {
"reasoning": { "type": "string" },
"rejection_category": { "type": "string", "enum": [
"budget", "timing", "already_have_solution", "no_authority",
"no_pain", "competitor_locked", "compliance", "unclear"
] },
"objection_phrase": { "type": "string",
"description": "원문에서 핵심 거절 표현 인용." },
"rebuttal_angle": { "type": "string",
"description": "이 거절에 대응할 다음 메일 앵글 (한 문장)." },
"is_recoverable": { "type": "boolean" }
},
"required": ["reasoning","rejection_category","objection_phrase","rebuttal_angle","is_recoverable"]
}
import { z } from 'zod'; import { callAIObject } from '@/lib/ai-gateway/call'; export const ReplyIntentSchema = z.object({ reasoning: z.string(), intent: z.enum([ 'interested', 'request_more_info', 'not_now_followup', 'not_interested', 'wrong_person', 'ooo_autoreply', 'unsubscribe', ]), sentiment: z.enum(['positive','neutral','negative']), urgency: z.enum(['high','normal','low']), suggested_followup_days: z.number().int().min(1).max(90).nullable(), confidence: z.number().min(0).max(1), }); export async function classifyReply(reply: { subject: string; body: string }) { const result = await callAIObject({ model: 'gemini-3-flash-lite', category: 'general', schema: ReplyIntentSchema, system: 'You classify B2B sales email replies. Output strict JSON.', prompt: `SUBJECT: ${reply.subject}\nBODY:\n${reply.body}`, temperature: 0.1, }); if (result.confidence < 0.7) { // fallback: 더 큰 모델로 재시도 return callAIObject({ ...args, model: 'gemini-3-flash-preview' }); } return result; }
| 모델 | Input ($/M tok) | Output ($/M tok) | TTFT | Throughput | Structured OH |
|---|---|---|---|---|---|
| Gemini 3.1 Flash Lite | ~$0.10 | ~$0.40 | ~0.9s | ~250 tok/s | +60-100 in |
| Gemini 3 Flash Preview | $0.50 | $3.00 | ~1.3s | ~284 tok/s | +60-100 in |
| Gemini 3 Pro | ~$2.50 | ~$15 | ~2.5s | ~120 tok/s | +60-100 in |
| GPT-5.4-mini | ~$0.25 | ~$1.20 | ~1.5s | ~150 tok/s | +80-120 (cached) |
| GPT-5.4 | ~$2.50 | ~$10 | ~2.0s | ~110 tok/s | +80-120 |
| Claude Haiku 4.5 | ~$0.80 | ~$4 | ~600ms | ~95 tok/s | +150-300 |
| Claude Opus 4.7 | ~$15 | ~$75 | ~2.0s | ~50 tok/s | +150-300 |
500K 호출/월 분류 작업 기준 추정: Gemini Flash Lite ~$30 < GPT-5.4-mini ~$85 < Claude Haiku ~$210 < Gemini 3 Pro ~$600 < Claude Opus ~$3,000. Structured output 은 schema 토큰을 input 에 더하지만 output 토큰을 줄여 총 cost 는 보통 free-form 대비 동등 또는 -10%.
evidence_quote 강제 → hallucination 거의 0. 2026 cookbook 표준.thinking_budget (Gemini 3) / reasoning_effort (GPT-5) / thinking (Claude 4.7) — 모델 내부 vs schema reasoning 둘 다 활용.핵심 단위는 campaign + subsequence. campaign 활성화 시 lead enrollment → step 발송 후 reply 도착 → AI categorizer 호출 → 카테고리에 매칭되는 subsequence 의 trigger 와 매치 시 lead 가 subsequence 로 이동, parent 의 남은 step 영구 skip. Lifecycle: active → paused (on reply) → categorized → branched → completed | bounced | unsubscribed | OOO_rescheduled.
Form-driven 빌더 (드래그앤드롭 아님). Subsequence 는 별도 탭: Campaign > Subsequence > Add > Choose Condition > Choose Lead Status > Set Delay > Write Copy. Trigger 4종: Lead Categories (AI 분류), Text Content (reply 본문 키워드), Re-Engagement, Bounced Leads. delay 는 카테고리 부여 후 N일/시간. multichannel 없음 — email-only.
campaign(id, name, status, schedule, settings_jsonb) campaign_step(id, campaign_id, order, delay_minutes, subject, body, variants_jsonb) subsequence(id, parent_campaign_id, name, condition_events jsonb, delay_minutes) subsequence_step(...) lead_category(id, campaign_id, name, prompt_text) // 사용자 정의 카테고리 + AI prompt campaign_lead( id, campaign_id, lead_id, status, current_step_id, current_subsequence_id, category_id, ooo_return_date, next_send_at) reply_event(id, campaign_lead_id, received_at, body, ai_category_id, ai_confidence) send_event(id, campaign_lead_id, step_id, sent_at, opened_at, clicked_at, bounced_at)
condition_events jsonb 예: [{type:"category",category_id:42}, {type:"text",keyword:"pricing"}, {type:"bounce"}, {type:"reengage"}]. API: POST /api/v1/campaigns/create-subsequence.
{category, confidence, ooo_return_date?} 강제 추정.이벤트 기반 + 즉시 평가. step 발송 후 IMAP/Microsoft Graph webhook 으로 reply 도착 → AI categorizer 동기 호출 → 카테고리 부여 즉시 모든 active subsequence 의 condition_events 순회 평가 → 첫 매칭 wins. "5일 안 열면 분기" 같은 시간 조건은 1st-class 가 아니라 delay 필드로 — subsequence trigger 매칭 후 N일 wait 한 뒤 첫 step 발송.
| 플랜 | 가격 | 분기/AI |
|---|---|---|
| Base | $39/mo | subsequence O, AI 분류 X |
| Pro | $94/mo | + default AI 5 카테고리, API/webhook |
| Unlimited Smart | $174/mo | + 무제한 |
| Unlimited Prime | $379/mo | + 12M leads, dedicated IP |
차별점: 분기 trigger 의 1st-class 요소가 "AI 가 분류한 reply 의미" 라는 점 — open/click 이 아니라 reply intent 가 라우팅 키.
모델은 sequence (multichannel) + step (8 type) 의 그래프. type: email | linkedIn | call | sms | whatsApp | zapier | task | condition. condition step 이 분기 노드 — 이전 결과(open/reply/click/connect) 를 condition.property + rules 로 평가, Yes branch / No branch 두 자식 step 으로 갈라짐 (parentId + ifConditionPositive).
condition step 은 별도 waitInMinutes 로 "최대 N 분 기다린 뒤 평가" 가능 — 시간 만료시 No 분기로.
드래그앤드롭 시퀀스 캔버스. 좌측 sidebar 에서 step type drag → drop. condition step 떨어뜨리면 Yes/No 두 slot 자동 생성. condition 모달: "If the contact has..." dropdown (Opened email/Clicked link/Replied/Connected/Accepted invitation/Has phone/Email valid 등) → operator (is/isNot/contains/isSet/isNotSet) → value. wait timeout 별도 필드. multichannel 분기 예시: Email → if not opened 3d → LinkedIn connect → if accepted → LinkedIn msg → if no reply → Call task. step variant (A/B) 도 지원.
sequence_step( id, sequence_id, parent_id, // DAG 자기참조 if_condition_positive bool, // 부모가 condition 일 때 Yes/No type enum('email','linkedIn','call','sms','whatsApp','zapier','task','condition'), action_type, // linkedIn: message|connect|inMail|... delay_minutes int, wait_minutes int, // condition only execution_mode enum('automatic','manual'), payload_jsonb // variants / message / conditions[] ) contact_event(id, contact_id, sequence_id, step_id, type, occurred_at) // type: email_opened, email_clicked, email_replied, // linkedin_accepted, linkedin_replied, call_completed, ...
condition payload_jsonb: {conditions:[{property:"emailOpened",rules:[{operator:"isSet"}]}]}. property 는 GET /v3/sequences/steps/contact-filter-properties 로 동적 조회.
{intent_category, confidence, suggested_reply, scheduling_intent, recommended_action} 강제. Anthropic-style tool use 로 book_meeting / move_to_sequence / mark_unsubscribe 함수 호출 추정.condition step 에 도달한 prospect 는 next_check_at = now + 10 min 으로 큐에 들어가고 워커가 그때마다 contact_event 조회. 조건 만족 → Yes 분기, wait_minutes 만료 → No 분기. atomic claim 은 sequence_contact.next_action_at < now FOR UPDATE SKIP LOCKED 패턴 추정. DAG 분기 깊이 사실상 무제한 (condition step 이 자식 condition 가능).
rules 배열 + operator combination 으로 표현.| 플랜 | 가격 | 분기/AI |
|---|---|---|
| Email Volume | $49-159/mo | 이메일 전용, 분기 O |
| Multichannel | $89-99/user/mo | + LinkedIn/Call/SMS/WhatsApp 분기 (LinkedIn $69, Call $29 부가) |
| Jason AI Starter | $500/mo | 1,000 active contacts, AI autonomous |
| Jason AI Growth | $1,500/mo | 확장 |
차별점: 8 종 step type 의 진짜 multichannel (email + LinkedIn + call + SMS + WhatsApp) 을 DAG 분기로 묶을 수 있는 유일한 sequence builder + LLM 백엔드 사용자 선택권.
sequence + subsequence 모델. parent 는 선형 step. subsequence 진입 시 parent 의 남은 step 영구 skip. 특이점: 각 subsequence step 마다 condition 재평가 — 더이상 매칭 안 되면 status=Finished. lifecycle: Active → InSubsequence (immediate) → Completed | Finished | Replied | Bounced | Unsubscribed.
선형 form 빌더. parent step 추가 → Subsequence 버튼 활성화 → trigger 조건 모달 → AND 로 묶인 condition 빌더 (한 줄당 name dropdown + operation dropdown + value input, + 버튼으로 multi-row). condition name: Replied / Open Counts / Clicked URL / Specific Outcome / Steps Completed / Prospect Tag / Reply Keywords.
subsequence( id, parent_sequence_id, title, schedule_id, first_step_relative_days, status enum('draft','active','paused') ) subsequence_condition( id, subsequence_id, name enum('Replied','Email Open Count','Link Click', 'Outcome','Tag','Step Completed','Reply Keyword'), operation enum('is','is not','is any','greater than',...), value text|int|bool, condition_id text // API 의 conditionId, 클라이언트 식별자 )
API: POST /v1/sequences/{sequenceId}/subsequence body = {title, scheduleId, firstStepRelativeDays, conditions:[{name,operation,value,conditionId}]}. 모든 condition 이 AND. value 타입은 server-side validation.
3 사 중 AI 비중 가장 약함. "Reply Keywords" condition 도 단순 substring/regex. Outcome 은 사용자가 master inbox 에서 수동 태깅. OOO 자동 파싱·복귀일 reschedule 같은 기능 미지원. → 린다가 Saleshandy 보다 더 잘할 수 있는 가장 큰 영역.
공식: "Prospects move into a subsequence immediately after the conditions are met; there is no delay." 이벤트 발생 시점에 AND-매트릭스 평가, 매칭 시 즉시 이동. 핵심 특이점: step 진입 직전에 conditions 를 다시 체크해 더이상 매칭 안 되면 Finished (implicit dynamic guard).
Outreach Starter ~$36 (분기 X) · Pro $69-74 (분기 O) · Scale $149+. AI 호출 없으므로 BYOK 없음.
차별점: AND-only 명시적·예측가능한 condition matrix + 매 step condition 재평가 (rule-based guard) — AI 의존 없이 결정론적 분기.
핵심 통찰: Outreach 는 "branch 가 sequence 안에 있지 않다". Sequence 자체는 linear. 분기는 외부 Trigger 가 이벤트 받아 "현재 시퀀스를 finish 시키고 새 시퀀스로 옮긴다" 는 sequence swap 방식. 이게 mid-flight rewrite 의 토대.
trigger(id, name, event_type, event_state, enabled, owner_id) trigger_condition(id, trigger_id, branch_index NULL=main, expr_tree_jsonb) trigger_action(id, trigger_id, branch_index, action_type, params_jsonb, order) event_log(id, prospect_id, event_type, payload_jsonb, occurred_at) // append-only 스트림 — trigger evaluator 의 입력 agent_run(id, agent_type, target_id, source_type=internal|web, input_filter_jsonb, status, started_at, finished_at) agent_output(id, agent_run_id, field_name, value_text, citations_jsonb)
핵심: trigger_condition.expr_tree_jsonb 가 AST 로 저장 ({op:"AND",children:[{field:"title",op:"contains",value:"VP"},...]}). 30 branch × 평균 5 조건 = 150 row/trigger.
Outreach 에는 공식 "sequence rewrite agent" 가 없다. 대신 패턴은 — Research Agent 가 custom field 업데이트 → Trigger 가 field changed event 받음 → Finish Sequence + Add to Sequence (new) action → de-facto rewrite. 즉 LLM 이 sequence 의 step 을 바꾸는 게 아니라 시퀀스를 교체한다.
Trigger evaluator: 이벤트 기반 async. 모든 mutation 이 event_log append → consumer 가 enabled trigger fan-out 평가. Per-trigger 평가는 single-thread (race 방지). Branch 30개는 한 transaction 안에서 평가 후 action 일괄 enqueue.
Mid-flight rewrite atomic: sequence swap 은 state.status=finished(trigger_swap) + INSERT new sequence_state(step=0) 두 step. 같은 transaction. 진행 중 step 이 send 큐에 이미 enqueue 됐다면 send-time 에 state.status==active 재확인 (claim 패턴) 으로 fire-after-finish 방지. 이게 "in-flight enrollment 안전 swap" 의 mechanism.
agent_run + event_log + trigger.fired_history 세 셋이 trail.pause until return_date. 그 사이 trigger fire 해도 paused 상태에선 step send 안 됨.Standard ~$100/seat/mo · Professional $120-170 · Enterprise $160+. 최소 10-15 seat, annual contract, $5K-25K implementation fee. AI agents 는 Enterprise 또는 별도 SKU.
차별점: "Sequence 안에 분기 두지 말고, 외부 Trigger 가 sequence 전체를 swap 하는 event-driven 아키텍처 — 30-branch trigger × per-sequence Ruleset × AI custom-field-writer" 의 조합.
핵심: Conductor AI (Buyer Priority Model) 가 매 signal 마다 실시간 task ranking 재계산. patent-pending 자체 엔진. Cadence 안에 분기 거의 없음 — 모든 분기 로직은 Plays 와 Conductor ranking 으로 대체.
signal(id, person_id|account_id, type, source, strength, payload_jsonb, occurred_at) // partition by occurred_at, retention 30-90d bpm_score(id, person_id, score float, top_signals jsonb, rationale text, computed_at, valid_until) // recomputed on signal change (incremental) rhythm_action(id, person_id, action_type, priority_score, reason_text, ranking_signal_id, expires_at, assigned_user_id, completed_at) // ephemeral task queue per user play(id, name, event_type, conditions_jsonb, action_jsonb) // conditions can reference BPM score / signal types
핵심: bpm_score.rationale 가 자연어로 저장돼 UI "왜" 설명에 그대로 사용. signal 테이블 partition + 짧은 retention — Conductor 가 실시간 re-rank 위해 hot 데이터만 필요.
Conductor 실행 주기: real-time, event-driven. Signal stream (Kafka-like) → BPM scorer → affected user 의 Rhythm queue 업데이트. UI 는 폴링/SSE 로 reload. Race: Rhythm action 은 ephemeral, expires_at 으로 stale 자동 drop. 동시 enqueue dedup: (person_id + action_type + signal_id) UNIQUE.
승인 vs 자동: Conductor 는 ranking 만, action 실행은 항상 user click. Plays 의 일부 action 만 auto-execute (canned email, log activity). AI 는 추천기, 사용자가 executor — Outreach 보다 보수적.
bpm_score.top_signals + rationale 가 UI 노출. Dismiss/완료 모두 로그.Essentials / Advanced / Premier. Median $125-165/seat/mo. Dialer add-on $200-480/yr/user. Conversations·Rhythm·Deals 모두 별도 SKU. Conductor AI 는 Rhythm SKU 포함.
차별점: "Cadence 안에 분기 두지 않고, 7일 prediction BPM 이 매 signal 마다 실시간 task ranking 재계산 — AI ranker 가 cadence orchestrator 를 대체하는 구조."
cold email outbound 전문. Campaign (main sequence) + Subsequence (label/keyword-triggered branch) + Unibox V2 (AI-labeled inbox) + AI Reply Agent (HITL/Autopilot).
Campaign builder: 단순 step list (email only). Day delay + variant. 다중채널 분기 없음.
Subsequence wizard 4-step: ① main 활성화 (선행 조건) → ② Trigger 선택 (3 타입): Status (8 default + custom) / Activity (link click, completed without reply, opened) / Keyword/phrase (세미콜론 구분, 정확 매칭 권장) → ③ Step 작성 + delay → ④ Daily limit + schedule.
Unibox V2: GPT-4 기반 sentiment + auto label. Default 8개 + 무제한 custom. Custom label = 라벨명 + AI description 텍스트 (pencil icon). Misclassification feedback (robot icon).
AI Reply Agent: HITL (draft → 사람 승인 → send) 또는 Autopilot (auto-send, 모호하면 pause). 5-step setup, step 5 (agent training) 가 가장 중요 — tone, product, goal context. "Optimize" 버튼이 AI 가 guidance 자체를 개선 제안.
label(id, workspace_id, name, is_default, ai_description, created_by) // ai_description: GPT-4 prompt 의 한 줄 — few-shot 이 아닌 description-based zero-shot reply_event(id, lead_id, raw_body, parsed_body, detected_language, sentiment_score, assigned_label_id, ai_confidence, received_at) subsequence(id, parent_campaign_id, status, trigger_type, trigger_config_jsonb) // trigger_config: {label_id} | {keywords:[]} | {activity:"link_click"} agent_config(id, workspace_id, mode=HITL|autopilot, training_context_text, optimize_iterations, label_filters_jsonb) agent_draft(id, reply_event_id, draft_body, status=pending|approved|sent, credits_used, created_at)
agent_config.training_context_text 가 system prompt 의 일부로 injection. "Optimize" 는 meta-LLM 호출 — 기존 training text + 최근 misclassification 을 보고 새 text 제안.Reply pipeline: IMAP/webhook → reply_event 생성 → NLP 큐 (async <5분) → GPT-4 호출 → label 저장 → lead.current_label 업데이트 → subsequence trigger evaluator. 중요: 기존 매칭 lead 자동 진입 안 함 — 새로 라벨 받은 lead 만 (forward-only, race 회피).
Atomic claim: subsequence_state UNIQUE(lead_id, subsequence_id). HITL vs Autopilot: agent_config.mode + per-label override. Autopilot 이어도 confidence < threshold 면 HITL 폴백.
Growth $37 / Hypergrowth $77.60 (annual) / Light Speed $286.30 / Enterprise quote. Seat 기반 X — send volume + feature tier 기반. AI Reply Agent 5 credit/reply, $9/150~$197/10K. AI Custom Label 은 Hyper Growth / Light Speed 한정. Unibox NLP 기본 라벨링은 모든 plan 무료.
차별점: cold-email-only narrow scope + GPT-4 기반 50+ language 라벨링 + label/keyword/activity trigger subsequence 가 default — branch UI 가 곧 'label-driven subsequence'인 가장 단순한 아키텍처.
| 영역 | Smartlead | Reply.io | Saleshandy | Outreach | Salesloft | Instantly |
|---|---|---|---|---|---|---|
| 분기 모델 | Parent + Subseq | DAG of typed steps | Parent + Subseq (매 step 재평가) | Linear + External Trigger | 없음 (AI ranking 대체) | Parent + Subseq |
| Sequence 안 분기 | 없음 | condition step (Yes/No) | 없음 | 없음 (linear) | 없음 (linear) | 없음 (linear) |
| 분기 발화 위치 | Subseq trigger 평가 | Condition step 10분 polling | 이벤트 즉시 + step 재평가 | Trigger evaluator (외부) | Plays + BPM ranking | Subsequence trigger |
| LLM 모델 | BYOK GPT-4 | Claude/Gemini/Mistral/GPT 선택 | 없음 (rule-based) | 자체 + Gemini (grounding) | 자체 BPM + LLM (assistant) | GPT-4 (Unibox) |
| OOO 자동 처리 | AI 복귀일 파싱 + reschedule | Jason AI 필요 | 미지원 (수동) | Ruleset OOO section | cadence pause + demote | NLP 가 라벨 + scheduled_send |
| 다채널 | Email only | 8 step type | Email + LinkedIn task | Email/Call/LinkedIn/SMS/Task | Email/Call/LinkedIn/SMS | Email only |
| 가격 (entry) | $39/mo | $49-99/user/mo | $69-74/mo (Pro) | $100-175/seat/mo | $125-165/seat/mo | $37-286/mo (volume) |
| 차별점 | AI reply intent 가 라우팅 1st-class | 진짜 multichannel DAG + LLM 선택권 | 결정론적 rule + 매 step 재평가 | Sequence swap (event-driven) | AI ranker 가 orchestrator 대체 | 50+ language 무료 라벨링 |
Outreach 의 검증된 패턴. 필요한 것: (a) event_log append-only 스트림, (b) trigger evaluator (event_type 인덱스), (c) AST 형태의 trigger_condition.expr_tree_jsonb, (d) 30 branch 제약은 LLM 비용 한계가 아니라 evaluator latency 한계 — 린다도 비슷한 상한 필요. AI 가 sequence 직접 재작성하지 않고, AI 가 custom_field 채우고 trigger 가 그 변화 보고 시퀀스 swap 이 핵심 분리.
Salesloft Conductor 가 LLM 이 아닌 자체 ML 인 이유: ranking 은 매 signal arrival 마다 incremental 재계산 필요. LLM 비용·latency 부적합. 린다 도입 시: (a) signal table partitioned + 30-90d retention, (b) 가벼운 gradient boosting / logistic 모델 (린다 데이터 적으면 rule-based + learn-to-rank 부트스트랩), (c) rationale 생성만 LLM 으로 — score 의 top-k feature 를 자연어로 풀어주는 별도 step. UI 는 score+rationale 같이 노출 (explainability 가 채택률 핵심).
Instantly 가 무료 제공하는 게 GPT-4 native multilingual + zero-shot classification 덕분. 린다: (a) label.ai_description 텍스트만 user 작성, (b) 모든 라벨 정의를 system prompt 에 inject, (c) reply body + label list → structured JSON (label_id, confidence, sentiment, language, return_date). 비용은 input 토큰 dominant — 라벨 정의는 prompt cache 로 절감. Misclassification feedback 은 별도 label_feedback 테이블 저장 → 라벨 description 자동 개선 ("Optimize" 패턴).
Instantly Reply Agent 가 Autopilot 이어도 uncertain 이면 pause. 린다: agent_config.mode + per_label_threshold + global_confidence_floor 3-layer. Audit 은 agent_draft 보존 + credits_used + status. 5 credit/reply 의 pricing 신호 — Instantly 는 LLM cost 를 user 에게 직접 transfer. 린다도 LLM 호출량은 워크스페이스 단위 metering 필요.
Outreach 가 "30 branch action 결과를 다음 branch 입력으로 못 씀" 으로 제약 명시한 이유: branch 평가가 동시·일괄이 아니면 일관성 깨짐. 린다: (a) event_log consumer 가 per-trigger single-threaded 처리 (Redis stream consumer group), (b) action enqueue 시 (person_id, sequence_id, version) dedup, (c) sequence step send 직전에 state.status==active && version 일치 재확인 (claim 패턴). 답장 도착 같은 strong signal 은 synchronous fast path 로 별도 처리해 race 회피.
LLM 호출은 이미 callAI / callAIObject / callAIWithRetry 통합 진입점으로 정리되어 있다.
Gemini 카테고리 분리 (5개 키), 자동 토큰 추적 (ai_token_usage_logs), Zod 통합까지.
callAI() — 텍스트 (Google/OpenAI/Anthropic/Perplexity)callAIObject() — 구조화 (Zod schema, Gemini structuredOutputs, OpenAI/Anthropic response_format 자동 처리)callAIWithRetry() — fallback chain 지원trackTokenUsage() → ai_token_usage_logsgetGeminiClient(category) — 토큰 추적 Proxy 자동 적용parseJsonArray<T>(), safeJsonParse<T>()callClaude(systemPrompt, userPrompt, maxTokens)| 파일 | 함수 | 모델 | 현재 파싱 | Schema 후보 |
|---|---|---|---|---|
services/ai-classification.service.ts:49-55 |
classifyReply() | gemini-3.1-flash-lite | 수동 JSON.parse | intent/sentiment/confidence enum |
workers/bullmq/reply-tags-auto-classify.worker.ts:54-70 |
defaultGemini() | gemini-3-flash | responseMimeType + responseSchema ✓ | matched_tag_names (이미 enum) |
workers/bullmq/crm-stage-classify.worker.ts → services/crm/stage-classifier.service.ts:163+ |
classifyThread() | claude-sonnet-4-6 | callAIObject + Zod ✓ | deal stage enum (이미 정의) |
| 파일 | 함수 | 모델 | 현재 파싱 |
|---|---|---|---|
services/crm/stage-classifier.service.ts:97-114 |
llmOutputSchema (Zod) | claude-sonnet-4-6 | callAIObject + Zod ✓ (assigned_stage, confidence, signals, extraction_json) |
skills/email-hunt/lib/company-emails.ts |
generateObject() | - | PrioritizedUrlsSchema |
| 파일 | 함수 | 모델 | 현재 파싱 |
|---|---|---|---|
services/sequence-proposal/generate-batch-emails.service.ts:1-80 |
generateBatchEmails() | gemini-3-flash-lite | callAIObject + Zod ✓ ({subject, bodyText}[]) |
lib/claude.ts:26-78 |
callClaude() | claude-opus-4-6 | text content parsing (Blog CMS) |
| 파일 | 함수 | 모델 | 현재 파싱 |
|---|---|---|---|
services/hot-lead-evaluation/triage.service.ts:54-83 |
triageBatch() | gemini-3-flash-preview | callAIObject + Zod ✓ (batch 20명/콜) |
services/lead-scoring.service.ts:60-68 |
LeadScoreSchema | gpt-5-mini | Zod 정의됐으나 callAI 텍스트 사용 |
services/scoring-preset-gemini.ts:56-115 |
callGeminiForPreset() | gemini-3.1-flash-lite | 수동 fetch + JSON.parse |
callAIObject() 패턴 (ai-sdk generateObject, Zod schema, Gemini structuredOutputs:true):
services/crm/stage-classifier.service.ts — llmOutputSchema (Zod)services/hot-lead-evaluation/triage.service.ts — evaluationSchema (Zod)services/sequence-proposal/generate-batch-emails.service.ts — BatchEmailSchemaresponseMimeType + responseSchema:
workers/bullmq/reply-tags-auto-classify.worker.ts — matched_tag_names (Google Type enum)| 전략 | 예시 | 코드 경로 |
|---|---|---|
| Zod 스키마 검증 | stage-classifier, triage, lead-scoring | callAIObject 자동 파싱 + Zod validate |
| 수동 JSON 파싱 + 폴백 | ai-classification, scoring-preset | try-catch + safeJsonParse() → fallback |
| 후보 set 필터링 (hallucination 방어) | reply-tags-auto-classify | LLM matched_tags ∩ active_tags |
| CRM stage 신호 floor 강제 | stage-classifier | LLM assignedStage ≥ signal-engine floor |
| Retry with fallback chain | callAIWithRetry() | primary → fallback → error |
현재 Gemini responseMimeType + responseSchema (Google Type enum) 사용 중. callAIObject() 로 마이그레이션 → Zod schema 통일 + ai-sdk 자동 처리. 코드 간결화, 향후 모델 전환 유연성.
streamObject() 로 TTFB 3.7× 향상 (bench-stream-vs-generate.ts 실측). 사용자 체감 속도 +300%, 동일 비용.
callAIObject 사용, 배치 20명/콜, 비용 추적 완벽. Stage 2 로 임베딩 재순위화 추가 고려.
callAIObject + Zod + Claude Sonnet 4.6 안정. 향후 confidence threshold 통합만 (currently logged but not gated).
현재 callAI 텍스트 → 수동 JSON.parse. 파싱 버그 제거, 타입 안전성 +100%, 캐시 hit rate 개선.
C2 행동 분기 구현 시 LLM 호출이 필요한 4 지점 + 각 지점의 Schema·모델·검증 매핑.
| LLM 사용 지점 | 모델 | Schema 핵심 필드 | Provider | 린다 파일 |
|---|---|---|---|---|
| ① 답장 intent 분류 (분기 라우팅의 키) | Gemini 3 Flash Lite | reasoning, intent (7 enum), sentiment, urgency, suggested_followup_days, confidence | responseSchema | services/ai-classification.service.ts 확장 |
| ② OOO 복귀일 추출 | Gemini 3 Flash | reasoning, is_ooo, return_date (nullable ISO), evidence_quote, alternate_contact_email, confidence | responseSchema | utils/auto-reply-detection.ts 신규 함수 |
| ③ 시퀀스 카피 자동 생성 (분기 step 추가 시) | Gemini 3 Flash | 본문 = plain text + 메타 (subject, primary_cta, tone) | callAIObject (메타만) | sequence-ai-gen.service.ts 확장 |
| ④ Subsequence 라우팅 추천 (AI fallback) | Gemini 3 Flash | recommended_subsequence_id, reasoning, alternatives[], confidence | callAIObject | services/sequence-branching.service.ts 신규 |
| LLM 사용 지점 | 모델 | Schema 핵심 필드 | Provider | 린다 파일 |
|---|---|---|---|---|
| ① 거절 패턴 batch 추출 (주 1회) | GPT-5.4-mini strict | reasoning, rejection_category (8 enum), objection_phrase, rebuttal_angle, is_recoverable | response_format strict | objection-pattern-extractor.worker 신규 |
| ② 거절 패턴 자연어 요약 | Gemini 3 Flash | pattern_summary, frequency, top_phrases[], avoidance_suggestion | responseSchema | 같은 워커 내부 후속 호출 |
| ③ 회신율 TOP 다이제스트 인사이트 생성 | Gemini 3 Flash Lite | weekly_summary, insights[], action_items[] | responseSchema | weekly-digest-data.service.ts 확장 |
| ④ Sentiment → Lead Score rationale | Gemini 3 Flash Lite (선택) | score_delta, reasoning (UI 노출용) | responseSchema | sentiment-score-sync.worker 신규 |
| ⑤ Mid-flight rewrite (Phase 4) | Gemini 3 Pro thinking + Claude 4.7 thinking | plan (sequence swap), rewrite_subject, rewrite_body, justification, citations[] | responseSchema + tool use | 신규 sequence-rewrite.service.ts |
| 결정 | 참조 SaaS | 이유 |
|---|---|---|
| 분기 모델 = parent + subsequence (DAG X) | Smartlead · Saleshandy | UI 복잡도 ↓, 실무 운영 5단계 이상 가독성 무너짐. DAG 는 Phase 4 검토. |
| 분기 평가 = 이벤트 즉시 + 시간조건은 delayed job | Saleshandy 즉시 + Reply.io polling 절충 | 이벤트 즉시가 race 가장 적음. 시간조건만 BullMQ delayed. |
| OOO = LLM 복귀일 파싱 + 자동 재개 | Smartlead · Saleshandy · Hunter | 시장 표준. 린다 baseline parity. |
| Reply intent 분류 = Gemini 3 Flash Lite + Zod (린다 default) | Instantly Unibox V2 | 저가 + 50+ language native. responseSchema 통일. |
| 거절 패턴 학습 = batch 워커 + AI 추천, HITL approve | Outreach Research Agent + Salesloft active learning | 완전 자동 rewrite 는 위험. 사람 승인 게이트가 ROI 핵심. |
| Mid-flight rewrite = sequence swap (in-place edit X) | Outreach 검증 패턴 | Atomic 안전. AI 가 custom_field 채우고 trigger 가 swap. |
| HITL/Autopilot = confidence threshold spectrum | Instantly Reply Agent | binary 가 아니라 per-label threshold + global floor. |
| Explainability = LLM rationale 필드 + UI 노출 | Salesloft Rhythm bpm_score.rationale | 채택률 핵심. dismiss feedback 도 학습 시그널로. |
| 작업 | 모델 | 호출 형태 | 검증 |
|---|---|---|---|
| 답장 intent 분류 (7 cat) | Gemini 3 Flash Lite | responseSchema enum + reasoning + confidence | Zod, confidence<0.7 → Flash 로 retry |
| OOO 복귀일 파싱 | Gemini 3 Flash Preview | responseSchema + nullable + evidence_quote | Zod date parse, 미래 6개월 내 sanity check |
| 거절 사유 batch 라벨링 (오프라인) | GPT-5.4-mini strict | json_schema strict | Zod / Pydantic |
| 시퀀스 카피 rewrite | Gemini 3 Flash Preview | plain text + 메타 호출 (subject, cta) 분리 | tone classifier 후처리 |
| 고난도 reasoning (전략 분석) | Gemini 3 Pro + thinking_budget | responseSchema + plan field | 사람 검수 큐 |
| Fallback (Gemini 장애) | Claude Haiku 4.5 tool use strict | tool_choice forced | 동일 Zod |
reasoning → 데이터 필드 → confidence 순.callAIObject() 단일 entry point (provider swap 1줄).elysia-server/src/llm/schemas/ 모듈 SSOT (CLAUDE.md 원칙).| 노드 | 크기 | 작업 | 새 파일 / 수정 파일 |
|---|---|---|---|
| N1.1 [S] | S | llm/schemas SSOT 디렉토리 생성 + reply-intent, ooo-return-date, rejection-pattern, lead-score Zod 정의 | 신규 elysia-server/src/llm/schemas/*.ts 5 파일 |
| N1.2 [M] | M | ai-classification.service.ts → callAIObject 전환 + ReplyIntentSchema 사용 | services/ai-classification.service.ts |
| N1.3 [M] | M | parseOOOReturnDate 신규 함수 + Zod schema + Gemini 3 Flash 호출 | utils/auto-reply-detection.ts 함수 추가 |
| N1.4 [S] | S | email_replies.resume_at 컬럼 추가 migration | drizzle migration + db/schema/emails.ts |
| N1.5 [M] | M | enrollment-auto-resume.worker 신규 (1hr cron) | 신규 워커 |
| N1.6 [L] | L | sequence-branching.service 신규 + branch-eval.worker + sequenceSteps 컬럼 추가 | 신규 service + worker + migration |
| N1.7 [M] | M | weekly-digest 확장 — 회신율 TOP / 거절 TOP / intent 분포 + LLM rationale | weekly-digest-data.service.ts 확장 |
| N1.8 [L] | L | objection-pattern-extractor.worker + objection_patterns 테이블 + admin Insights UI | 신규 워커 + migration + admin page |
| N1.9 [M] | M | sentiment-score-sync.worker — Sentiment → leadScore 피드백 | 신규 워커 |
| N1.10 [L] | L | Phase 4: sequence-rewrite.service (Outreach 패턴) + trigger field_changed evaluator | 신규 service + trigger 엔진 |
elysia-server/src/llm/schemas/ 디렉토리 신설, FE 도 re-export 로 SSOT 통일.