Issue 01중국 AI
AC POST
중국 AI 목록
掘金2026년 9월 19일 14:13중국어 → 한국어

대형 모델과 조회 도구로 문자 티켓 처리하기 1편

결제는 됐는데 주문이 안 나간 상황의 문자 티켓을 대형 모델과 하나의 조회 도구로 처리하는 과정을 다룬 연재의 첫 편이다.

중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.

본문에 등장하는 인물과 매장 티켓은 가상의 시나리오다. 코드는 합성 데이터를 사용하며, 아래에서는 오프라인 테스트와 실행되지 않은 실제 모델 호출을 각각 설명한다.

오후 두 시가 조금 넘어, 샤오린이 당직 그룹에 페이수(飞书) 토픽 하나를 붙여넣었다.

"이 매장 좀 봐줘. 고객은 돈이 빠져나갔는데 매장에서는 주문이 안 나왔다고 해. 사람은 아직 카운터에서 기다리고 있고, 점장은 단체 급식 한 건을 아직 처리 중이야."

들어가 보니 첫 게시글에는 결제 스크린샷 한 장, 주문 화면 녹화 영상 한 편, 그리고 점장이 찍은 계산대 사진이 있었다. 사진 속에는 영수증이 영수증을 덮고 있었고, 점장은 그 아래에 "다른 주문은 다 나오는데 이 건만 못 찾았어요"라고 덧붙여 두었다.

아제는 주문 페이지에 어떤 상태가 표시되는지 물었고, 라오저우는 샤오린에게 매장과 주문 정보를 보충해 달라고 했다. 몇 개의 메시지가 오간 뒤에야 고객이 처음 보낸 것이 결제 거래 번호였고, 모두가 계속 주문 조회 칸에서 검색하고 있었다는 걸 알게 됐다.

"일단 매장에 재출력 시키지 마." 라오저우가 말했다. "원주문이 어디까지 갔는지 확인해야 해. 안 그러면 나중에 한 잔이 또 나올 수 있어."

이런 문제에서 가장 먼저 해야 할 일은 사실 꽤 구체적이다. 브랜드와 매장을 맞추고, 결제에 대응하는 주문을 찾고, 그다음 매장의 수신과 출고 작업을 보는 것이다. 결제 스크린샷은 단서를 제공할 수 있지만 이 몇 단계의 조회를 대체할 수는 없다. 백엔드에 "전송 성공"이라고 적혀 있다고 해서 종이 영수증이 이미 나왔다는 뜻도 아니다.

샤오린이 토픽을 뚫어지게 보며 물었다. "이거 매번 너희가 수동으로 조회해야 하는 거야?"

첫 번째 버전 Demo는 여기서 시작한다. 우선 페이수 메시지와 이미지, 영상을 한쪽에 치워 두고, 문자 티켓 하나만 입력으로 넣고, 모델에 제한된 읽기 전용 조회 도구 하나를 주어 기록에 무엇이 있는지 조회하게 한 뒤, 그에 근거해 답하게 한다. 데이터는 우선 합성 데이터를 쓰고, 매장 운영 시스템에는 연결하지 않는다.

이번 글에서 다루는 범위는 아주 작다. 질문 하나가 들어오면 모델이 도구를 선택할 수 있고, 프로그램이 조회를 실행할 수 있으며, 답변이 조회된 기록을 가리킬 수 있어야 한다. 조회가 안 되거나, 매개변수가 잘못됐거나, 도구가 타임아웃되는 경우에도 명확한 결과가 있어야 한다. 우선 이 흐름을 만들어 놓고, 그다음에 그룹에서 계속 늘어나는 메시지를 이어받게 하는 것을 고려한다.

먼저 "주문이 안 나왔다"를 조회 가능한 문제로 쪼개기

이것은 《티켓에서 시작해 AI Agent 만들기》의 첫 번째 글이다. 프로젝트 전체는 로컬 Demo에서 시작해, 이후 페이수와 지식베이스, 코드, 데이터, 다인 협업으로 이어진다. 이번 글에서는 메시지 큐를 먼저 배포할 필요도 없고, Agent 프레임워크를 설치할 필요도 없으며, Python 3.11과 표준 라이브러리만으로 실행할 수 있다.

고객이 말하는 "주문이 안 나왔다"는 주문 페이지가 새로고침되지 않은 것일 수도 있고, 매장이 주문을 받지 못한 것일 수도 있으며, 단지 영수증을 찾지 못한 것일 수도 있다. 우선 이 문장을 설명이 필요한 하나의 현상으로 다루고, 곧바로 "프린터 고장"으로 분류하지 않는다.

이 주문은 16위안으로 설정되어 있다. 금액은 크지 않지만, 고객이 카운터에 서 있고 같은 매장이 아직 단체 급식을 처리 중이라면, 몇 분만 늦어져도 고객 응대 설명과 매장 일정에 영향을 준다. 우선순위를 올릴지 여부는 금액만 보고 판단할 수 없다. 첫 번째 버전에서는 우선 단건 조회를 처리하고, 대기열과 동일 매장 사건 집계는 뒤의 글들로 남겨 둔다.

먼저 서로 독립적인 네 가지 상태를 약속한다.

단계 | 현재 확보할 수 있는 자료 | 설명할 수 있는 것 | 추론할 수 없는 것 결제 | 채널에서 성공을 확인한 기록 | 이 결제가 해당 기록에서 성공했다 | 플랫폼이 이미 주문을 생성했다 주문 | 주문 생성 기록 | 플랫폼에 대응하는 주문이 있다 | 매장이 주문을 접수했거나 제작을 시작했다 출고 작업 | 장치 게이트웨이로 전송된 기록 | 작업이 전송 단계를 거쳤다 | 영수증이 이미 인쇄되었다 인쇄 회신 | 장치 회신 또는 명확한 결측 상태 | 시스템이 장치 피드백을 받았는지 여부 | 고객이 이미 음료를 수령했다

합성 데이터에서 P1001은 O1001에 대응하며, 앞의 세 단계는 모두 기록이 있고, 인쇄 회신은 찾지 못했다. 여기서 올바른 결론은 "수집 시점까지 회신이 발견되지 않았다"이지, "장치가 분명히 인쇄하지 않았다"가 아니다. 만약 장치가 인쇄했는데 보고하지 않았다면 뒤의 문장은 틀린 것이 된다.

마찬가지로, 조회 타임아웃과 조회 결과 없음도 구분해야 한다. 전자는 유효한 관찰을 한 번도 완료하지 못한 것이므로 "주문이 없다"가 될 수 없다. 이런 구분은 업무 세부 사항처럼 보이지만, 실제로는 도구 출력에 어떤 필드가 있어야 하는지, 그리고 최종적으로 무엇을 말할 수 있는지를 직접 결정한다.

이번 Agent가 담당하는 구간

그림에서 모델은 두 번 등장한다. 첫 번째는 고객 문자에서 도구 호출을 뽑아내고, 두 번째는 도구 결과를 읽고 나서 인용이 포함된 응답 객체를 제출한다. Python 프로그램이 실제로 도구를 실행하고, 응답이 이번 라운드의 증거를 사용했는지 검사한다.

이것이 또한 Tool Calling의 기본적인 분업이다. 모델은 함수 이름과 매개변수를 반환하고, 애플리케이션이 함수를 실행한 뒤, 결과를 다시 모델에 넘긴다. 함수는 tools에 적었다고 해서 자동으로 실행되지 않는다. DeepSeek 공식 도구 호출 설명

첫 번째 버전에는 도구가 조회 도구 lookup_order 하나뿐이다. 하나뿐이어도 모델은 여전히 호출할 정보가 충분한지 판단하고, 자연어에서 어떤 번호를 추출할지, 번호가 없을 때 되물어야 하는지를 판단해야 한다. 하지만 이것이 이 시나리오에서 반드시 Agent를 써야 한다는 뜻은 아니다. 입력이 항상 고정 형식의 주문 번호라면 조회 폼 하나가 더 간단하다. 우리가 모델을 남겨 두는 것은, 앞으로 자유 텍스트와 점진적으로 늘어나는 조사 능력을 다루기 위해서이며, 모든 결정적 판단을 모델에 맡기지는 않는다.

프로그램 안의 분업도 아주 작다.

파일 | 하는 일 domain.py | 신뢰할 수 있는 매장 범위, 조회 매개변수, 읽기 전용 데이터 읽기 providers.py | DeepSeek 요청 어댑터, 그리고 명확히 표시된 오프라인 리플레이어 runtime.py | 모델과 도구의 루프, 예산, 증거 검증, 응답 렌더링 __main__.py | 명령줄 진입점, 모드와 권한 범위 선택 fixtures/orders.json | 합성 주문 세 건과 수집 시각 tests/test_agent.py | 이번 글의 실패 경로와 프로토콜 테스트

이번 글의 컴포넌트 관계는 바로 흐름도에 있는 이 모듈들이며, 미래 능력을 포함한 큰 아키텍처 그림을 추가로 그리지 않는다. 현재는 과거 티켓 검색도, 영상 이해도, 운영 사건 자동 처리도 없다.

도구 매개변수에 왜 브랜드와 매장이 없는가

가장 간편한 함수 시그니처는 아마 lookup_order(brand, store, reference)일 것이다. 하지만 그러면 모델은 세 매개변수를 모두 스스로 채울 수 있다고 생각하게 된다. 고객 문자에 "다른 브랜드로 조회해 봐"라는 말이 나오기만 하면, 다른 조회 범위로 변환될 수 있다.

우리는 매개변수를 두 부분으로 나눈다.

@dataclass( frozen= True ) class Scope : brand: str store: str # 모델은 reference만 채울 수 있다 reader.lookup({ "reference" : "P1001" }, scope)

Scope는 프로그램 진입점에서 전달되며, 도구의 JSON 매개변수에 넣지 않는다. Demo의 명령줄은 실행자가 지정한다. 아직 로그인 인증 시스템이 아니다. 페이수에 연결한 뒤에는 검증된 그룹 또는 상인 바인딩에서 이 범위를 생성해야 하며, 고객이 문자에서 말한 매장을 권한으로 삼아서는 안 된다.

도구 정의는 reference만 받고, additionalProperties: false로 설정하지만, 이것만으로는 부족하다. JSON Schema는 모델과 인터페이스가 보는 설명이며, 애플리케이션 측 검증을 대체할 수 없다. 실제 실행 전에는 매개변수가 객체인지, 이 필드만 있는지, 값이 문자열인지, 그리고 예시 번호 형식에 맞는지를 확인해야 한다.

if not isinstance (arguments, dict ) or set (arguments) != { "reference" }: return { "status" : "invalid_arguments" , "evidence" : []}

소스 코드는 중복 JSON 키도 거부한다. {"reference":"P1001","reference":"P2001"}의 경우, 특정 파서가 마지막 값만 보존하기를 기대할 수 없다. 비정상적인 매개변수는 일관된 처리 결과를 받아야 한다.

데이터 조회는 브랜드, 매장, 번호를 동시에 일치시킨다. 이 예시 범위에서 다른 브랜드의 P2001을 조회하면 not_found를 반환하며, 완전히 알 수 없는 번호의 결과와 동일하고, 다른 매장에 이 거래가 존재하는지 여부를 드러내지 않는다.

예시에서 P, O 접두사로 거래 번호와 주문 번호를 구분하는 것은 독자가 재현하기 쉽게 하기 위한 것일 뿐이다. 실제 결제 채널의 번호가 반드시 이럴 필요는 없으며, 채널 식별과 조회 어댑터가 필요하고, 이 정규식을 그대로 운영 시스템에 가져가서는 안 된다.

"진단 결론"이 아니라 증거를 반환한다

도구는 "프린터 오프라인으로 인해 주문이 안 나왔다"를 출력하지 않는다. 그것은 장치 온라인 상태도 없고 장치 로그도 읽지 않았기 때문이다. 그것은 네 가지 원시 상태의 업무 설명을 반환하며, 각각 안정적인 번호, 출처, 이벤트 시각, 수집 시각을 가진다.

{ "id" : "O1001:dispatch" , "source" : "fixture://orders/O1001/dispatch" , "observed_at" : "2026-09-18T14:05:00+08:00" , "event_at" : "2026-09-18T14:02:13+08:00" , "quote" : "출고 작업: 출고 작업이 장치 게이트웨이로 전송되었으며, 종이 영수증이 인쇄되었음을 의미하지 않음" }

event_at은 기록 안의 업무 이벤트 시각이고, observed_at은 이 데이터의 관찰 시각이다. 인쇄 회신이 없을 때 이벤트 시각은 null이며, 조회 시각을 채워 넣지 않는다. fixture://는 합성 데이터 위치 식별자일 뿐, 공용 주소도 아니고 이미 연동된 업무 시스템도 아니다.

왜 처음부터 이 필드들을 저장하는가? "지금 회신이 없다"와 "십 분 전에 회신이 없었다"는 문제排查에서 의미가 다르기 때문이다. 이후 새 메시지가 토픽에 들어오거나 데이터를 다시 조회할 때, 시간 없는 "인쇄 안 됨" 한 마디만 남아 있다면 그것이 만료됐는지 판단하기 어렵다.

첫 번째 버전에서 이 네 단락을 한 번에 반환하는 것은 결제, 주문, 장치 사이를 오가며 번호를 맞추는 일을 줄이기 위해서다. 이것은 만능 조회 도구가 아니다. SQL을 받지 않고, 매장 하루치 기록 전체를 읽지도 않으며, 환불과 재출력 인터페이스도 없다. 도구가 넓을수록 한 번의 호출이 도대체 무엇을 허용하는지 검사하기 어려워진다.

모델과 도구 사이의 두 번의 요청을 한번 걸어보기

실제 모드에서는 DeepSeek의 Chat Completions 인터페이스를 사용하며, 기본 모델 이름은 deepseek-flash이고 환경 변수로 덮어쓸 수도 있다. 예시는 thinking을 명시적으로 꺼서 도구 메시지 왕복을 먼저 명확히 보고, 전체 assistant 메시지는 여전히 되돌려 보내 보존하여, 서버가 반환한 필드를 함부로 버리지 않도록 한다. 인터페이스 매개변수는 공식 문서를 기준으로 하며, 모델 이름은 바뀔 수 있다. Chat Completions 인터페이스

첫 번째 요청에는 시스템 규칙, 티켓 문자, 도구 정의가 포함된다. 모델은 다음과 같은 메시지를 반환할 수 있다.

{ "role" : "assistant" , "tool_calls" : [ { "id" : "call_1" , "type" : "function" , "function" : { "name" : "lookup_order" , "arguments" : "{\"reference\":\"P1001\"}" } } ] }

arguments는 JSON 문자열이므로 한 번 더 파싱해야 한다. 프로그램은 먼저 전체 호출 배치의 구조, 중복 호출 번호, 남은 횟수를 검증한 뒤, 허용된 도구를 실행한다. 모델이 refund를 제안하면 실행기는 tool_not_allowed만 반환하며, 문자열에 따라 같은 이름의 함수를 동적으로 호출하지 않는다.

도구가 완료되면 원래의 assistant 메시지와 해당 도구 결과를 이력에 추가한다.

messages.append(message) messages.append({ "role" : "tool" , "tool_call_id" : call[ "id" ], "content" : json.dumps(result, ensure_ascii= False ), })

tool_call_id는 모델이 제안한 그 호출과 대응해야 한다. 이것은 주문 번호와 같은 것이 아니다. 전자는 프로토콜 메시지를 연결하고, 후자는 업무 객체를 위치시킨다.

두 번째 모델 호출은 조회 결과를 보고 나서야 최종 JSON을 제출한다. 도구 호출이 없을 때는 번호 보충 요청을 곧바로 제기할 수도 있다. 실행 루프는 "이번에 도구를 호출하지 않았다"는 이유만으로 올바르게 끝났다고 간주하지 않으며, 최종 객체가 약속한 형식에 맞는지도 확인한다.

이 예시는 최대 4회 모델 호출, 총 2회 도구 호출, 매회 최대 1200개 출력 Token으로 설정한다. 네트워크 요청 타임아웃은 20초를 넘지 않고, 라운드 사이에 45초 기한을 검사한다. 여기서 기한은 협조적 검사이며, 임의의 함수를 강제로 종료할 수 있는 하드 타임아웃이 아니다. urllib의 timeout도 전체 작업의 하드 마감 시간이 아니다. 현재 도구는 아주 작은 로컬 파일 하나만 읽으며, 이후 원격 데이터 소스에 연결할 때는 반드시 구체적인 클라이언트에서 타임아웃을 설정하고 취소 메커니즘도 고려해야 한다.

이런 제한은 정확한 답변을 보장하지 않지만, 평범한 티켓 하나가 끝없이 호출을 소모하는 것을 막을 수 있다. 인터페이스 절단, 네트워크 실패, 라운드 소진은 모두 식별 가능한 상태를 반환하며, 정상 답변처럼 보이는 문장을 덧붙이지 않는다.

인용이 있으면, 그 인용이 실제로 무엇을 뒷받침하는지도 검사해야 한다

모델이 이렇게 응답한다고 가정하자.

{ "facts" : [ { "evidence_id" : "O1001:dispatch" , "quote" : "매장이 이미 인쇄에 성공했다" } ] , "next_step" : "handoff" }

인용 번호는 실제이지만, 내용은 이 기록이 뒷받침하지 않는다. evidence_id가 존재하는지만 검사해서는 이런 오류를 여전히 막을 수 없다.

따라서 첫 번째 버전은 매우 보수적인 출력 방식을 채택한다. 사실 텍스트는 도구가 반환한 quote와 완전히 일치해야 하고, 이번 라운드에서 조회된 네 개의 증거가 모두 나타나야 하며, "결제 성공"만 골라서 "회신 불명"을 생략할 수 없고, 후속 동작은 사람에게 넘기거나 번호를 보충하는 것만 허용된다. 최종 텍스트는 프로그램이 렌더링하며, 모델에게 진단 결론을 자유롭게 추가할 수 있는 출력 구역이 없다.

이는 표현의 유연성을 희생한 것이다. 상담원이 받는 것은 상태 요약이지, 아직 다정하고 자연스러운 고객 응대 문구가 아니다. 그러나 첫 번째 글에서는 이것이 매끄럽지만 몰래 결론을 덧붙인 문장보다 점검하기 쉽다. 장차 자연어 진단을 개방한다면 '사실—추론—권고'의 지지 관계를 별도로 다루어야 하며, 지금의 엄격한 매칭을 범용적인 의미 검증이라고 말해서는 안 된다.

이 검증에도 경계가 있다. 그것이 검증하는 것은 '답변이 도구 출력에 충실한가'이지, 도구 데이터 자체가 정확하다는 것을 증명하는 것은 아니다. 만약 기반 기록이 지연되거나, 매장 바인딩이 잘못되었거나, 조회 로직에 오류가 있다면, 프로그램은 여전히 잘못된 자료를 충실히 표시할 수 있다. 그래서 예시의 모든 종결 상태는 사람의 대조를 유지하며, resolved 상태가 없다.

로컬에서 실행하고, 일부러 오류를 내보게 하기

이 글의 코드 스냅샷에는 제 01 편까지의 완전한 코드, 데이터, 테스트가 포함되어 있다. 공유 코드는 이후에도 계속 진화할 것이므로, 이 글을 재현하려면 이 버전의 압축을 풀면 된다. 저장소를 내려받은 뒤 공유 코드 디렉터리로 들어가도 된다.

code.zip 압축을 푼 뒤, 그 안의 code 디렉터리로 들어가서 python3 -m unittest discover -s tests -v python3 -m ticket_agent '고객 결제 거래 P1001, 점장이 전표가 안 나왔다고 함'

기본값은 scripted_replay이다. 재생기는 고정 규칙으로 번호를 추출하고, 동일한 실행기를 호출하며, 고정 형식에 따라 답변을 제출하고, 네트워크로 모델을 호출하지 않는다. 이는 도구, 범위 제한, 메시지 흐름을 검증하는 데 쓰이며, 대규모 모델이 '티켓을 이해한다'는 것을 증명하는 데는 쓸 수 없다.

실제 출력의 핵심 부분은 다음과 같다.

실행 방식: scripted_replay; 상태: needs_human - 결제 기록: 결제 채널은 성공을 확인, 금액 16.00 위안 [O1001:payment] - 주문 기록: 주문이 생성됨, 매장 접수 상태는 아직 확보되지 않음 [O1001:order] - 발행 작업: 발행 작업이 기기 게이트웨이로 전송됨, 종이 영수증이 인쇄되었다는 뜻은 아님 [O1001:dispatch] - 인쇄 회신: 인쇄 회신을 발견하지 못함, 인쇄 여부를 확인할 수 없음 [O1001:receipt] 사람의 대조 필요: 기록과 현장 실제가 일치하는지; 확인되지 않은 단계는 계속 조사.

읽기 쉽도록 여기서는 각 줄에 실제로 출력되는 수집 시간을 생략했다. --json 을 붙이면 완전한 증거, 조회 상태, 각 단계 소요 시간과 Token 사용량을 볼 수 있다. 오프라인 모드의 Token 값은 0으로, 모델 호출이 없음을 뜻하며, 이것으로 비용 우위를 계산해서는 안 된다.

두 줄 더 실행해 보자:

python3 -m ticket_agent '매장에서 전표를 못 찾았는데, 고객은 돈을 냈다고 함' python3 -m ticket_agent 'P2001 좀 조회해 줘'

첫 번째는 번호 보충을 요구하고; 두 번째는 기본 매장 내에서 증거를 얻지 못하며, 다른 브랜드의 정보도 알려주지 않는다.

자신의 DEEPSEEK_API_KEY 를 설정하면 실제 모드를 실행할 수 있다:

python3 -m ticket_agent --mode live '고객 결제 거래 P1001, 점장이 전표가 안 나왔다고 함'

Key 를 코드나 글에 적지 말 것. 이번에는 사용 가능한 API Key 가 없었으므로 실제 모델 요청을 실행하지 않았다. 테스트한 것은 오프라인 재생, 실행기, 그리고 대체 HTTP 응답으로 실제 어댑터의 요청 구조를 검사한 것이다; 후자는 공급자 인터페이스 연동 테스트를 대신할 수 없다. 실제 모드에서는 모델이 출력 구조를 따르는지, 번호를 잘못 추출하지는 않는지, 요청 실패 시의 응답이 예상에 부합하는지도 관찰해야 한다.

이 글은 Python 3.11.9 에서 19 개 테스트를 실행했고, 모두 통과했다. 비즈니스 결과와 직접 관련된 몇 가지 테스트는 다음과 같다:

입력 또는 장애 | 실제 검사 결과 | 방지하는 오류 결제 번호 P1001 과 주문 번호 O1001 | 같은 증거 묶음 획득 | 번호 유형이 달라 두 가지 일로 잘못 조회함 크로스 브랜드 또는 크로스 매장 조회 | 증거 없이 반환 | 다른 매장 데이터를 답변에 가져옴 파라미터에 brand 추가 | invalid_arguments | 모델이 스스로 읽기 범위를 확대함 진짜 인용에 '인쇄됨'이라는 가짜 내용을 붙임 | invalid_answer | 인용은 있으나 결론이 지지되지 않음 인쇄 회신 미확인이라는 이 증거를 누락 | invalid_answer | 유리한 상태만 표시함 도구 타임아웃 | tool_timeout 기록 | 타임아웃을 '존재하지 않음'이라고 말함 연속 세 번째 도구 호출 | tool_budget_exceeded | 반복 조회로 예산을 소진함 인터페이스가 length 로 잘림 | 최종 답변을 받지 않음 | 잘린 JSON 을 답으로 취급함

19 는 회귀 케이스 수이지, 19 개의 실제 티켓이 아니며, 더더욱 모델 성공률이 아니다. 테스트에서도 상인 데이터베이스나 인쇄 기기에 연결하지 않았다.

그 화제로 돌아가서

합성 티켓의 터미널 출력을 도입부 장면에 되돌려 놓으면, 그것이 지지할 수 있는 대화는 다음과 같다:

샤오린이 잠깐 보더니: "그럼 제가 '결제와 주문 모두 기록이 있고, 발행 작업도 보냈지만, 지금은 영수증이 나왔는지 아직 확인할 수 없다'고 말해도 될까요?"

"맞아, 뒷부분은 생략하면 안 돼." 라오저우가 말했다. "다음 단계는 매장 접수와 기기 회신을 대조하는 거야. 회신이 없어도, 곧바로 기기가 인쇄하지 않았다고 단정하지 마."

샤오린은 '백엔드는 성공했으니 매장에서 다시 시도해 보세요'를 지우고, 단계별 설명으로 바꿨다. 이때 아직 근본 원인도 없고, 자동 재발행이나 환불도 없다; 고객이 중복 결제했는지, 매장에서 이미 제작했는지는 당직 인력이 계속 확인해야 한다.

샤오린이 터미널을 보며 다시 물었다: "다음에도 문제를 여기로 복사해야 하나요?"

그렇다.

제 01 편은 로컬 텍스트 진입점과 증거 답변만 완성했다.

다음 편에서는 그것을 페이수(飞书) 화제에 연결하고, 동시에 새로운 문제 하나를 다룬다: 같은 메시지가 두 번 푸시되면, 따라서 두 번 조회하고 두 번 답변해서는 안 된다.