diff --git a/agent-contract/outer/openai-compatible-api.md b/agent-contract/outer/openai-compatible-api.md index 9258772..6011190 100644 --- a/agent-contract/outer/openai-compatible-api.md +++ b/agent-contract/outer/openai-compatible-api.md @@ -224,11 +224,11 @@ Strict output 모드: `tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"`를 `--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다. provider가 native OpenAI-compatible `tool_calls`를 반환하면 Node는 내부 `RunEvent.metadata["openai_tool_calls"]` JSON으로 보존하고, Edge는 이를 OpenAI-compatible `message.tool_calls` 또는 stream `delta.tool_calls`로 반환하며 `finish_reason: "tool_calls"`를 사용한다. provider native `tool_calls[].function.arguments`는 OpenAI 계약에 맞는 JSON string으로 반환한다. 단, provider가 요청 `tools[].function.parameters` schema상 배열/객체여야 하는 값을 JSON 문자열로 이중 인코딩한 경우 Edge는 해당 `arguments` JSON만 schema 기준으로 복원해 다시 JSON string으로 직렬화한다. -provider route에서 assistant content에 ``, `{{function(...)}}`, `[Calling tool: ...]` 같은 텍스트가 들어와도 Edge는 기본적으로 이를 파싱하거나 OpenAI `tool_calls`로 합성하지 않는다. 해당 텍스트는 backend가 반환한 content로 그대로 둔다. 예외적으로 Node adapter가 native tool API unsupported fallback을 수행해 `openai_text_tool_fallback: "true"` metadata를 붙인 실행에 한해서만 Edge가 text tool-call을 OpenAI `tool_calls`로 복원한다. -CLI route(`adapter: "cli"`)는 native backend tool calling이 없으므로, 호환 fallback으로만 assistant content의 Cline-style 텍스트 tool call 블록을 OpenAI-compatible `tool_calls`로 합성할 수 있다. 이 fallback이 지원하는 텍스트 블록의 최소 형태는 `>>JSON-or-text` 또는 `{{function_name(key=Python/JSON-like-literal)}}`이다. -CLI fallback에서 텍스트 tool call을 구조화할 때만 Edge는 요청의 `tools[].function.parameters` schema를 기준으로 arguments를 정규화한다. 예를 들어 tool schema가 `commands: string[]`만 허용하면 command 객체 입력도 shell string 배열로 접고, `commands: {command,args}[]`를 허용하면 shell 문법이 없는 명령을 structured argv로 만든다. -CLI fallback에서 schema에 없는 UI 설명용 `description`이나 실행 위치 힌트용 `runInTerminal`은 command 객체와 최상위 args에서 제거한다. 단, `cd`, `command -v`, `&&`, pipe, redirect, quote 등 shell 해석이 필요한 명령은 schema가 허용할 때 `commands: ["cd /work && git status"]` 같은 shell string으로 유지한다. -CLI route에서는 backend auto tool-calling 요구 조건으로 요청이 실패하지 않도록 내부 실행 입력의 `tool_choice`를 `"none"`으로 낮춘다. +요청에 `tools[]`가 있고 provider가 native `tool_calls` 없이 assistant content에 raw text tool-call 블록을 담아 응답하면, provider route(`openai_compat`, `vllm`, `ollama`, provider pool)와 CLI route(`adapter: "cli"`) 모두에서 Edge는 그 블록을 요청 tool schema 기준으로 구조화하거나 차단한다. 이 정규화가 인식하는 텍스트 블록의 최소 형태는 `>>JSON-or-text` XML 형식과 `{{function_name(key=Python/JSON-like-literal)}}` mustache 형식이다. +후보 tool 이름이 요청 `tools[]`에 있고 arguments가 파싱되어 해당 tool의 `function.parameters` schema를 만족하면, Edge는 이를 OpenAI `tool_calls`로 정규화하고 raw 블록을 `content`에서 제거한 뒤 `finish_reason: "tool_calls"`로 반환한다. 요청 `tools[]`에 없는 tool 이름, unclosed/function 정의 누락 같은 malformed 블록, schema를 위반하는 arguments는 성공 content로 반환하지 않고 tool validation 실패로 처리한다. non-stream과 strict buffered stream 응답은 bounded tool-validation attempt 한도까지 run을 재시도하고, 그래도 실패하면 `tool_validation_error`로 응답한다. live SSE 스트림은 raw 블록을 content delta로 flush하지 않고 `tool_validation_error` 이벤트로 스트림을 종료한다. +요청에 `tools[]`가 없으면 assistant content의 tool-call 유사 텍스트는 파싱하거나 합성하지 않고 backend content 원문으로 그대로 둔다. 자연어 추론은 어떤 경우에도 `tool_calls`로 변환하지 않는다. +raw ``/`{{...}}` 블록과 `<|mask_end|>` 같은 알려진 chat-template sentinel은 성공 응답의 `content`나 SSE delta에 노출하지 않는다. sentinel은 content와 reasoning 양쪽에서, streaming chunk 경계에 걸쳐 분할되더라도 sanitize한다. +text tool-call을 구조화할 때 Edge는 route와 무관하게 요청의 `tools[].function.parameters` schema를 기준으로 arguments를 정규화한다. 예를 들어 tool schema가 `commands: string[]`만 허용하면 command 객체 입력도 shell string 배열로 접고, `commands: {command,args}[]`를 허용하면 shell 문법이 없는 명령을 structured argv로 만든다. schema에 없는 UI 설명용 `description`이나 실행 위치 힌트용 `runInTerminal`은 command 객체와 최상위 args에서 제거하되, `cd`, `command -v`, `&&`, pipe, redirect, quote 등 shell 해석이 필요한 명령은 schema가 허용할 때 `commands: ["cd /work && git status"]` 같은 shell string으로 유지한다. CLI route(`adapter: "cli"`)는 native backend tool calling이 없어 이 text tool-call 구조화가 유일한 `tool_calls` 경로이며, backend auto tool-calling 요구 조건으로 요청이 실패하지 않도록 내부 실행 입력의 `tool_choice`를 `"none"`으로 낮춘다. `parallel_tool_calls`, `stream_options`, `store`는 클라이언트 호환성을 위해 수신하지만 현재 Edge 실행 의미에는 반영하지 않는다. 금지: diff --git a/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/code_review_cloud_G07_0.log b/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/code_review_cloud_G07_0.log new file mode 100644 index 0000000..50acb20 --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/code_review_cloud_G07_0.log @@ -0,0 +1,210 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization is review-agent-only. + +## 개요 + +date=2026-07-04 +task=m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md` +- Task ids: + - `client-smoke`: Pi/Cline형 긴 agent prompt와 `tools[]` dev Edge 호출 evidence + - `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서 정책 설명 +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 파일과 evidence path에 대조하고, secret 원문이 tracked 파일이나 review output에 남지 않았는지 확인하세요. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md`와 `PLAN-cloud-G07.md`를 `.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. +4. PASS이고 task group이 `m-openai-compatible-tool-call-boundary-hardening`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Contract Policy Update | [x] | +| [API-2] Dev Smoke Guide And Test Rule | [x] | +| [API-3] Credentialed Dev Runtime Evidence | [x] | + +## 구현 체크리스트 + +- [x] 01번 `agent-task/m-openai-compatible-tool-call-boundary-hardening/01_provider_text_boundary/complete.log`가 존재하고 Roadmap Completion에 `provider-raw-parse`, `unknown-tool-error`, `stream-flush-guard`, `native-preserve`가 포함됐는지 확인한다. (archive `agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/01_provider_text_boundary/complete.log`에서 PASS와 4개 task id 확인) +- [x] `agent-contract/outer/openai-compatible-api.md`가 provider route/provider-pool raw text tool-call 후보의 구조화/차단, unknown/malformed 처리, sentinel sanitize 정책을 설명하도록 갱신한다. +- [x] `docs/edge-local-dev-guide.md`와 `agent-test/dev/edge-smoke.md`에 dev-runtime Pi/Cline형 `tools[]` smoke 기준, evidence 보관 위치, secret 마스킹 원칙을 추가한다. +- [x] 원격 dev-runtime source를 01 완료 source와 동기화하고 dev-runtime Edge/Node binaries를 rebuild한 뒤 `config check`와 필요한 refresh/restart를 수행한다. (01 변경은 `apps/edge/`에 국한, `apps/node/` 무변경이라 Edge 바이너리만 rebuild + Edge serve 재시작; 3개 node는 reconnect 정책으로 유지) +- [x] dev Edge OpenAI-compatible endpoint에 긴 agent prompt와 `tools[]` 요청을 호출해 valid raw text는 `tool_calls`, unknown hallucination은 `tool_validation_error` 또는 retry 후 차단으로 끝나며 raw block이 body/SSE에 남지 않는 evidence를 남긴다. +- [x] `go test ./apps/edge/internal/openai -count=1`와 문서/evidence 검증 명령을 실행한다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/`로 이동한다. +- [x] PASS이고 task group이 `m-openai-compatible-tool-call-boundary-hardening`이면 런타임 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. +- [x] PASS split 작업이므로 이동 후 빈 active parent `agent-task/m-openai-compatible-tool-call-boundary-hardening/`를 제거했다. +- [x] WARN/FAIL이 아니므로 다음 active plan/review 파일 또는 unresolved verification report를 작성하지 않았다. + +## 계획 대비 변경 사항 + +- 계약 문단(API-1)을 계획의 "provider route/provider-pool" 한정 서술보다 넓게 갱신했다. 실제 post-01 코드에서 `collectChatCompletionOutput`/live·buffered stream은 `len(req.Tools) > 0`이면 route 구분 없이 text tool-call을 synthesize/validate하며, `shouldSynthesizeTextToolCalls`(fallback/cli 게이트)는 정의만 있고 호출되지 않는 dead symbol이다. 따라서 계약을 "provider route + CLI route 모두"로 서술하고, 구 계약의 `openai_text_tool_fallback` 예외 조건 서술은 제거했다. `tools[]`가 없으면 합성/파싱하지 않는다는 제외 조건은 유지했다. +- 원격 rebuild 범위를 Edge 바이너리로 한정했다. `git diff --stat b55d1a2..59e55d4 -- apps/`가 01 boundary 변경이 전부 `apps/edge/`(openai 패키지 + `node/mapper.go`)이고 `apps/node/` 무변경임을 보여, 계획의 "Edge/Node binaries rebuild" 중 Node 재빌드/재배포는 불필요로 판단했다. Edge serve만 새 바이너리로 재시작했고 3개 node는 reconnect 정책으로 유지되어 재-bootstrap 없이 connected를 확인했다. +- unknown-tool 라이브 evidence는 2회 시도했다. 1차(자유 유도)에서는 모델이 ``/`{{}}` 구조 블록이 아닌 산문(`lookup_weather(city="Seoul")`)을 내 content로 반환(leak 없음, finish_reason stop)되어 validation-error 경로를 유도하지 못했다. 2차에서 literal `` unknown-tool 블록을 강제 출력하도록 프롬프트해 실제 `tool_validation_error`(`tool "lookup_weather" is not in request tools`) 차단을 라이브로 확인했다. + +## 주요 설계 결정 + +- `docs/openai-compatible-api-contract.md`는 계약 pointer이므로 원문 정책을 복제하지 않고 그대로 두었다(grep으로 raw/tool_validation 문구 0건 확인). source of truth는 `agent-contract/outer/openai-compatible-api.md`. +- credential은 원격 `edge.yaml`의 `bearer_token`을 원격 셸 변수로만 추출해 `Authorization` 헤더에 인라인 주입했고, 명령/로그/evidence 파일/보고 어디에도 원문을 남기지 않았다(evidence dir 대상 token grep 0건). +- dev evidence는 gitignore된 `build/dev-runtime/logs/boundary-smoke-/`(원격)에 request/response 원문을 저장하고, tracked 문서에는 마스킹 요약과 경로만 남겼다. +- 결정적 차단 커버리지(unknown/malformed retry·fail, stream block, sentinel drop, every-candidate validation)는 01의 로컬 unit test 22종이 담당하고, 라이브 dev smoke는 실제 provider-pool 경로에서 no-raw-leak + valid 정규화 + unknown 차단을 보완 검증하는 역할로 분리했다. + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- 01 predecessor completion이 실제로 충족됐는지 확인한다. +- 계약 문서가 `docs/`에 중복 원문을 만들지 않고 `agent-contract`를 source of truth로 유지하는지 확인한다. +- dev smoke evidence가 token/secret 원문을 노출하지 않는지 확인한다. +- raw ``, unknown tool hallucination, `<|mask_end|>` leak 차단 evidence가 남았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### API-1 중간 검증 +```bash +$ rg --sort path -n "provider route에서|raw text|tool_validation_error|mask_end" agent-contract/outer/openai-compatible-api.md docs/openai-compatible-api-contract.md +agent-contract/outer/openai-compatible-api.md:227: ... raw text tool-call 블록을 ... provider route ... CLI route ... 구조화하거나 차단한다. +agent-contract/outer/openai-compatible-api.md:228: ... malformed 블록 ... tool validation 실패로 처리 ... tool_validation_error 로 응답 ... live SSE ... tool_validation_error 이벤트로 스트림을 종료. +agent-contract/outer/openai-compatible-api.md:230: raw /{{...}} 블록과 <|mask_end|> 같은 sentinel ... content/SSE delta에 노출하지 않는다. +# docs/openai-compatible-api-contract.md : 매칭 0건 (pointer 문서, 원문 미복제 확인) +``` + +### API-2 중간 검증 +```bash +$ rg --sort path -n "raw text tool|tool_validation_error|Pi/Cline|mask_end|agent-test/runs" docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md +docs/edge-local-dev-guide.md:281,295,304-306,309 (Raw text tool-call boundary smoke 절차/판정/evidence 위치) +agent-test/dev/edge-smoke.md:109 (필수 검증: Pi/Cline형 tools[] non-stream/stream smoke) +agent-test/dev/edge-smoke.md:123-124 (판정 기준 + agent-test/runs evidence 위치) +``` + +### API-3 원격 preflight / sync / rebuild / restart +```bash +# preflight (재빌드 전): 원격 stale 확인 +$ ssh toki@toki-labs.com '... git log --oneline -1; ports; cp status' +b55d1a2 fix: document and verify provider priority leveling # stale (pre-01) +port 18001/18083/18084/19093 LISTEN; cp_status_http=200 + +# clean-sync → origin/main(01 포함) + Edge 재빌드 + config check +$ ssh toki@toki-labs.com 'zsh -lc "cd /Users/toki/agent-work/iop-dev; git fetch origin main; git reset --hard origin/main; git clean -fd; go build -trimpath -o build/dev-runtime/bin/edge ./apps/edge/cmd/edge; cd build/dev-runtime; ./bin/edge --config edge.yaml config check"' +HEAD is now at 59e55d4 feat: openai-compatible tool call boundary hardening & knowledge tool optimization +edge binary rebuilt: -rwxr-xr-x 22026946 Jul 4 18:44 build/dev-runtime/bin/edge (go1.26.3) +OK edge.yaml + +# Edge serve 재시작 (old pid 97852 → new pid 21555) + reconnect/health +$ ssh toki@toki-labs.com '... kill $(cat edge.pid); nohup ./bin/edge --config edge.yaml serve ...' +healthz_http=200 +CP status nodes: mac-codex-node connected=true, gx10-vllm-node connected=true, onexplayer-lemonade-node connected=true (모두 in_flight=0) +/v1/models: {"data":[{"id":"qwen3.6:35b","object":"model","owned_by":"iop"}]} # (bearer token 인증, 원문 미기록) + +# 최종 remote 상태 +$ ssh toki@toki-labs.com '... git log --oneline -1; config refresh --help; cp status' +59e55d4 ...; refresh_help_ok; cp_status=200 +``` + +### API-3 credentialed boundary smoke (token redacted; evidence dir gitignored) +```bash +# 원격 실행. token은 edge.yaml에서 셸 변수로만 추출, Authorization 헤더 인라인 주입, 파일/로그/보고 미기록. +# evidence: (원격, gitignored) build/dev-runtime/logs/boundary-smoke-20260704_184616/ + +# 1) known tool, non-stream → POST /v1/chat/completions (tools=[shell], tool_choice=auto) +finish_reason=tool_calls has_tool_calls=True names=['shell'] first_args={"commands":["ls -la /tmp"]} +raw_leak_tool_call_tag=False raw_leak_mask_end=False content_preview=(empty) + +# 2) known tool, stream → "stream":true +sse_has_tool_calls_delta=2 sse_finish_tool_calls=1 sse_done=1 +sse_raw_tool_call_tag=0 sse_raw_mask_end=0 sse_validation_error=0 + +# 3) unknown tool, 자유 유도, non-stream → 모델이 산문 출력 (구조 블록 아님) +finish_reason=stop has_tool_calls=False raw_leak_tool_call_tag=False raw_leak_mask_end=False +content_preview=lookup_weather(city="Seoul") # {{}}/ 블록 아님 → content로 반환, leak 없음 + +# 4) unknown tool, literal 강제, non-stream → 차단 확인 +error_type=tool_validation_error error_msg=tool "lookup_weather" is not in request tools +has_tool_calls=False finish_reason=None raw_leak_tool_call_tag=False raw_leak_mask_end=False content_preview=(empty) + +# evidence 파일 token 누출 검사: grep "$TOKEN" logs/boundary-smoke-20260704_184616/ → 0건 +``` + +### 최종 검증 +```bash +$ go test ./apps/edge/internal/openai -count=1 +ok iop/apps/edge/internal/openai 1.567s + +# 결정적 차단 경로 커버리지 (targeted, 22 tests) +$ go test ./apps/edge/internal/openai -run 'TestChatCompletions.*(Unknown|Malformed|TextToolCall|Synthes)|TestValidateToolCallResponse|TestChatCompletionsProviderStreamBlocksMalformedTextToolCall' -count=1 -v +--- PASS x22 (Synthesizes*, Retries*/Fails* Malformed·Unknown, ProviderStreamBlocks Unknown·Malformed, LeavesUnknown*AsContent, ValidatesEveryToolCall, SentinelDrop ...) +ok iop/apps/edge/internal/openai 0.011s + +$ rg --sort path -n "tool_validation_error|raw text tool|mask_end|Pi/Cline" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md +# 3개 위치 모두 매칭 (계약/dev 가이드/dev test-rule) + +# credentialed dev-runtime evidence: 위 "API-3 credentialed boundary smoke" 참조 +# saved output path (원격, gitignored): build/dev-runtime/logs/boundary-smoke-20260704_184616/ +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - implementation deviation: Pass + - verification trust: Pass + - spec conformance: Pass +- 발견된 문제: + - Nit (fixed): `agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/PLAN-cloud-G07.md:1`의 stray `현재` 접두어를 제거했다. + - Nit (fixed): `docs/edge-local-dev-guide.md:286`의 secret placeholder export 예시를 제거하고, `docs/edge-local-dev-guide.md:294`의 stream smoke 및 `docs/edge-local-dev-guide.md:300`의 deterministic unknown raw block fixture를 명시했다. +- 다음 단계: PASS 종결. active PLAN/REVIEW를 log로 아카이브하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/complete.log b/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/complete.log new file mode 100644 index 0000000..77d7dbb --- /dev/null +++ b/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/complete.log @@ -0,0 +1,46 @@ +# Complete - m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke + +## 완료 일시 + +2026-07-04 + +## 요약 + +OpenAI-compatible raw text tool-call boundary 계약 문서와 dev smoke 기준/evidence 정리를 1회 리뷰 루프에서 PASS로 종결했다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | 계약/dev 문서가 provider raw text tool-call 정규화/차단 정책을 반영했고, dev-runtime credentialed smoke evidence와 local Go 검증이 확인됨. | + +## 구현/정리 내용 + +- `agent-contract/outer/openai-compatible-api.md`가 `tools[]` 요청의 provider/CLI raw text tool-call 정규화, unknown/malformed 차단, sentinel sanitize 정책을 설명하도록 갱신됐다. +- `docs/edge-local-dev-guide.md`와 `agent-test/dev/edge-smoke.md`에 Pi/Cline형 `tools[]` boundary smoke 기준, evidence 위치, secret 마스킹 원칙이 추가됐다. +- dev-runtime Edge를 01 완료 source로 동기화하고 Edge binary rebuild/restart 후 known tool 정규화, known stream delta, unknown raw block 차단 evidence를 남겼다. +- 리뷰 중 Nit로 PLAN stray prefix와 dev guide secret placeholder/weak unknown fixture를 직접 정리했다. + +## 최종 검증 + +- `go test ./apps/edge/internal/openai -count=1` - PASS; `ok iop/apps/edge/internal/openai 1.568s` +- `go test ./apps/edge/internal/openai -run 'TestChatCompletions.*(Unknown|Malformed|TextToolCall|Synthes)|TestValidateToolCallResponse|TestChatCompletionsProviderStreamBlocksMalformedTextToolCall' -count=1 -v` - PASS; targeted 22 tests passed, `ok iop/apps/edge/internal/openai 0.010s` +- `rg --sort path -n "tool_validation_error|raw text tool|mask_end|Pi/Cline" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md` - PASS; 계약/dev guide/dev test-rule 모두 매칭 +- `ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com` non-secret remote preflight/evidence shell assertions - PASS; HEAD `59e55d4`, remote checkout clean on `main` tracking `origin/main`, Edge binary/refresh help OK, Control Plane status HTTP 200, evidence dir `build/dev-runtime/logs/boundary-smoke-20260704_184616/` 존재, `auth_marker_files=0`, `tool_validation_error`와 `tool_calls` marker 확인 +- `ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com` non-secret response leak marker file assertions - PASS; `` marker는 `req_unknown_forced.json` 요청 fixture에만 존재하고 response/evidence output에는 `<|mask_end|>`, `Authorization`, `Bearer` marker 없음 + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md` +- Completed task ids: + - `client-smoke`: PASS; evidence=`agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/plan_cloud_G07_0.log`, `agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/code_review_cloud_G07_0.log`; verification=`remote dev-runtime evidence dir build/dev-runtime/logs/boundary-smoke-20260704_184616/`, `ssh non-secret remote preflight/evidence shell assertions` + - `contract-docs`: PASS; evidence=`agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/plan_cloud_G07_0.log`, `agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/code_review_cloud_G07_0.log`; verification=`rg --sort path -n "tool_validation_error|raw text tool|mask_end|Pi/Cline" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/PLAN-cloud-G07.md b/agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/PLAN-cloud-G07.md rename to agent-task/archive/2026/07/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/plan_cloud_G07_0.log diff --git a/agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/CODE_REVIEW-cloud-G07.md b/agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/CODE_REVIEW-cloud-G07.md deleted file mode 100644 index fc307f1..0000000 --- a/agent-task/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/CODE_REVIEW-cloud-G07.md +++ /dev/null @@ -1,134 +0,0 @@ - - -# Code Review Reference - API - -> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** -> The task is NOT complete until every implementation-owned section below is filled in. -> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. -> Fill implementation-owned sections, then stop with active files in place and report ready for review. -> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. -> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. -> Finalization is review-agent-only. - -## 개요 - -date=2026-07-04 -task=m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke, plan=0, tag=API - -## Roadmap Targets - -- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/openai-compatible-tool-call-boundary-hardening.md` -- Task ids: - - `client-smoke`: Pi/Cline형 긴 agent prompt와 `tools[]` dev Edge 호출 evidence - - `contract-docs`: OpenAI-compatible 계약 문서와 dev 운영 문서 정책 설명 -- Completion mode: check-on-pass - -## 이 파일을 읽는 리뷰 에이전트에게 - -> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. - -각 항목의 구현을 실제 파일과 evidence path에 대조하고, secret 원문이 tracked 파일이나 review output에 남지 않았는지 확인하세요. - -1. 판정을 append한다. -2. `CODE_REVIEW-cloud-G07.md`와 `PLAN-cloud-G07.md`를 `.log`로 아카이브한다. -3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다. -4. PASS이고 task group이 `m-openai-compatible-tool-call-boundary-hardening`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [API-1] Contract Policy Update | [ ] | -| [API-2] Dev Smoke Guide And Test Rule | [ ] | -| [API-3] Credentialed Dev Runtime Evidence | [ ] | - -## 구현 체크리스트 - -- [ ] 01번 `agent-task/m-openai-compatible-tool-call-boundary-hardening/01_provider_text_boundary/complete.log`가 존재하고 Roadmap Completion에 `provider-raw-parse`, `unknown-tool-error`, `stream-flush-guard`, `native-preserve`가 포함됐는지 확인한다. -- [ ] `agent-contract/outer/openai-compatible-api.md`가 provider route/provider-pool raw text tool-call 후보의 구조화/차단, unknown/malformed 처리, sentinel sanitize 정책을 설명하도록 갱신한다. -- [ ] `docs/edge-local-dev-guide.md`와 `agent-test/dev/edge-smoke.md`에 dev-runtime Pi/Cline형 `tools[]` smoke 기준, evidence 보관 위치, secret 마스킹 원칙을 추가한다. -- [ ] 원격 dev-runtime source를 01 완료 source와 동기화하고 dev-runtime Edge/Node binaries를 rebuild한 뒤 `config check`와 필요한 refresh/restart를 수행한다. -- [ ] dev Edge OpenAI-compatible endpoint에 긴 agent prompt와 `tools[]` 요청을 호출해 valid raw text는 `tool_calls`, unknown hallucination은 `tool_validation_error` 또는 retry 후 차단으로 끝나며 raw block이 body/SSE에 남지 않는 evidence를 남긴다. -- [ ] `go test ./apps/edge/internal/openai -count=1`와 문서/evidence 검증 명령을 실행한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## 코드리뷰 전용 체크리스트 - -> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. - -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. -- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. -- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. -- [ ] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-openai-compatible-tool-call-boundary-hardening/02+01_contract_dev_smoke/`로 이동한다. -- [ ] PASS이고 task group이 `m-openai-compatible-tool-call-boundary-hardening`이면 런타임 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. -- [ ] WARN/FAIL이면 다음 active plan/review 파일 또는 unresolved verification report를 작성한다. - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 연결 대상: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 자동 후속 불가 이유: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- 01 predecessor completion이 실제로 충족됐는지 확인한다. -- 계약 문서가 `docs/`에 중복 원문을 만들지 않고 `agent-contract`를 source of truth로 유지하는지 확인한다. -- dev smoke evidence가 token/secret 원문을 노출하지 않는지 확인한다. -- raw ``, unknown tool hallucination, `<|mask_end|>` leak 차단 evidence가 남았는지 확인한다. - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -### API-1 중간 검증 -```bash -$ rg --sort path -n "provider route에서|raw text|tool_validation_error|mask_end" agent-contract/outer/openai-compatible-api.md docs/openai-compatible-api-contract.md -(output) -``` - -### API-2 중간 검증 -```bash -$ rg --sort path -n "raw text tool|tool_validation_error|Pi/Cline|mask_end|agent-test/runs" docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md -(output) -``` - -### API-3 중간 검증 -```bash -$ ssh -o BatchMode=yes -o ConnectTimeout=5 toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --branch --short && git log --oneline -1 && test -x build/dev-runtime/bin/edge && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --help >/dev/null && for p in 18001 18083 18084 19093; do lsof -nP -iTCP:$p -sTCP:LISTEN >/dev/null || exit 20; done && curl -fsS -m 3 -o /dev/null http://127.0.0.1:18001/edges/edge-toki-labs-dev/status' -(output) -``` - -### 최종 검증 -```bash -$ go test ./apps/edge/internal/openai -count=1 -(output) - -$ rg --sort path -n "tool_validation_error|raw text tool|mask_end|Pi/Cline" agent-contract/outer/openai-compatible-api.md docs/edge-local-dev-guide.md agent-test/dev/edge-smoke.md -(output) - -$ -(output or saved evidence path) -``` - ---- - -> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** -> If anything is blank, go back and fill it in before saving this file. diff --git a/agent-test/dev/edge-smoke.md b/agent-test/dev/edge-smoke.md index 18b3bcb..4b225e5 100644 --- a/agent-test/dev/edge-smoke.md +++ b/agent-test/dev/edge-smoke.md @@ -106,6 +106,7 @@ mac-mlx-vllm provider는 mac-codex-node 소속 resource로, Edge host와 같은 - 변경한 edge 패키지 또는 `go test ./apps/edge/...`를 실행한다. - registry, service, transport, console, HTTP/A2A 입력 표면을 바꾼 경우 edge-node 메시지 2회 왕복과 command 응답을 확인한다. - OpenAI-compatible 경계를 바꾼 경우 dev `18083` 기준 `iop-edge smoke openai` 또는 동등한 `/healthz`, `/v1/models`, `/v1/responses` 확인으로 edge service와 node adapter 경로 수렴을 확인한다. +- OpenAI-compatible tool-call 경계(`apps/edge/internal/openai/**`의 text tool-call 합성/validation)를 바꾼 경우 dev `18083` `/v1/chat/completions`에 Pi/Cline형 `tools[]` 요청을 non-stream/stream 각각 최소 1회 보내 raw text tool-call boundary smoke를 수행한다. token은 원격 환경 변수에서 주입하고 원문을 명령/로그/보고에 남기지 않는다. - bootstrap/artifact 경계를 바꾼 경우 dev artifact/base URL 후보 `18082`가 local/test `18080` field baseline을 덮어쓰지 않는지 확인한다. ## 보조 검증 @@ -119,6 +120,8 @@ mac-mlx-vllm provider는 mac-codex-node 소속 resource로, Edge host와 같은 - node 로컬 `[node-message]` payload 라인 목록과 edge `[node-*-message]` payload 라인 목록이 run별로 내용/순서까지 동일해야 한다. - edge complete는 같은 run의 마지막 `[node-*-message]` 이후에만 정상이다. - OpenAI-compatible smoke에서 `/healthz`, `/v1/models`, `/v1/responses`가 기대 상태로 응답한다. +- raw text tool-call boundary smoke: Pi/Cline형 `tools[]` 요청에서 응답 body와 SSE delta 어디에도 ``, `{{`, `<|mask_end|>` 원문이 성공 content로 남지 않는다. 요청 `tools[]`에 있는 valid raw text tool-call은 `message.tool_calls`(또는 stream `delta.tool_calls`)와 `finish_reason: "tool_calls"`로 정규화되고, unknown tool hallucination이나 malformed 블록은 성공 content가 아니라 non-stream `tool_validation_error`(HTTP 502) 또는 SSE `tool_validation_error` 이벤트로 끝난다. non-stream/strict buffered stream은 bounded retry 후 차단을 확인한다. 세부 payload와 계약은 `docs/edge-local-dev-guide.md`의 Raw text tool-call boundary smoke와 `agent-contract/outer/openai-compatible-api.md`를 따른다. +- raw boundary smoke evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`)나 code-review output path에 저장하고, 저장물에도 token 원문을 남기지 않는다. - provider-pool dispatch는 `in_flight >= capacity`인 provider를 후보에서 제외하고, 남은 후보 중 가장 낮은 `in_flight` 레벨을 먼저 선택한다. 같은 `in_flight` 레벨 안에서는 낮은 priority 값과 rotation으로 분산한다. - dev-runtime capacity smoke는 `/v1/responses`와 `/v1/chat/completions` 각각에 provider capacity 총합 + 1개 동시 요청을 보내고, Control Plane status의 `provider_snapshots`에서 총 `in_flight`가 capacity 총합에 도달하며 `queued`가 1 이상 잡히는지 확인한다. 현재 `gx10-vllm=4`, `onexplayer-lemonade=3`, `mac-mlx-vllm=2`이면 endpoint별 10개 동시 요청에서 총 `in_flight=9`, `queued>=1` 관측을 기준으로 한다. 13개 동시 확장 smoke에서는 총 `in_flight=9`, `queued>=4` 관측을 기대한다. - capacity smoke 완료 후 대상 provider의 `in_flight=0`, `queued=0` 회복을 확인한다. diff --git a/docs/edge-local-dev-guide.md b/docs/edge-local-dev-guide.md index 120e923..975d9bb 100644 --- a/docs/edge-local-dev-guide.md +++ b/docs/edge-local-dev-guide.md @@ -276,6 +276,43 @@ curl -fsS http://toki-labs.com:18083/v1/chat/completions \ 현재 공개 API는 개별 request의 최종 `node_id`를 응답에 노출하지 않는다. 요청별 배정을 확정해야 할 때는 Edge dispatch trace/log를 추가한 뒤 판정한다. +### Raw text tool-call boundary smoke + +Pi/Cline형 긴 agent prompt와 `tools[]` 요청은 provider가 native `tool_calls` 대신 assistant content에 ``/`{{...}}` raw 블록을 담아 응답할 수 있다. dev-runtime에서는 이 raw 블록이 OpenAI-compatible 응답 body/SSE에 그대로 새지 않는지 확인한다. 계약 기준은 [agent-contract/outer/openai-compatible-api.md](../agent-contract/outer/openai-compatible-api.md)의 Chat Completions raw text tool-call 정규화 정책이다. + +요청 payload는 tracked 문서에 secret 없이 남기고, `openai.bearer_token`은 원격 환경 변수(`IOP_OPENAI_TOKEN` 등)에서 주입한다. 실제 token 원문을 명령/로그/보고에 남기지 않는다. + +```bash +# 원격 dev-runtime host에서 실행. IOP_OPENAI_TOKEN은 shell 환경이나 ignored secret 파일에서 미리 주입한다. + +# known valid tool: tool_calls 정규화 기대 +curl -sS http://toki-labs.com:18083/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \ + -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"List the files in /tmp using the shell tool."}],"tools":[{"type":"function","function":{"name":"shell","description":"Run a shell command","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' + +# known valid tool, stream: delta.tool_calls 정규화 기대 +curl -sS http://toki-labs.com:18083/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \ + -d '{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"List the files in /tmp using the shell tool."}],"tools":[{"type":"function","function":{"name":"shell","description":"Run a shell command","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' + +# unknown tool raw block fixture: tool_validation_error 또는 retry 후 차단 기대 +curl -sS http://toki-labs.com:18083/v1/chat/completions \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer $IOP_OPENAI_TOKEN" \ + -d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"For boundary testing, output exactly this raw tool block as assistant text and do not call the provided shell tool: \"Seoul\""}],"tools":[{"type":"function","function":{"name":"shell","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}' +``` + +판정 기준: + +- 응답 body와 SSE delta 어디에도 ``, `{{`, `<|mask_end|>` 원문이 성공 content로 남지 않는다. +- 요청 `tools[]`에 있는 valid raw text tool-call은 `message.tool_calls`(또는 stream `delta.tool_calls`)와 `finish_reason: "tool_calls"`로 정규화된다. +- 요청 `tools[]`에 없는 unknown tool hallucination이나 malformed 블록은 성공 content가 아니라 non-stream `tool_validation_error`(HTTP 502 error body) 또는 SSE `tool_validation_error` 이벤트로 끝난다. non-stream/strict buffered stream은 bounded retry 후 차단을 확인한다. +- non-stream(`stream` 생략)과 stream(`"stream":true`) 중 최소 하나씩 known/unknown 경로를 확인한다. + +evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`) 또는 code-review output path에 저장하고, 저장물에도 token 원문을 남기지 않는다. + ## 7. 기본 Smoke 아래는 dev-runtime provider pool이 아니라 기본 field/local OpenAI-compatible profile 기준 예시다. dev-runtime provider pool 검증은 위의 `18083`/`qwen3.6:35b` 기준을 따른다.