Stream Gate 준비 실패에서도 요청 terminal 관측 계약을 지키고, direct provider identity 마이그레이션 누락으로 기존 검증과 운영 설정이 깨지지 않게 한다.
188 lines
9.5 KiB
Markdown
188 lines
9.5 KiB
Markdown
# Edge Local Quickstart
|
|
|
|
이 문서는 Edge를 로컬에서 빌드하고 실행해 Node를 연결하고, OpenAI-compatible smoke까지 확인하는 최소 절차다.
|
|
|
|
설정 기준은 `edge.yaml`이다. 주소는 환경 변수로 흩뿌리지 않는다.
|
|
|
|
## 1. Package
|
|
|
|
```bash
|
|
make build
|
|
```
|
|
|
|
## 2. Edge 설정
|
|
|
|
```bash
|
|
rm -rf "$HOME/iop-edge-test"
|
|
mkdir -p "$HOME/iop-edge-test"
|
|
tar -xzf build/packages/iop-edge-<platform>.tar.gz -C "$HOME/iop-edge-test"
|
|
cd "$HOME/iop-edge-test/iop-edge-<platform>"
|
|
./iop-edge config init
|
|
```
|
|
|
|
`edge.yaml`에서 아래 값만 맞춘다. 모델만 필요하면 바꾼다.
|
|
|
|
### Provider-first 구성 (권장)
|
|
|
|
Provider-first는 `models[]`로 라우팅 키를 정의하고 `nodes[].providers[]`로 provider 후보를 선언한다. 두 구조를 `models[].providers[provider_id]` 키로 연결한다.
|
|
|
|
```yaml
|
|
models:
|
|
# Top-level catalog: canonical routing key → provider-pool mapping.
|
|
# models[].id is the external model id; providers maps provider id → served model.
|
|
- id: "gemma4:26b"
|
|
providers:
|
|
ollama-gemma: "gemma4:26b"
|
|
|
|
edge:
|
|
id: "edge-1"
|
|
name: "Edge 1"
|
|
|
|
server:
|
|
listen: "0.0.0.0:19090"
|
|
|
|
bootstrap:
|
|
listen: "0.0.0.0:18080"
|
|
artifact_dir: "artifacts"
|
|
|
|
refresh:
|
|
enabled: true
|
|
listen: "127.0.0.1:19093"
|
|
|
|
# Legacy adapter/target fallback (backward compat only).
|
|
# New deploys should use models[] + nodes[].providers[].
|
|
openai:
|
|
enabled: true
|
|
listen: "0.0.0.0:18081"
|
|
provider_id: "ollama-gemma"
|
|
adapter: "ollama"
|
|
target: "gemma4:26b"
|
|
|
|
nodes:
|
|
- id: "node-ollama-1"
|
|
agent_kind: "generic-node"
|
|
providers:
|
|
- id: "ollama-gemma"
|
|
type: "ollama"
|
|
category: "local_inference"
|
|
base_url: "http://127.0.0.1:11434"
|
|
models:
|
|
- "gemma4:26b"
|
|
capacity: 2
|
|
```
|
|
|
|
`models[]`를 사용할 경우 `openai.adapter`/`openai.target`은 하향 호환 fallback이다. 실제 routing은 `nodes[].providers[].id`를 기준으로 provider-pool에서 선택된다.
|
|
|
|
확인:
|
|
|
|
```bash
|
|
./iop-edge --config edge.yaml env
|
|
./iop-edge --config edge.yaml config check
|
|
```
|
|
|
|
## 3. Node 등록
|
|
|
|
```bash
|
|
./iop-edge --config edge.yaml node register node-ollama-1 \
|
|
--adapter ollama \
|
|
--ollama-base-url http://127.0.0.1:11434
|
|
```
|
|
|
|
출력된 OS별 bootstrap 명령 원문을 보관한다. 명령 원문에는 실제 token이 포함되므로 tracked 문서에는 기록하지 않는다.
|
|
|
|
## 4. Edge 실행
|
|
|
|
```bash
|
|
mkdir -p logs run
|
|
nohup ./iop-edge --config edge.yaml serve > logs/edge.stdout.log 2>&1 &
|
|
echo $! > run/iop-edge.pid
|
|
```
|
|
|
|
확인:
|
|
|
|
```bash
|
|
# Control Plane에 연결된 Edge 상태 조회
|
|
curl -fsS http://<control-plane-host>:18000/edges
|
|
```
|
|
|
|
## 5. Node 실행
|
|
|
|
3단계에서 출력된 bootstrap 명령을 Node host에서 그대로 실행한다. Linux/macOS는 generated `curl | bash` 명령을 사용하고, Windows native PowerShell은 generated `.ps1` bootstrap과 `Start-IopNode` 함수를 사용한다.
|
|
|
|
Node 연결 확인:
|
|
|
|
```bash
|
|
curl -fsS http://<control-plane-host>:18000/edges/<edge-id>/status
|
|
```
|
|
|
|
## 6. 기본 Smoke
|
|
|
|
```bash
|
|
curl -fsS http://<edge-host>:18081/v1/models
|
|
|
|
./iop-edge --config edge.yaml smoke openai \
|
|
--model gemma4:26b \
|
|
--base-url http://<edge-host>:18081 \
|
|
--timeout 60s
|
|
```
|
|
|
|
외부 OpenAI-compatible client base URL:
|
|
|
|
```text
|
|
http://<edge-host>:18081/v1
|
|
```
|
|
|
|
## 7. Thinking/Reasoning 제어 smoke
|
|
|
|
`/v1/chat/completions` 요청은 OpenAI-compatible field를 기본으로 사용한다. Provider-pool pure `passthrough`는 selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field를 보존해야 하며, `chat_template_kwargs` 같은 provider-native option을 IOP allowlist로 막지 않는다. `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`은 normalized backend 또는 provider별 차이를 보완하기 위한 IOP 확장 field다.
|
|
|
|
- dev GX10 `laguna-s:2.1` 공식 sampling baseline은 Laguna S 2.1 모델 카드의 vLLM recipe를 따른다: `temperature=0.7`, `top_p=0.95`, model `generation_config`의 `top_k=20`.
|
|
- GX10 공식 근거: https://huggingface.co/poolside/Laguna-S-2.1
|
|
- dev OneXPlayer/RTX5090 `ornith:35b` sampling baseline은 `temperature=0.6`, `top_p=0.95`, `top_k=20`을 유지한다. 공식 예제에 없는 non-neutral `repeat_penalty`는 임의로 추가하지 않는다. RTX5090 saved recipe의 `--repeat-penalty 1.0`과 `--min-p 0.00`은 출력을 바꾸지 않는 neutral runtime serialization로만 유지한다.
|
|
- Ornith 공식 근거: https://huggingface.co/deepreinforce-ai/Ornith-1.0-35B
|
|
- 이 값은 caller가 sampling field를 생략했을 때 쓰는 provider 기본값이다. caller가 명시한 sampling field가 있으면 요청값이 우선한다.
|
|
- dev GX10 vLLM은 `poolside/Laguna-S-2.1-NVFP4`와 quantization-matched `Laguna-S-2.1-DFlash-NVFP4`를 사용하고 `--tool-call-parser poolside_v1`, `--reasoning-parser poolside_v1`, `--chat-template /run/iop/laguna-s-2.1-thinking.jinja`, `--default-chat-template-kwargs '{"enable_thinking":true}'`, `--override-generation-config '{"temperature":0.7,"top_p":0.95}'`로 설정한다.
|
|
- GX10 host의 `/home/toki/iop-gx10-vllm/laguna-s-2.1-thinking.jinja`는 container에 read-only bind한다. stock generation prefix `<think>`가 첫 생성 토큰 `</think>`를 유도해 reasoning이 비는 현상이 재현됐으므로, dev template은 `<think>\n`을 사용한다.
|
|
- dev OneXPlayer/RTX5090 Lemonade는 해당 Ornith recipe의 `llamacpp_args`에 `--temp 0.6 --top-p 0.95 --top-k 20`을 저장한다. RTX5090 수동 toggle은 이 sampling 값과 함께 `preserve_thinking=true`, unified KV, Q8 KV, neutral min-p/repeat-penalty를 exact profile로 검증한다.
|
|
- Laguna reasoning은 provider-native `reasoning` field로 나가며 Pi `openai-completions`가 이를 `thinking_delta`로 소비한다. Pi Laguna profile은 thinking level을 `chat_template_kwargs.enable_thinking`으로 전달하고 `preserve_thinking=true`로 이전 assistant reasoning을 유지한다.
|
|
- 출력 smoke는 같은 요청을 Pi `high`와 `off`로 대조한다. `high`에서는 `thinking_start`/`thinking_delta`/`thinking_end`와 최종 text가, `off`에서는 thinking event 0개와 최종 text가 나와야 한다. agentic multi-turn에서는 tool-call 전후 reasoning, tool result, 최종 text까지 확인한다.
|
|
- 현재 dev-corp provider-pool device mapping은 `gemma4:26b` -> Mac Studio provider capacity `5`, `ornith:35b` -> DGX Spark 01/02 provider 합산 capacity `8`이다. 세부 endpoint와 runtime args는 `agent-test/inventory-dev-corp.yaml`을 기준으로 한다.
|
|
- dev-corp provider-pool 안정 smoke: 기본 smoke에서는 `think`, `reasoning_effort`, `thinking_token_budget`을 생략하고 현재 provider 기본값을 유지한다. Provider-native passthrough를 검증할 때는 selected provider가 직접 지원하는 field를 그대로 보낸다.
|
|
- 현재 dev-corp public capacity smoke 표준은 public OpenAI-compatible base `https://digitalplatform.iop.ai.kr/v1`의 `/chat/completions`에서 `ornith:35b` 9/6 동시 요청과 `gemma4:26b` 9/6 동시 요청을 각각 확인하는 방식이다.
|
|
- 일반 표준 caller의 `stream=false` 측정은 `/v1/chat/completions`에서 요청 파라미터만으로 확인한다.
|
|
- `include_reasoning=false`는 non-provider normalized route의 hide 동작 기준이다. dev-corp provider-pool pure `passthrough`에서는 client가 reasoning field를 선택적으로 무시/제거한다.
|
|
- `think=false` 또는 `reasoning_effort=none`은 hide-only 옵션이 아니라 thinking disable 요청이다. Provider-native field가 있는 경우 해당 field를 우선 사용해 passthrough 보존을 검증한다.
|
|
- Provider-pool passthrough 파라미터의 세부 계약과 금지/허용 범위는 `agent-contract/outer/openai-compatible-api.md`를 기준으로 한다.
|
|
|
|
예시 (dev-corp `gemma4:26b` provider-pool non-stream 측정, think 생략):
|
|
|
|
아래 `18081` 포트는 local Edge 예시다. dev-corp public smoke에서는 base URL을 `https://digitalplatform.iop.ai.kr/v1`로 바꾼다. Direct Edge listener `http://digitalplatform.iop.ai.kr:18086/v1`도 동작하지만 사용자-facing 기본값은 포트 없는 public URL이다.
|
|
|
|
```bash
|
|
curl -fsS http://<edge-host>:18081/v1/chat/completions \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'Authorization: Bearer <token>' \
|
|
-d '{"model":"gemma4:26b","messages":[{"role":"user","content":"hello"}],"stream":false}'
|
|
```
|
|
|
|
예시 (non-provider normalized route에서 response reasoning_content 숨김):
|
|
|
|
```bash
|
|
curl -fsS http://<edge-host>:18081/v1/chat/completions \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"model":"<model-alias>","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
|
|
```
|
|
|
|
## 8. Raw text tool-call boundary smoke
|
|
|
|
긴 agent prompt와 `tools[]` 요청은 provider가 native `tool_calls` 대신 assistant content에 raw 블록을 담아 응답할 수 있다. 계약 기준은 `agent-contract/outer/openai-compatible-api.md`의 Chat Completions raw text tool-call 정규화 정책이다.
|
|
|
|
요청 payload는 tracked 문서에 secret 없이 남기고, token은 원격 환경 변수에서 주입한다. 실제 token 원문을 명령/로그/보고에 남기지 않는다.
|
|
|
|
판정 기준:
|
|
|
|
- 응답 body와 SSE delta 어디에도 raw tool block 원문이 성공 content로 남지 않는다.
|
|
- 요청 `tools[]`에 있는 valid tool-call은 `message.tool_calls` 또는 stream `delta.tool_calls`와 `finish_reason: "tool_calls"`로 정규화된다.
|
|
- 요청 `tools[]`에 없는 unknown tool hallucination이나 malformed 블록은 success content가 아니라 `tool_validation_error`로 끝난다.
|
|
|
|
evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`) 또는 code-review output path에 저장한다.
|