FUTURE_CAR SPEC OC - zero1ne-26/doc GitHub Wiki

[OC ๊ตฌํ˜„ ์ŠคํŽ™] ๋ฏธ๋ž˜์ฐจ ์—ฐ๋™ โ€” ์„ธ์…˜ ๋ชจ๋“œ ยท passthrough

  • ์ž‘์„ฑ: ๋ฏธ๋ž˜์ฐจ ํŒ€, 2026-08-23 ยท ๋Œ€์ƒ ์ฝ”๋“œ: orchestration-server 64065c7
  • ๋ฏธ๋ž˜์ฐจ๊ฐ€ OC ์— ์š”์ฒญํ•˜๋Š” ๋ณ€๊ฒฝ์€ 3 ์ ˆ(์„ธ์…˜ ๋ชจ๋“œ) ํ•˜๋‚˜๋‹ค. 2 ์ ˆ์€ OC ์ž‘์—…์ด ์—†๊ณ  "๋™์˜"๋งŒ ํ•„์š”ํ•˜๋‹ค. 1 ์ ˆ์€ ์ด๋ฏธ ๋™์ž‘ ์ค‘์ธ ๊ฒƒ(์ฐธ๊ณ ).
  • ๊ณ„์•ฝ ์ •๋ณธ: ์œ„ํ‚ค MQTT_SCHEMA-FUTURE_CAR ยง3-2-1

1. ์ง€๊ธˆ ์ด๋ฏธ ๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ (OC ๋ณ€๊ฒฝ ์—†์Œ)

๋ฐฉํ–ฅ ํ† ํ”ฝ OC ์ฝ”๋“œ ๋น„๊ณ 
OC โ†’ ๋ฏธ๋ž˜์ฐจ future-car/request type=plan (retained) mqttx/future_vehicle.go PublishFutureVehiclePlan stops[] = {id, kind, place, contexts}. ๋ฏธ๋ž˜์ฐจ๋Š” stops ๋ฅผ ์žฅ์†Œ ์นดํƒˆ๋กœ๊ทธ๋กœ๋งŒ ์“ด๋‹ค
OC โ†’ ๋ฏธ๋ž˜์ฐจ dashboard (retained) mqttx/location_tasks.go ์ฐจ ์žฅ์†Œ์˜ tasks[] ๋ฅผ ์—ฌ์ • ํƒœ์Šคํฌ๋กœ ์“ด๋‹ค. status ๋กœ ์™„๋ฃŒ ์—ฌ๋ถ€ ํŒ๋‹จ
OC โ†’ ๋ฏธ๋ž˜์ฐจ me (retained) mqttx/create_persona.go PersonaDisplayPayload.ai_companion.prompt ์Šคํ… ์ƒ์„ฑ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์•ž์— ๋ถ™์ธ๋‹ค
OC โ†’ ๋ฏธ๋ž˜์ฐจ ai/response mqttx/ai_chat.go transcript = ๊ด€๋žŒ๊ฐ ๋ฐœํ™”. ๋ฏธ๋ž˜์ฐจ๊ฐ€ ๊ตฌ๋…ํ•ด ์Šคํ… ์ƒ์„ฑ ์žฌ๋ฃŒ๋กœ ์“ด๋‹ค
๋ฏธ๋ž˜์ฐจ โ†’ ํƒœ๋ธ”๋ฆฟ ai/response {ok, context_id, message} ยท ai/suggestions (OC ์ฝ”๋“œ ์—†์Œ) ๋ฏธ๋ž˜์ฐจ๊ฐ€ ์ง์ ‘ ๋ฐœํ–‰ํ•œ๋‹ค(2์žฅ). OC ๋Š” ๊ตฌ๋…ํ•˜์ง€ ์•Š๋Š” ํ† ํ”ฝ์ด๋ผ ์˜ํ–ฅ ์—†์Œ
๋ฏธ๋ž˜์ฐจ โ†’ OC ai/mode (retained) ์‹ ๊ทœ โ€” 3์žฅ {"mode":"car","ttl_sec":1800} / {"mode":"chat"} ๋ฅผ ์ด๋ฏธ ๋ฐœํ–‰ ์ค‘(OC ๊ฐ€ ๊ตฌ๋…ํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” retained ๋กœ ๋‚จ์„ ๋ฟ)
๋ฏธ๋ž˜์ฐจ โ†’ OC task/complete {task_id} mqttx/task_complete.go ํƒœ๋ธ”๋ฆฟ๊ณผ ๋™์ผ ํŽ˜์ด๋กœ๋“œ. OC ๊ฐ€ dashboardยทpoint ์žฌ๋ฐœํ–‰

2. Aโ€ฒ โ€” ๋ฏธ๋ž˜์ฐจ ๋ฌธ์žฅ์€ ๋ฏธ๋ž˜์ฐจ๊ฐ€ ai/response ์— ์ง์ ‘ ์“ด๋‹ค (OC ์ž‘์—… ์—†์Œ, ๋™์˜๋งŒ)

๋ฏธ๋ž˜์ฐจ๊ฐ€ ํƒœ๋ธ”๋ฆฟ ์ฑ„ํŒ…์— ๋„์šฐ๋Š” ๋ฌธ์žฅ("์ถœ๋ฐœ ํ• ๊นŒ์š”?", ๊ฐ step ์˜ ์งˆ๋ฌธ)์€ LLM ๋‹ต๋ณ€์ด ์•„๋‹ˆ๋ผ ์ •ํ•ด์ง„ ๋ฌธ์žฅ์ด๋‹ค. OC ๋ฅผ ๊ฑฐ์น˜๋ฉด LLM ์ด ๋ฌธ์žฅ์„ ๋ฐ”๊พธ๊ณ , ์ง€์‹œ๋ฌธ์ด ๋Œ€ํ™” ๊ธฐ๋ก์— ๋‚จ๊ณ , ์„ธ์…˜๋‹น 1๊ฑด ๋ฝ์— ์šฐ๋ฆฌ ์š”์ฒญ์ด ์กฐ์šฉํžˆ ๋“œ๋กญ๋๋‹ค. ๊ทธ๋ž˜์„œ ๋ฏธ๋ž˜์ฐจ๊ฐ€ orchestration/{sid}/ai/response ์— ์ง์ ‘ ๋ฐœํ–‰ํ•œ๋‹ค(๋ธŒ๋กœ์ปค ACL ์ƒ future-car ๊ณ„์ •์€ orchestration/# ์“ฐ๊ธฐ ๊ฐ€๋Šฅ).

๋ฏธ๋ž˜์ฐจ๊ฐ€ ๋ฐœํ–‰ ํŽ˜์ด๋กœ๋“œ ํƒœ๋ธ”๋ฆฟ ๋™์ž‘
ai/response { "ok": true, "context_id": "<๋ฏธ๋ž˜์ฐจ ์ƒ์„ฑ>", "message": "์ถœ๋ฐœ ํ• ๊นŒ์š”?" } chat-store.receiveAiResponse ๊ฐ€ ๋ชจ๋ฅด๋Š” context_id ์˜ message ๋ฅผ AI ๋งํ’์„ ์œผ๋กœ ์ถ”๊ฐ€ โ€” ์ด๋ฏธ ๊ทธ๋ ‡๊ฒŒ ๋™์ž‘
ai/suggestions { "context_id": "<๊ฐ™์€ ๊ฐ’>", "suggestions": ["๋„ค, ์ถœ๋ฐœํ•ด์š”!", "์ž ๊น๋งŒ์š”"] } ๋‹ต ํ›„๋ณด ์นฉ ํ‘œ์‹œ โ€” ์ด๋ฏธ ๊ทธ๋ ‡๊ฒŒ ๋™์ž‘. ํƒญํ•˜๋ฉด ai/request type=text ๋กœ ๋‚˜๊ฐ€ transcript ๊ฐ€ ๋œ๋‹ค

OC ๊ฐ€ ํ•  ์ผ์€ ์—†๋‹ค. ์ด ๋‘ ํ† ํ”ฝ์€ OC ๊ฐ€ ๊ตฌ๋…ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋‹ค๋งŒ ๊ณ„์•ฝ์ƒ OC ๊ฐ€ ๋ฐœํ–‰์ž์ธ ํ† ํ”ฝ์— ๋ฏธ๋ž˜์ฐจ๊ฐ€ ๊ฐ™์ด ์“ฐ๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋™์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค(AsyncAPI ๋ฌธ์„œยท๋ชจ๋‹ˆํ„ฐ๋ง ๊ด€์ ). ๊ฑฐ์ ˆํ•˜๋ฉด ๋Œ€์•ˆ์œผ๋กœ ai/request ์— passthrough: true (LLMยทํžˆ์Šคํ† ๋ฆฌยทsuggestions ์ƒ๋žตํ•˜๊ณ  text ๋ฅผ ๊ทธ๋Œ€๋กœ message ๋กœ)๋ฅผ ๊ตฌํ˜„ํ•ด ๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ•œ๋‹ค โ€” ๋ฏธ๋ž˜์ฐจ๋Š” env ํ•œ ์ค„ (OC_CHAT_PUBLISH=request)๋กœ ๊ทธ ๊ฒฝ๋กœ๋กœ ๋Œ์•„๊ฐ„๋‹ค.

๋ฏธ๋ž˜์ฐจ๊ฐ€ ์ง์ ‘ ์“ฐ๋Š” ๋ฉ”์‹œ์ง€๋Š” OC ์˜ ai_messages ํžˆ์Šคํ† ๋ฆฌ์— ๋‚จ์ง€ ์•Š๋Š”๋‹ค. ์—ฌ์ • ๋’ค ์ผ๋ฐ˜ ์ฑ„ํŒ…์—์„œ "์ฐจ๊ฐ€ ํ•œ ๋ง"์„ ๋งฅ๋ฝ์œผ๋กœ ์“ฐ๊ณ  ์‹ถ๋‹ค๋ฉด OC ๊ฐ€ ai/response ๋ฅผ ๊ตฌ๋…ํ•ด assistant ํ„ด์œผ๋กœ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค โ€” ์„ ํƒ.


3. ๋ณ€๊ฒฝ B โ€” ์„ธ์…˜ ๋ชจ๋“œ ai/mode (์—ฌ์ • ์ค‘ LLM ๋‹ต๋ณ€ ์ƒ๋žต)

3-1. ํ† ํ”ฝ

ํ•ญ๋ชฉ ๊ฐ’
ํ† ํ”ฝ orchestration/{sessionId}/ai/mode
๋ฐœํ–‰ ๋ฏธ๋ž˜์ฐจ (enter ์งํ›„ / exit ์งํ›„)
retain / QoS true / 1
ํŽ˜์ด๋กœ๋“œ {"mode":"car","ttl_sec":1800} ๋˜๋Š” {"mode":"chat"}

topics.go ์— ์ถ”๊ฐ€:

// AiModeTopic โ€” SUBSCRIBE orchestration/{sessionId}/ai/mode (retained, ๋ฏธ๋ž˜์ฐจ ๋ฐœํ–‰)
const AiModeTopic = "orchestration/+/ai/mode"
// AiModeTopicFmt โ€” PUBLISH(์‚ญ์ œ์šฉ) orchestration/{sessionId}/ai/mode
const AiModeTopicFmt = "orchestration/%s/ai/mode"

3-2. ์ƒํƒœ ๋ณด๊ด€ โ€” ์‹ ๊ทœ internal/mqttx/ai_mode.go

์„ธ์…˜๋ณ„ (mode, expiresAt). ๊ธฐ์กด SessionStore ์— ํ•„๋“œ๋ฅผ ์–น์–ด๋„ ๋˜์ง€๋งŒ, ๋ฏธ๋ž˜์ฐจ ์—ฌ์ •์€ ์„ธ์…˜๋ณด๋‹ค ์งง๊ณ  TTL ์ด ๋ณ„๊ฐœ๋ผ ๋ถ„๋ฆฌ๋ฅผ ๊ถŒํ•œ๋‹ค(TimedLock ๊ณผ ๊ฐ™์€ ๊ฒฐ์ธ ์ž‘์€ ๋งต).

type AiModeStore struct {
    mu      sync.RWMutex
    entries map[string]aiModeEntry   // sessionID โ†’ {mode, expiresAt}
}

func (s *AiModeStore) IsCar(sessionID string) bool   // mode=="car" && time.Now() < expiresAt
func (s *AiModeStore) Set(sessionID, mode string, ttl time.Duration)
func (s *AiModeStore) Clear(sessionID string)

func RegisterAiModeHandler(svc *Service, store *AiModeStore) error {
    return svc.Subscribe(AiModeTopic, 1, func(topic string, payload []byte) {
        // ๋นˆ payload = retained ์‚ญ์ œ โ†’ Clear
        // {"mode":"car","ttl_sec":N}  โ†’ Set(sid, "car", N์ดˆ; 0/๋ˆ„๋ฝ์ด๋ฉด ๊ธฐ๋ณธ 30๋ถ„, ์ƒํ•œ 2์‹œ๊ฐ„)
        // {"mode":"chat"}             โ†’ Clear
        // ๋ชจ๋ฅด๋Š” mode ๋Š” ๋ฌด์‹œ + ๋กœ๊ทธ
    })
}

server.go ์˜ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก ์ค„์— RegisterAiModeHandler(mqttService, aiModeStore) ์ถ”๊ฐ€. RegisterAiChatHandler ์— aiModeStore ์ธ์ž๋ฅผ ๋„˜๊ธด๋‹ค.

3-3. ai_chat.go ์˜ car ๋ชจ๋“œ ๋ถ„๊ธฐ โ€” ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ ์กฐ๋ฆฝ ์ „, LLM ํ˜ธ์ถœ ์ „

if aiModeStore.IsCar(sessionID) {
    // ๊ด€๋žŒ๊ฐ ๋ฐœํ™”: STT ๋งŒ ํ•˜๊ณ  LLM ๋‹ต๋ณ€ยทsuggestions ๋Š” ์ƒ๋žตํ•œ๋‹ค.
    var transcript string
    switch req.Type {
    case "voice":
        transcript = sttOnly(...)          // ๊ธฐ์กด voice ๊ฒฝ๋กœ์—์„œ STT ๊นŒ์ง€๋งŒ (ChatWithAudio ์˜ ์ „์‚ฌ ๋ถ€๋ถ„)
    case "text":
        transcript = req.Text
    }
    // user ํ„ด์€ ์ €์žฅํ•œ๋‹ค(์—ฌ์ • ๋’ค ์ผ๋ฐ˜ ์ฑ„ํŒ…ยท์š”์•ฝ์—์„œ ๋งฅ๋ฝ์œผ๋กœ ์“ฐ์ผ ์ˆ˜ ์žˆ์Œ) โ€” 6์žฅ ์—ด๋ฆฐ ์งˆ๋ฌธ
    _ = queries.InsertAiMessage(ctx, ...Role:"user", Transcript: transcript, Content: transcript)
    resp := AiChatResponse{OK: true, ContextID: req.ContextID, Transcript: transcript, Message: ""}
    publish(resp)
    return
}

โš ๏ธ voice ๊ฒฝ๋กœ ์ฃผ์˜: ํ˜„์žฌ STT ์™€ LLM ๋‹ต๋ณ€์ด llmClient.ChatWithAudio ํ•œ ํ˜ธ์ถœ์— ๋ฌถ์—ฌ ์žˆ๋‹ค (ai_chat.go voice case). car ๋ชจ๋“œ์—์„œ๋Š” ์ „์‚ฌ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ์ „์‚ฌ๋งŒ ํ•˜๋Š” ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”ํ•˜๋‹ค โ€” ChatWithAudio ์— "๋‹ต๋ณ€ ์ƒ๋žต" ์˜ต์…˜์„ ๋‘๊ฑฐ๋‚˜, STT ์ „์šฉ ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌํ•œ๋‹ค. ์ด๊ฒŒ ์ด ๋ณ€๊ฒฝ์—์„œ ๊ฐ€์žฅ ํฐ ์ž‘์—…์ด๋‹ค.

3-4. ๋ชจ๋“œ ํ•ด์ œ โ€” ์„ธ ๊ฒน

๊ฒฝ๋กœ ์–ด๋””์„œ ๋ฌด์—‡์„
TTL AiModeStore.IsCar expiresAt ์ง€๋‚˜๋ฉด false (๋ณ„๋„ ์ •๋ฆฌ ์Šค๋ ˆ๋“œ ๋ถˆํ•„์š”)
exit ๋ฏธ๋ž˜์ฐจ๊ฐ€ {"mode":"chat"} ๋ฐœํ–‰ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ Clear
signout end_session.go RegisterEndSessionHandler ์˜ store.DeleteByUUID ์งํ›„ aiModeStore.Clear(sid) + retained ์‚ญ์ œ: svc.Publish(fmt.Sprintf(AiModeTopicFmt, sid), []byte{}, 1, true) (me ๋ฅผ ์ง€์šฐ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹)

3-5. ๋™์ž‘ ์ •์˜ (car ๋ชจ๋“œ)

์ž…๋ ฅ ai/request ์ถœ๋ ฅ ai/response LLM ํžˆ์Šคํ† ๋ฆฌ suggestions
ํƒœ๋ธ”๋ฆฟ type=voice {ok, context_id, transcript} (message:"") โœ— user ํ„ด โœ—
ํƒœ๋ธ”๋ฆฟ type=text {ok, context_id, transcript:text} (message:"") โœ— user ํ„ด โœ—
chat ๋ชจ๋“œ(๊ธฐ๋ณธ) ์ง€๊ธˆ๊ณผ ๋™์ผ โœ“ user+assistant โœ“

4. ๊ฒ€์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค

  1. ai/mode ์—†์ด ํƒœ๋ธ”๋ฆฟ ํ…์ŠคํŠธ โ†’ ์ง€๊ธˆ๊ณผ ๋™์ผ(ํšŒ๊ท€ ์—†์Œ)
  2. {"mode":"car"} ๋ฐœํ–‰ โ†’ ํƒœ๋ธ”๋ฆฟ ํ…์ŠคํŠธ โ†’ message:"", ai/suggestions ๋ฏธ๋ฐœํ–‰
  3. car ๋ชจ๋“œ์—์„œ ๋ฏธ๋ž˜์ฐจ๊ฐ€ ์ง์ ‘ ์“ด ai/response ๊ฐ€ ํƒœ๋ธ”๋ฆฟ์— ๊ทธ๋Œ€๋กœ ๋ณด์ž„(OC ๋ฌด๊ด€)
  4. {"mode":"chat"} โ†’ 2 ์˜ ์š”์ฒญ์ด ๋‹ค์‹œ LLM ๋‹ต๋ณ€์„ ๋ฐ›์Œ
  5. ttl_sec:5 ๋กœ car โ†’ 6์ดˆ ๋’ค ํƒœ๋ธ”๋ฆฟ ํ…์ŠคํŠธ โ†’ LLM ๋‹ต๋ณ€ ๋ฐ›์Œ(TTL)
  6. car ๋ชจ๋“œ ์ค‘ signout โ†’ retained ai/mode ๊ฐ€ ๋น„์–ด ์žˆ์Œ(MQTTX ๋กœ ์žฌ๊ตฌ๋…ํ•ด ํ™•์ธ)
  7. OC ์žฌ์‹œ์ž‘ โ†’ retained ai/mode ๊ฐ€ ๋‹ค์‹œ ๋“ค์–ด์™€ ๋ชจ๋“œ ๋ณต์›

5. ๊ฐ ๋ณ€๊ฒฝ์˜ ํฌ๊ธฐ

๋ณ€๊ฒฝ ํŒŒ์ผ ํฌ๊ธฐ
Aโ€ฒ ์ง์ ‘ ๋ฐœํ–‰ (OC ์—†์Œ) ๋™์˜๋งŒ. ๋Œ€์•ˆ passthrough ๋ฉด ai_chat.go ๋ถ„๊ธฐ ~20์ค„
B ํ† ํ”ฝยท์ƒํƒœ topics.go, ai_mode.go(์‹ ๊ทœ), server.go ~80์ค„
B car ๋ถ„๊ธฐ ai_chat.go ~30์ค„ + STT ์ „์šฉ ๊ฒฝ๋กœ ๋ถ„๋ฆฌ(voice)
B ํ•ด์ œ end_session.go 2์ค„

6. ์—ด๋ฆฐ ์งˆ๋ฌธ (๋ฏธ๋ž˜์ฐจ ํŒ€ ์˜๊ฒฌ ๋ณ‘๊ธฐ)

  • car ๋ชจ๋“œ์—์„œ ๊ด€๋žŒ๊ฐ user ํ„ด์„ ํžˆ์Šคํ† ๋ฆฌ์— ์ €์žฅํ• ์ง€ โ†’ ์ €์žฅ (๋ฆฌํ”Œ๋ ‰์…˜ยท์š”์•ฝ ๋งฅ๋ฝ)
  • ๋ฏธ๋ž˜์ฐจ๊ฐ€ ์ง์ ‘ ์“ด ๋ฌธ์žฅ์„ OC ๊ฐ€ ๊ตฌ๋…ํ•ด assistant ํ„ด์œผ๋กœ ์ €์žฅํ• ์ง€ โ†’ ์„ ํƒ(ํ•˜๋ฉด ์—ฌ์ • ๋’ค ์ฑ„ํŒ…์ด "์ฐจ๊ฐ€ ํ•œ ๋ง"์„ ์•Ž)
  • ttl_sec ๊ธฐ๋ณธ๊ฐ’/์ƒํ•œ โ†’ 30๋ถ„ / 2์‹œ๊ฐ„
  • ์—ฌ์ • ์ค‘ ๊ด€๋žŒ๊ฐ์˜ ์ผ๋ฐ˜ ์งˆ๋ฌธ์— ๋‹ต์ด ์—†์–ด๋„ ๋˜๋Š”๊ฐ€ โ†’ ์—ฐ์ถœ ์˜๋„๋กœ ํ™•์ธ ํ•„์š” (์ด ์ŠคํŽ™์˜ ์ „์ œ)
โš ๏ธ **GitHub.com Fallback** โš ๏ธ