Hampter Lab

Small, specific ideas for AI


LLM으로 LED 제어하기

그거 아십니까. LLM으로 LED 제어가 정말 잘 됩니다. 단순히 껐다 켰다 하는 수준이 아니라, 굉장히 엄청나게 다양한 걸 시킬 수 있어요. 이 글은 제가 그 과정에서 거친 시행착오와, 최종적으로 도달한 꽤 마음에 드는 구조에 대한 이야기입니다.

다음과 같은 구성으로 작성되었습니다.

  1. 아주 간단하게 LED 제어하기
  2. 좋지 않은 방식으로 LED 제어하기
  3. 멋지게 LED 제어하기

아주 간단하게 LED 제어하기

처음 만들고 싶었던 건 별게 아니었어요. LLM이 화가 났을 때 빨간색 LED를 켜서, 〈2001 스페이스 오디세이〉의 HAL 9000이나 그런 작품들에 나오는 omni eye 같은 분위기를 연출하고 싶었습니다.

그래서 저는 ESP32 하나에 동그란 LED 링(WS2812 12개) 하나를 붙였습니다. 굳이 원형 LED를 고른 이유는 앞서 말했듯 "눈"을 연출하고 싶었기 때문이에요. 이건 제 첫번째 버전인데, 대충 상자를 케이스로 삼았습니다. 모티브는 놀랍게도, HAL 9000입니다.

이제 이걸 LLM에 연결합니다.

LLM과 LED를 연결하기.

LLM과 LED를 연결하는 것은, 두 가지 과정으로 나눠서 생각해야합니다. 제 생각에 어렵게 느껴지는데 사실 별거 아닌 부분이 있고, 별거 아니라고 생각했는데 어려운 부분이 있습니다.

첫번째 부분: LLM에 LED를 연결하는 부분입니다.

이 부분도... 다시 두 부분으로 나누어지는데요

우선 어떻게 컴퓨터랑 led를 연결하느냐... 에 대한 부분은 esp32는 아주 저렴하면서 wifi를 지원합니다.덕분에 이걸 활용했습니다. 컴퓨터에서 어떻게 다시 llm으로 연결되느냐.. 에 대한 부분은 mcp를 사용했습니다.

혹시 MCP를 모르시는 분을 위해 아주 간단히 설명하자면(많은게 생략되었습니다), 이름/설명/인자 이렇게 세개로 이루어져있습니다. 이름은 말그대로 이름이고요, light_control이라고 지어두면 llm이 아 얘는 불을 컨트롤 하는 도구구나 하고 눈치 채겠죠? 설명은 역시 말그대로 설명입니다. 자 인자에 on을 쓰면 불이 켜지고 off를 넣으면 불이 꺼져 이렇게 설명해주는 거죠 인자는 이제 llm이 작성하는 부분입니다. 사용자가 불꺼 라는 명령을 하면, 설명읽고 인자에 off를 넣어서 실행합니다.

정확히는 이런느낌입니다.

네 불을 끌게요. [light_control('off')] 이제 [ ] 안에 든거는 특별하게 작동해서 이 포맷으로 통신이 되는거죠

그러면 이제 esp32에서는 off가 저 통신라인으로 들어오면 꺼두게 하면 됩니다.

이제 여기서 어려운 두번째 부분이 나옵니다.

두번째 부분: 그래서 뭘 어떻게 제어하게 함? 이 글은 대부분 이 부분에 대해 고민한 부분들입니다.

on이면 led가 켜지고... off면 led가 꺼지게 한다. 이 정도면 아주 쉽습니다. 빨간불, 파란불, 초록불을 나오게? 이것도 쉬워요. 그냥 on off 대신 설명에 red/blue/green을 넣으면 됩니다.

결과적으로, llm을 일종의 분류기로 사용하는거죠.

저의 첫번쨰 버전도 이것과 완전히 동일했습니다만, 이제 llm입장을 속이기 위해 약간의 트릭을 사용했습니다.. 아래 인터렉션을 통해 제 첫번째 버전을 체험해보실 수 있습니다. 아주 간단해요.

MCP TOOL ready
Description
name
express_emotion
description
Set LED eye mood.
Input
HSV mask pass loading hsv intensity masks

실제 LED 링의 빛 움직임.

트릭이란, 인자에 입력 가능한 텍스트로 angry, annoy, neutral 이렇게 셋을 작성하도록 한 겁니다. 이름도 set_emotion으로 짓고요. 이 중 하나를 적으면 이렇게 됩니다.

  • Angry: LED가 빨간색으로
  • Annoy: LED가 노란색으로
  • Neutral: LED가 초록색으로

그래서 이제 어떠한 llm이 화날 만한 상황이 오면 set_emotion('angry')라고 실행하게 하는거죠. 이 부분은 뭐 프롬프트 레벨에서는 간단한 트릭입니다. 사실 이 분이 꽤나 재미있는 부분이긴 합니다. tool description이 실제로 그 행동을 하도록 강제하는 것은 아니기 때문에, 이 특성으로 다양한 가능성과 위험성이 발생합니다. 이 부분에 대한 이야기는 다음에 llm spanker에 대한 글을 작성할 때 진행하겠습니다.

어쨋거나, 결과적으로 이건 3색 분류기입니다. 하지만, 나이브하게 생각하면, 훨씬 더 많은 색, 심지어는 움직이는 패턴도 추가할 수 있습니다. 패턴 하나를 만들고, 그 레이블을 LLM에 소개만 시켜주면 되니까요.

다만 이러한 접근에는 내제된 한계가 존재합니다. 패턴이 몇개 안되는 경우에는, 사실 큰 문제는 없습니다. 그 LED 자체가 재미없고 심심하다는거 말고요. 하지만 패턴이 많아지면? 이제 LLM 프롬프트에 매핑 테이블을 잔뜩 넣어줘야 합니다. 색이 수십 개로 늘어나면 그만큼 길어져요. MCU의 작은 메모리 안에서도, 그만큼의 패턴을 기억해야하고요.

좋지 않은 방식으로 LED 제어하기

곰곰이 생각해보니, ChatGPT든 Claude든 LED 코딩을 정말 잘합니다. 그러니까, 위에 있는 눈 깜빡이는 패턴도 사실 CHATGPT가 만들어준거거든요. 그럼 아예 실시간으로. LLM이 LED를 제어하는 코드를 통째로 실시간으로 짜게 시키면 되는 거 아닌가? 하는 생각이 들었어요.

예를 들면 "무지개 느낌으로 한 바퀴 돌게 해줘" 혹은 "comet처럼 꼬리를 남기며 돌게 해줘" 정도만 말해도, 대충 이런 코드는 금방 나옵니다.

무지개 회전이라면 이런 식이고요.

#define NUM_LEDS 12

CRGB leds[NUM_LEDS];

void rainbowSpin() {
  uint8_t baseHue = millis() / 12;

  for (uint8_t i = 0; i < NUM_LEDS; i++) {
    leds[i] = CHSV(baseHue + i * 255 / NUM_LEDS, 255, 180);
  }

  FastLED.show();
}

comet 꼬리라면 이런 식입니다.

#define NUM_LEDS 12

CRGB leds[NUM_LEDS];

void cometTrail() {
  fadeToBlackBy(leds, NUM_LEDS, 46);

  uint8_t head = (millis() / 45) % NUM_LEDS;
  leds[head] += CHSV(150, 255, 255);

  FastLED.show();
}

그러나 여기에는 아주 치명적인 문제점 두가지가 있습니다.

첫번째 문제는 속도와 환경입니다. 우선, LLM이 ESP32같은 MCU에 코드를 올리고 컴파일 하는 것, 자체에는 어떠한 어려움도 없습니다. 실제로 저도 코덱스가 PLATFORMIO로 접근하게 하여 컴파일까지 진행합니다. 문제는 이 컴파일 과정이 느리고, 선도 필요합니다. 컴파일러도 미리 준비가 되어있어야하고요. 아무리 짱구를 돌려봐도 상당히 느리고 귀찮은 작업이 선행되어야했습니다. 고작 LED의 빨간색을 파란색으로 바꾸는데 3분동안 기다릴 수는 없어요. 이 관점에서 그나마 가장 괜찮다고 생각한 방법은 micropython과 같은 스크립트 언어 기반으로 어떻게든 추상화를 잘 짜서 정의해두는 거였습니다. 어떻게 추상화를 박아넣을지는 솔직히 생각은 안해봤는데 아마 어떠한 방식으로던 가능을 하지 않을까 싶습니다. 다만 그렇다라도, 여전히 한가지 치명적인 문제가 남습니다.

바로 안정성입니다. 아주 치명적인 문제가 될 수 있는데, 잘못된 코드가 들어가면 디바이스가 멀쩡하다는 보장이 없습니다. 사실 더 나아가서, LED를 폭탄으로 만들지 않을거란 보장이 없습니다...! 어쨋든 기본적으로 지금까지의 바이브코딩이 그나마 용서될 수 있던것은, 아무리 망해봤자 컴퓨터 하나 박살나는거 이상으로는 망하기 어렵기 때문 아니겠습니까? 하지만 이런 물리 하드웨어 상에서는, 솔직히 led 조종하는 것 정도는 괜찮지 않나 싶기도 하지만, 어쨋거나 원칙적으로는 led가 벽돌도 폭탄도 되지 않는다는 보장이 없는 이상 쓰기 불안한 것도 사실입니다.

멋지게 LED 제어하기

새삼스럽지만 저의 LED가 동그랗다라는 것을 깨달았습니다. 그리고 HSV 공간도 동그랗다는걸 깨달았습니다. 마침 운좋게도 당시 회사에서 무선통신 바탕으로 작업하고 있던 시기여서, 뭔가 sin cos을 끼얹으면 뭔가 야무지게 연결되지 않을까? 싶더라고요

결과적으로 아주 잘 됐습니다.

FORMULA MAP 12 samples
h
s
v
i
theta
h
s
v
Formula runtime sampling hsv masks

수식을 바탕으로 표현 가능한 여러 패턴들.

두 가지 변수를 정의했습니다. 각 LED의 위치가 theta가 되고[1], t는 시간입니다. 원형으로 배치된 12개 LED는 각자 자기 각도(theta)를 가지고 있고, 거기에 시간(t)과 관계되어 다양한 패턴을 만듭니다.

theta + t의 경우, 시간이 흐르면 모든 LED의 색상이 같은 속도로 회전하니까, 결과적으로 무지개가 빙글빙글 도는 패턴이 됩니다. 복잡하지만, 다른 패턴들도 이러한 방식으로 표현 가능합니다. 위의 인터렉션을 보시면 대충 감이 오실꺼라고 생각됩니다.

하지만 문제가 있습니다. 사람이 쓰기에는 상당히 괴랄합니다. 애초에 이런식으로 표현하고 보니, 우선 구성 자체는 매우 직관적이고 단순하기 때문에, 왜 애초에 이렇게 사용하지 않았지 라고 생각했습니다만, 사용하다 보니 알았습니다. 사람이 쓰기에는 대단히 비직관적이에요. HSV 색 공간과, 수식을 통한 시간적 움직임을 동시에 머릿속에 띄워놓고, 그걸 12개 LED 각각에 매핑해야 하니까요. 삼항연산자라도 하나 끼면 머리가 아파오기 시작합니다. 정신이 혼미해집니다.

하지만 큰 상관은 없습니다. 왜냐? 이건 어차피 사람이 쓸 게 아니거든요.

LLM은 이런 수식을 정말 잘 만듭니다. 재미있는 건, LLM한테 시키면 처음엔 "저는 이런 거 못 해요, 훈련 안 됐어요 어려워요" 이런 소리를 하는데 잘합니다. 아마 정렬과 능력 사이에서 오는 일종의 환각으로 생각하고 있습니다. 아라한 장풍 대작전이라고 영화가 있는데 거기서 엥 그러면 내가

뭐 벽도타고 장풍도 쏘겠네 이러면서 벽도 타고 장풍도 쏘는 장면이 있습니다. 약간 그 느낌입니다. 생각해보면 LLM이 잘하는 일의 특성과 잘 맞긴 합니다. 명확한 규칙(HSV 매핑, 시간 변수, 제한된 연산자)이 주어진 상태에서 수식을 조합하는 일이니까요. 어찌됬건 프롬프트만 잘 만들어주면 됩니다. 특히, 이제와서든 꽤나 구닥다리처럼 느껴지기도 합니다만, few-shot learning이 아주 효과적입니다.

그래서 결과적으로는 h,s,v 세 인자값에 대해서 수식을 입력받는 mcp로 구성하면 됩니다. (이 수식을 실제로 어떻게 파싱해서 계산하는지[2], 어디까지의 문법을 지원하는지[4], 그리고 mcp를 실제로 어떻게 나눴는지[6]는 부록에 따로 적어뒀습니다.)

CREATE PATTERN ready
sample: create_pattern / slot 1 / blink three times
Prompt runtime paste valid JSON to preview

프롬프트를 복사하고 결과를 붙여넣는 패턴 생성기.

위 인터렉션을 통해서 한 번 체험해 볼 수 있습니다. 실제로 어떤 prompt, 즉 description이 들어가서 어떤 출력을 보내는지 느낌을 보실 수 있습니다. 프롬프트 구성 자체는 mcp가 아니다 보니까 약간의 변형이 있었습니다. 우선 입력하는 부분에, 뭐 예를들면 "3번 깜빡이기" 혹은 "죽음을 led로 표현하기" 이런것들을 적으시고, copy prompt를 누르고, 복사된 프롬프트를 사용하시는 llm, 챗지피턴 클로드간에 붙여넣어 결과를 보시면 됩니다. 아마 프롬프트가 제대로 작동했다면 정규화된 hsv 수식이 나올텐데, 그걸 위 붙여놓는곳에 넣으시면 실제 작동 모습도 확인 가능합니다.

이러한 방식은 다양한 이점을 제공합니다.

  1. 빠르고 가볍습니다. 평가할 때는 theta에 각 LED의 각도를, t에 경과 시간을, i에 LED 인덱스를 넣고 같은 수식을 LED 개수만큼 반복해서 돌립니다. 게다가 색상(Hue)·채도(Sat)·명도(Val)가 각각 독립된 수식이라, LED 하나당 세 번, 12개면 한 프레임에 36번 평가하는 셈이에요. 이게 ~60fps 백그라운드 태스크에서 계속 돌아가는데도 ESP32는 끄떡없습니다. 매 프레임 문자열을 다시 파싱하는데도요. 그리고 수식만 입력으로 넣으면 되기 때문에, 컴파일 등의 과정도 필요 없습니다. 그리고, 아마 어지간하면 동일 기능 대비 코드 길이도 짧을 겁니다. 이건 llm 비용 입장에서 이득입니다.
  1. 안전합니다. 이 평가기에는 애초에 위험한 동작 자체가 문법에 없어요. 반복문도, 함수 정의도, 메모리 접근도, 외부 호출도 없습니다. 표현식 평가만 하니 무한 루프로 기기가 멈출 일도 없고요[3][5].

우선, 여기까지만 해도 이 작은 원형 led만으로 다양한 것들이 가능해집니다.

  • 분위기에 맞게 조명을 틀어달라고 할 수 있고요, 뭐 어떤 음악에 어울리는 조명 이렇게요.
  • 아니면 타이머같은걸 만들어 달라고 할 수도 있고, 메트로놈으로 써도 괜찮겠네요
  • llm의 감정 표현 : 처음의 시작이었죠. 이제는 단순히 색만 바뀌는게 아니라, 문맥에 따라 다양한 표현이 가능합니다.
  • 바이브 코딩하시면 일종의 진행 표시줄.. tqdm으로 써도 됩니다. 이제 중간중간 완성도에 따라서 채우라고 명령해두는거죠. 에러나면 빨간색으로 바꾸고. (정말 뜬금없는 이야기인데, tqdm이 바이브코딩으로 인해 가장 사용률 떨어진 라이브러리 중 하나 아닐까요? )

그런데, 이것 이외에 정말 멋진 장점 하나가 딸려옵니다. 바로, 다른 값을 끼워 넣기가 굉장히 쉽습니다. 무슨 말이냐면, 저 t라는 변수 있잖아요? 이건 사실 그냥 하나의 숫자값일 뿐입니다. 말 그대로 변수입니다. 그렇다면 여기에 다른 센서값을 넣으면 되는 거 아닌가? 하는 생각이 들었어요.

그래서 텅 빈 변수 몇개를 시스템에 추가했습니다[7]. 이런 표현이 가능하게요.

hue = theta + var_a

이제 var_a에 무엇을 넣느냐에 따라 디바이스의 정체성이 바뀝니다. 온도계 값을 넣을 수도, 음성의 피치 값을 넣을 수도 있어요. llm 입장에서도, 그냥 var_a가 온도계에서 흘러오는 값이다. 라고만 알면 되요.

예를 들어볼게요. "나는 -4도에서 40도까지의 온도를 전체적으로 보고 싶다. 낮을 때는 차갑게 보이고, 올라갈수록 빨갛게 변하고, 32도를 넘으면 정말 큰일 난 거니까 시뻘건 알람처럼 깜빡거렸으면 좋겠다." 이런 요구사항을, 바이브 코딩하듯 LLM에게 툭 던지면 됩니다. 아래 인터렉션을 참고해주세요.

SENSOR VAR INPUT temp
h
v
input
var_a
var_b
status
SENSOR VAR runtime waiting for sensor var

-4~40도 온도 입력 예시.

이제 실제로 변수의 움직임에 따라서 반응하는 것을 확인 할 수 있습니다. 온도계, 음성에 따른 반응, 심지어 온도계와 습도계 두개의 변수를 받아 불쾌지수를 계산하는 것도 가능합니다. 외부 api 데이터나 더 많은 센서들과 결합한다면 그만큼 더 많은 활용이 가능하겠죠

정말 멋있다고 생각합니다.

마치며

만드는 데 든 비용은 대략 5달러 정도였습니다. 원리만 이해하시면 (그렇게 어렵진 않다고 생각해요) 여기저기 응용할 곳이 많아 보입니다. 실제 작동 영상이에요.

[1] theta의 정의

theta는 단순한 LED 인덱스가 아니라 정규화된 각도입니다. i번째 LED라면 theta = 2π · i / 12로, 0부터 한 바퀴(2π)까지 고르게 분포시킵니다. 그래야 12개가 원 위에 색을 균등하게 펼칠 수 있어요. LED가 24개면 마찬가지로 2π · i / 24로 나눠주면 됩니다. 원형이라 각도 매핑이 깔끔하게 떨어졌는데, 원이 아닌 다른 형태라면 인덱싱을 어떻게 가져갈지는 따로 고민해야 할 문제입니다.

[2] 파서

이 파서는 재귀 하강(recursive descent) 방식의 경량 수식 평가기입니다. AST는.... 여기 쓰기 넘 복잡하고 무겁고 할 필요도 없지 싶어서요. 문자열을 왼쪽부터 한 번 훑으면서, 연산자 우선순위에 맞춰 함수가 함수를 재귀적으로 호출하는 방식으로 파싱과 계산을 진행했습니다. 우선순위는 삼항연산자(?:) → 논리(||, &&) → 비교(<, >, == 등) → 덧셈·뺄셈 → 곱셈·나눗셈·나머지 → 단항(-, !, 괄호) 순으로 함수가 층층이 쌓여 있습니다.

[3] 파서 엣지케이스 처리

재귀 하강 파서다 보니 몇 가지 엣지케이스를 처리해줬습니다.

하나는 재귀 깊이입니다. 괄호를 깊게 중첩하면 함수 호출이 계속 쌓이는데, 백그라운드 태스크 스택이 4KB뿐이라 너무 깊으면 스택이 터집니다. 그래서 깊이 카운터로 상한을 뒀습니다.

다른 하나는 NaN입니다. mod(1, 0)이나 sqrt(-1) 같은 걸로 NaN을 만들 수 있고요. 그래서 이런 부분은 한 번 걸러줬습니다.

float eval(const char* expr, float theta, float t, int i) {
  _expr = expr; _pos = 0;
  _theta = theta; _t = t; _i = i;
  _depth = 0;                                  // 깊이 초기화
  float r = _parseConditional();
  if (isnan(r) || isinf(r)) return 0.0f;        // NaN/Inf 차단
  return r;
}

static const int MAX_DEPTH = 32;

float _parseConditional() {
  if (++_depth > MAX_DEPTH) { _depth--; return 0.0f; }  // 너무 깊으면 탈출
  // ... 기존 로직 ...
  _depth--;
  return cond;
}

그런데 사실 이정도로 복잡한걸 짤 일이 없기는 합니다.

[4] 지원하는 수식의 범위

"수식을 어디부터 어디까지 지원할 것인가"는 본문에 넣을까 말까 고민한 지점입니다. 중요한 부분같아용.

결국 LLM이 뱉은 수식을 파싱해서 직접 계산해야 하는데, 세상의 모든 문법을 다 지원하는 건 구현이 까다롭고 사실 다 필요하지도 않습니다. 그래서 패턴을 표현하는 데 필요한 만큼만 지원했습니다. 사칙연산과 나머지(%), 삼각함수(sin/cos/tan)와 abs/sqrt/floor/ceil, 두 인자 함수(min/max/pow/mod), 비교 연산자(<, >, <=, >=, ==, !=)와 논리 연산자(&&, ||, !), 그리고 삼항연산자(?:)까지입니다.

처음엔 "사칙연산이랑 sin/cos 정도면 되겠지" 했는데, 막상 만들다 보니 비교·논리·삼항이 들어가는 순간 표현력이 확 올라가더라고요. if문이 생기는거니까요

[5] HSV → RGB와 wrap-around (출력 범위 갇힘)

칩은 결국 RGB만 받기 때문에, 매 프레임 계산한 HSV 값을 RGB로 변환해서 흘려보내야 합니다. 이때 출력은 최종적으로 전부 범위가 강제로 갇힙니다. 색상(Hue)은 0~2π로 두는데, 한 바퀴를 넘으면 fmod로 다시 0으로 감기게 처리합니다. 음수로 떨어지면 2π를 더해 양수 구간으로 끌어올리고요. 무지개가 끊김 없이 회전하는 게 바로 이 wrap-around 덕분입니다. 반면 채도와 명도는 회전하는 값이 아니라 0~1로 잘라내고(constrain), 명도는 한 번 abs를 씌워 음수가 들어와도 밝기로 해석되게 했습니다. 이렇게 처리한 값을 0~255 바이트로 변환해 LED에 넣어요.

그래서 LLM이 v = 99999 같은 말도 안 되는 값을 뱉어도 그냥 1로 잘려 최대 밝기에서 멈출 뿐, 아무것도 망가지지 않습니다. 적어도 제가 분석한 결과로는 그러긴 한데, 혹시 이걸 부술 아이디어가 있으신 분은 저에게 알려주시면 감사하겠습니다

[6] MCP 구성

LED 표현 방식에 집중하느라 본문에서는 생략했지만, 사실 실제 디바이스 만들때는 mcp 세 개를 만들었습니다. 몇개의 mcp를 만들까 꽤 고민한 지점이에요. 그러니까, led가 사람을 위한 ui라면 mcp는 llm을 위한 ui잖아요 llm찡을 위한 그런 고민이 필요합니다.

create_pattern : hue·saturation·brightness 수식과 이름을 받아 영구 슬롯(1~5)에 패턴을 생성하고 저장합니다. change_slot : 활성 슬롯을 전환합니다. 0은 기본 눈 깜빡임(IDLE), 1~5는 저장된 패턴, 6은 소등(Blackout)이고, 실행 시간(duration)을 함께 줄 수 있습니다. slot_status : 저장된 모든 슬롯의 이름과 수식을 조회합니다.

이렇게 나눈 건 패턴을 저장하고 재사용할 수 있게 하고 싶어서였습니다. 그러면서 그냥 사용자가 저거 켜줘 했을때 키면서, 동시에 모든 수식은 보여주지 않기를 원했습니다. 그래서 의도적으로 저장할때 수식 뿐 아니라 name과 간단한 설명과 저장하게 했어요. 이건 사람을 위한게 아닙니다. 다른 세션의 llm을 위한 요소에요

다만 생성·저장(create_pattern)과 실행(change_slot)을 분리한 게 최선이었는지는 아직 확신이 없어요. 테스트 편의상 나눠뒀는데, 실제 사용 패턴에 따라서는 하나로 합치는 편이 나을 수도, 아니면 별도의 임시 메모리를 두는 편이 나을 수도 있습니다. 이건 유즈케이스를 더 모아봐야 답이 나올 문제 같습니다.

[7] 센서 포트와 정규화

var_a 같은 변수에 외부 센서값을 꽂을 때 핵심은, 센서값의 범위를 수식의 영역에 맞춰 정규화하는 일입니다. 이 시스템에서 Hue는 0~2π 한 바퀴로 표현되는데, 온도 25~35도를 그대로 Hue에 넣으면 한 바퀴(약 6.28)를 한참 넘어가서 색이 마구 돌아버려요. 그래서 "관심 있는 입력 구간"을 "보고 싶은 색 구간"으로 매핑하는 한 줄이 필요합니다. 센서값이 들쭉날쭉 튀면 색도 같이 떨리니, 간단한 이동평균 같은 스무딩을 한 번 걸어주면 훨씬 보기 좋고요. 문제는 정규화를 언제하냐입니다.

  • 사실 아예 안할 수 도 있어요. 가능한 값 범위만 알려준다면 llm이 수식 생성 과정에서 알아서 할 수도 있습니다. 다만 여러 엣지케이스가 발생할 것같은 느낌입니다.
  • 정규화를 포트 연결 그러니까 var_a와 외부 값을 연결하는 과정에 미리 하는게 최선인 듯 싶습니다. 다만 이렇게 미리 고정해두면 세션이 길어질수록 LLM이 현재 매핑 상태를 헷갈려 하는 문제가 있어요. 그래서 연결을 관리하는 별도의 런타임이 필요합니다. 런타임에서는 어떠한 포트가 어떤 세션에서 어떠한 정규화로 연결되었는지를 설명해줘야하고요. 이 연결 자체를 mcp로 관리 할 수 있겠죠.