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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user