stocks: 미국 주식 지원(개별 종목) + datacollect LLM 직접 호출 + 백엔드 기본 NAS

미국 주식 (개별 종목 조회 — add/check/judge/analysis/position):
- marketUtils: KR(6자리)/US(티커) 시장 판별, 통화·시총 포매팅($/원, B·T/억·조)
- yahooClient: US는 raw 티커(.KQ/.KS 미부착), 심볼 라우팅 분리
- yahooFundamentals: Yahoo quoteSummary(무키) cookie+crumb 핸드셰이크로 ROE/PER/PBR/마진/성장/부채 fetch (429·crumb 하드닝)
- fundamentalsRouter: 심볼별 Naver(KR)/Yahoo(US) 분기
- criteriaEval: market-aware — US는 유보율 미보고라 유동성·안정성을 부채비율(D/E)·시총($B)으로 대체
- types: Stock.market 필드; slashStocks/llmJudge: 통화·라우팅·프롬프트 US 대응
- 테스트: stocksMarket + criteriaEval US 케이스 (전체 통과)

datacollect/llm.ts: LLM을 확장(PC)에서 직접 호출(과거 bridge /api/lm 프록시 제거)
 → 백엔드 NAS 이전 시 PC→NAS→PC 실패 제거, 스크래핑=NAS·LLM 추론=PC 분리

package.json: datacollectBridge 기본값 nas + https://dc.koritips.com; 버전 2.2.261

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:43:04 +09:00
parent 4288b212d5
commit e6d780be61
13 changed files with 531 additions and 119 deletions
+52 -36
View File
@@ -1,17 +1,45 @@
/**
* Datacollect LLM 호출 인프라 — bridge `/api/lm` 프록시 통해 OpenAI 호환 chat
* completion 단발 호출.
* Datacollect LLM 호출 인프라 — OpenAI 호환 chat completion 단발 호출.
*
* v2.2.201 에서 slashRouter.ts 에서 분리. 옛 위치는 slashRouter.callLmSynthesis
* 였으나 datacollect handlers + teamops/communication 양쪽이 import 하므로
* 별도 인프라 모듈로.
*
* v2.2.261: LLM 을 **확장(PC)에서 직접** 호출한다(과거엔 bridge `/api/lm` 프록시 경유).
* 확장은 PC 의 Node 프로세스라 `g1nation.ollamaUrl`(보통 127.0.0.1) 이 PC 의 LM Studio/
* Ollama 를 정확히 가리키고, Node fetch 는 CORS 가 없어 프록시가 불필요하다. Bridge 를
* NAS 로 옮긴 뒤 프록시 경유 시 PC→NAS→PC 로 LLM 을 찾으려다(=NAS 의 127.0.0.1) 실패하던
* 문제를 제거 — 스크래핑/저장은 NAS, LLM 추론은 PC 로 깔끔히 분리된다.
*
* `repairKoreanGlitches` 는 callLmSynthesis 출력 위생용 — 모델이 한·영 혼합
* 토큰 깨짐 (예: "핵ess") 을 뱉으면 LLM 1회 추가 호출로 교정.
*/
import * as vscode from 'vscode';
import { bridgeFetch, BRIDGE_API } from './bridgeClient';
/**
* LM 서버(OpenAI 호환 `/v1/chat/completions`)를 확장에서 직접 호출. LM Studio/Ollama 는
* 인증이 없으므로 토큰 불필요. 타임아웃 가드 포함, 비정상 응답이면 throw.
*/
async function lmChat(lmUrl: string, payload: unknown, timeoutMs = 120_000): Promise<any> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(`${lmUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal,
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`LM 서버 오류 ${res.status} (${lmUrl}): ${text.slice(0, 200)}`);
}
return await res.json();
} finally {
clearTimeout(timer);
}
}
/**
* 모델 출력 붕괴(degeneration) 감지 — 작은/약한 모델이 긴 한국어 입력에서
@@ -43,23 +71,17 @@ async function callLmOnce(
lmUrl: string, model: string, sys: string, prompt: string,
sampling: { temperature: number; repeat_penalty: number; top_k: number },
): Promise<string> {
const res = await bridgeFetch<any>(BRIDGE_API.lm.proxy, {
method: 'POST',
body: JSON.stringify({
url: `${lmUrl}/v1/chat/completions`,
payload: {
model,
messages: [
{ role: 'system', content: sys },
{ role: 'user', content: prompt },
],
temperature: sampling.temperature,
top_p: 0.85,
top_k: sampling.top_k,
repeat_penalty: sampling.repeat_penalty,
},
}),
}, { timeoutMs: 120_000 });
const res = await lmChat(lmUrl, {
model,
messages: [
{ role: 'system', content: sys },
{ role: 'user', content: prompt },
],
temperature: sampling.temperature,
top_p: 0.85,
top_k: sampling.top_k,
repeat_penalty: sampling.repeat_penalty,
}, 120_000);
const content = res?.choices?.[0]?.message?.content
?? res?.choices?.[0]?.text
?? res?.answer
@@ -124,22 +146,16 @@ export async function callLmSynthesis(prompt: string, systemPrompt?: string, opt
*/
async function repairKoreanGlitches(text: string, lmUrl: string, model: string): Promise<string> {
try {
const res = await bridgeFetch<any>(BRIDGE_API.lm.proxy, {
method: 'POST',
body: JSON.stringify({
url: `${lmUrl}/v1/chat/completions`,
payload: {
model,
messages: [
{ role: 'system', content: '당신은 한국어 교정기입니다. 입력 텍스트에서 한글과 영문 알파벳이 한 단어로 잘못 합쳐진 깨진 표기(예: "핵ess"→"핵심", "결ently"→"결국")만 자연스러운 한국어로 교정합니다. 그 외 내용·문장·마크다운 구조·표·정상적인 영문 용어(API, JSON 등)는 한 글자도 바꾸지 않으며, 교정된 전체 텍스트만 그대로 출력합니다(설명·주석 금지).' },
{ role: 'user', content: text },
],
temperature: 0,
top_p: 0.7,
top_k: 20,
},
}),
}, { timeoutMs: 120_000 });
const res = await lmChat(lmUrl, {
model,
messages: [
{ role: 'system', content: '당신은 한국어 교정기입니다. 입력 텍스트에서 한글과 영문 알파벳이 한 단어로 잘못 합쳐진 깨진 표기(예: "핵ess"→"핵심", "결ently"→"결국")만 자연스러운 한국어로 교정합니다. 그 외 내용·문장·마크다운 구조·표·정상적인 영문 용어(API, JSON 등)는 한 글자도 바꾸지 않으며, 교정된 전체 텍스트만 그대로 출력합니다(설명·주석 금지).' },
{ role: 'user', content: text },
],
temperature: 0,
top_p: 0.7,
top_k: 20,
}, 120_000);
const fixed = String(
res?.choices?.[0]?.message?.content
?? res?.choices?.[0]?.text