Phase 7a-2: id-9b Modelfile (no-think) + 이드 페르소나 강화
- Modelfile.id-9b 생성: qwen3.5:9b-q8_0 기반, no-think ChatML 템플릿 - 모든 Ollama 호출(8개 노드+2개 Python)에 system: '/no_think' 이중 방어 - Call Haiku/Opus: 이드 페르소나 [자아]/[성격]/[말투]/[응답 원칙]/[기억] 강화 - Call Qwen Response: system 파라미터 분리 + 경량 자아 추가 - Claude API 노드에는 /no_think 미적용 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -161,4 +161,4 @@ DEVONthink 4 (맥미니):
|
|||||||
- response_tier는 Qwen v2 분류기 출력. 기존 complexity 기반 라우팅은 레거시 호환
|
- response_tier는 Qwen v2 분류기 출력. 기존 complexity 기반 라우팅은 레거시 호환
|
||||||
- n8n v2.11+ Code 노드는 Task Runner 샌드박스(VM)에서 실행됨. `$http.request()`/`this.helpers.httpRequest()`/`fetch()`/`AbortController`/`URL` 사용 불가. `require('http')`/`require('https')`/`require('url')` 사용 (NODE_FUNCTION_ALLOW_BUILTIN=crypto,http,https,url). 각 Code 노드에 `httpPost`/`httpPut` 헬퍼 함수 인라인 정의
|
- n8n v2.11+ Code 노드는 Task Runner 샌드박스(VM)에서 실행됨. `$http.request()`/`this.helpers.httpRequest()`/`fetch()`/`AbortController`/`URL` 사용 불가. `require('http')`/`require('https')`/`require('url')` 사용 (NODE_FUNCTION_ALLOW_BUILTIN=crypto,http,https,url). 각 Code 노드에 `httpPost`/`httpPut` 헬퍼 함수 인라인 정의
|
||||||
- 샌드박스 사용 가능: `Buffer`, `setTimeout`, `TextEncoder`, `FormData`, `$env`, `$input`, `$()`, `$getWorkflowStaticData()`, `$json`, `require('url').parse()` (new URL 불가), `console`
|
- 샌드박스 사용 가능: `Buffer`, `setTimeout`, `TextEncoder`, `FormData`, `$env`, `$input`, `$()`, `$getWorkflowStaticData()`, `$json`, `require('url').parse()` (new URL 불가), `console`
|
||||||
- id-9b:latest가 기본 GPU 모델. qwen3.5:9b-q8_0은 레거시 백업이며 검증 완료 후 삭제 예정
|
- id-9b:latest가 기본 GPU 모델 (Modelfile: `ollama/Modelfile.id-9b`, FROM qwen3.5:9b-q8_0 + no-think 템플릿). 모든 Ollama 호출에 `system: '/no_think'` 이중 방어 적용
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ type 판단:
|
|||||||
try:
|
try:
|
||||||
resp = httpx.post(
|
resp = httpx.post(
|
||||||
f"{GPU_OLLAMA_URL}/api/generate",
|
f"{GPU_OLLAMA_URL}/api/generate",
|
||||||
json={"model": "id-9b:latest", "prompt": prompt, "stream": False, "format": "json", "think": False},
|
json={"model": "id-9b:latest", "system": "/no_think", "prompt": prompt, "stream": False, "format": "json", "think": False},
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
return json.loads(resp.json()["response"])
|
return json.loads(resp.json()["response"])
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
"name": "IMAP Trigger",
|
"name": "IMAP Trigger",
|
||||||
"type": "n8n-nodes-base.imapEmail",
|
"type": "n8n-nodes-base.imapEmail",
|
||||||
"typeVersion": 2,
|
"typeVersion": 2,
|
||||||
"position": [0, 300]
|
"position": [
|
||||||
|
0,
|
||||||
|
300
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -32,17 +35,23 @@
|
|||||||
"name": "Parse Mail",
|
"name": "Parse Mail",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
"position": [220, 300]
|
"position": [
|
||||||
|
220,
|
||||||
|
300
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"jsCode": "function httpPost(url, body, { timeout = 15000, headers = {} } = {}) {\n return new Promise((resolve, reject) => {\n const data = JSON.stringify(body);\n const u = require('url').parse(url);\n const mod = require(u.protocol === 'https:' ? 'https' : 'http');\n const req = mod.request({\n hostname: u.hostname, port: u.port, path: u.path,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data), ...headers }\n }, (res) => {\n let body = '';\n res.on('data', c => body += c);\n res.on('end', () => {\n if (res.statusCode >= 400) return reject(new Error(url + ' \\u2192 ' + res.statusCode + ': ' + body.slice(0, 200)));\n try { resolve(JSON.parse(body)); } catch(e) { reject(new Error('JSON parse error: ' + body.slice(0, 200))); }\n });\n });\n req.on('error', reject);\n req.setTimeout(timeout, () => { req.destroy(); reject(new Error(url + ' \\u2192 timeout after ' + timeout + 'ms')); });\n req.write(data);\n req.end();\n });\n}\n\nconst item = $input.first().json;\nconst prompt = `메일을 분류하고 요약하세요. JSON만 출력.\n\n{\n \"summary\": \"한국어 2~3문장 요약\",\n \"label\": \"업무|개인|광고|알림\",\n \"has_events\": true/false,\n \"has_tasks\": true/false\n}\n\n보낸 사람: ${item.from}\n제목: ${item.subject}\n본문: ${item.body.substring(0, 3000)}`;\n\ntry {\n const r = await httpPost(`${$env.GPU_OLLAMA_URL}/api/generate`,\n { model: 'id-9b:latest', prompt, stream: false, format: 'json', think: false },\n { timeout: 15000 }\n );\n const cls = JSON.parse(r.response);\n return [{ json: { ...item, summary: cls.summary || item.subject, label: cls.label || '알림', has_events: cls.has_events || false, has_tasks: cls.has_tasks || false } }];\n} catch(e) {\n return [{ json: { ...item, summary: item.subject, label: '알림', has_events: false, has_tasks: false } }];\n}"
|
"jsCode": "function httpPost(url, body, { timeout = 15000, headers = {} } = {}) {\n return new Promise((resolve, reject) => {\n const data = JSON.stringify(body);\n const u = require('url').parse(url);\n const mod = require(u.protocol === 'https:' ? 'https' : 'http');\n const req = mod.request({\n hostname: u.hostname, port: u.port, path: u.path,\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data), ...headers }\n }, (res) => {\n let body = '';\n res.on('data', c => body += c);\n res.on('end', () => {\n if (res.statusCode >= 400) return reject(new Error(url + ' \\u2192 ' + res.statusCode + ': ' + body.slice(0, 200)));\n try { resolve(JSON.parse(body)); } catch(e) { reject(new Error('JSON parse error: ' + body.slice(0, 200))); }\n });\n });\n req.on('error', reject);\n req.setTimeout(timeout, () => { req.destroy(); reject(new Error(url + ' \\u2192 timeout after ' + timeout + 'ms')); });\n req.write(data);\n req.end();\n });\n}\n\nconst item = $input.first().json;\nconst prompt = `메일을 분류하고 요약하세요. JSON만 출력.\n\n{\n \"summary\": \"한국어 2~3문장 요약\",\n \"label\": \"업무|개인|광고|알림\",\n \"has_events\": true/false,\n \"has_tasks\": true/false\n}\n\n보낸 사람: ${item.from}\n제목: ${item.subject}\n본문: ${item.body.substring(0, 3000)}`;\n\ntry {\n const r = await httpPost(`${$env.GPU_OLLAMA_URL}/api/generate`,\n { model: 'id-9b:latest', system: '/no_think', prompt, stream: false, format: 'json', think: false },\n { timeout: 15000 }\n );\n const cls = JSON.parse(r.response);\n return [{ json: { ...item, summary: cls.summary || item.subject, label: cls.label || '알림', has_events: cls.has_events || false, has_tasks: cls.has_tasks || false } }];\n} catch(e) {\n return [{ json: { ...item, summary: item.subject, label: '알림', has_events: false, has_tasks: false } }];\n}"
|
||||||
},
|
},
|
||||||
"id": "m1000001-0000-0000-0000-000000000003",
|
"id": "m1000001-0000-0000-0000-000000000003",
|
||||||
"name": "Summarize & Classify",
|
"name": "Summarize & Classify",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
"position": [440, 300]
|
"position": [
|
||||||
|
440,
|
||||||
|
300
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -54,7 +63,10 @@
|
|||||||
"name": "Save to mail_logs",
|
"name": "Save to mail_logs",
|
||||||
"type": "n8n-nodes-base.postgres",
|
"type": "n8n-nodes-base.postgres",
|
||||||
"typeVersion": 2.5,
|
"typeVersion": 2.5,
|
||||||
"position": [660, 300],
|
"position": [
|
||||||
|
660,
|
||||||
|
300
|
||||||
|
],
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"postgres": {
|
"postgres": {
|
||||||
"id": "KaxU8iKtraFfsrTF",
|
"id": "KaxU8iKtraFfsrTF",
|
||||||
@@ -70,7 +82,10 @@
|
|||||||
"name": "Embed & Save",
|
"name": "Embed & Save",
|
||||||
"type": "n8n-nodes-base.code",
|
"type": "n8n-nodes-base.code",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
"position": [880, 300]
|
"position": [
|
||||||
|
880,
|
||||||
|
300
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -108,7 +123,10 @@
|
|||||||
"name": "Is Important?",
|
"name": "Is Important?",
|
||||||
"type": "n8n-nodes-base.if",
|
"type": "n8n-nodes-base.if",
|
||||||
"typeVersion": 2.2,
|
"typeVersion": 2.2,
|
||||||
"position": [1100, 300]
|
"position": [
|
||||||
|
1100,
|
||||||
|
300
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -125,7 +143,10 @@
|
|||||||
"name": "Notify Chat",
|
"name": "Notify Chat",
|
||||||
"type": "n8n-nodes-base.httpRequest",
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
"typeVersion": 4.2,
|
"typeVersion": 4.2,
|
||||||
"position": [1320, 200]
|
"position": [
|
||||||
|
1320,
|
||||||
|
200
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
@@ -200,4 +221,4 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"executionOrder": "v1"
|
"executionOrder": "v1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -105,6 +105,7 @@ def translate_and_summarize(title: str, content: str, lang: str) -> dict:
|
|||||||
f"{GPU_OLLAMA_URL}/api/generate",
|
f"{GPU_OLLAMA_URL}/api/generate",
|
||||||
json={
|
json={
|
||||||
"model": "id-9b:latest",
|
"model": "id-9b:latest",
|
||||||
|
"system": "/no_think",
|
||||||
"prompt": f"다음 기사를 2~3문장으로 요약하세요:\n\n제목: {title}\n본문: {content[:3000]}",
|
"prompt": f"다음 기사를 2~3문장으로 요약하세요:\n\n제목: {title}\n본문: {content[:3000]}",
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"think": False,
|
"think": False,
|
||||||
|
|||||||
15
ollama/Modelfile.id-9b
Normal file
15
ollama/Modelfile.id-9b
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM qwen3.5:9b-q8_0
|
||||||
|
|
||||||
|
SYSTEM "/no_think"
|
||||||
|
|
||||||
|
PARAMETER temperature 0.7
|
||||||
|
PARAMETER num_ctx 8192
|
||||||
|
|
||||||
|
TEMPLATE """{{- if .System }}<|im_start|>system
|
||||||
|
{{ .System }}<|im_end|>
|
||||||
|
{{ end }}{{- range .Messages }}{{- if eq .Role "user" }}<|im_start|>user
|
||||||
|
{{ .Content }}<|im_end|>
|
||||||
|
{{ else if eq .Role "assistant" }}<|im_start|>assistant
|
||||||
|
{{ .Content }}<|im_end|>
|
||||||
|
{{ end }}{{- end }}<|im_start|>assistant
|
||||||
|
"""
|
||||||
Reference in New Issue
Block a user