feat: 로컬 LLM 채팅 기능 추가 (Ollama + vLLM, 스트리밍, MCP 도구 호출)

- OllamaController: Ollama/vLLM 프록시 API (채팅, 스트리밍, 모델 목록, 설정)
- UI: 새 대화 탭, 세션 관리, Markdown 렌더링, 스트리밍 응답
- vLLM: OpenAI-compatible API 지원, MCP function calling 통합
- Fix: McpClient DI 팩토리 등록 (HttpClient BaseAddress 문제 해결)
- Fix: llm-model.json 직렬화 JsonSerializer 사용
- Fix: nl2sql_worker KST 시간대 표시 (AT TIME ZONE Asia/Seoul)
- Program.cs: Ollama/vLLM HttpClient 등록 (1800s timeout)
This commit is contained in:
windpacer
2026-05-12 19:59:31 +09:00
parent ab290df5cd
commit 35136ba91e
13 changed files with 4091 additions and 11 deletions

View File

@@ -1,3 +1 @@
{
"vllm_model": "Qwen3.6-27B-FP8"
}
{"vllm_model":"Qwen3.6-27B-FP8"}

View File

@@ -0,0 +1,4 @@
{
"host": "localhost",
"port": 11434
}

View File

@@ -110,13 +110,14 @@ N분 간격 집계 공식 (time_bucket 금지, date_trunc 사용):
10분 버킷: to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/600)*600) AS bucket
N분 버킷: to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/(N*60))*(N*60)) AS bucket
예시 (2분 간격, 여러 태그):
SELECT to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/120)*120) AS bucket,
예시 (2분 간격, 여러 태그, KST 표시):
SELECT to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/120)*120) AT TIME ZONE 'Asia/Seoul' AS bucket,
tagname, AVG(value::double precision) AS avg_val
FROM history_table
WHERE tagname IN ('tag1', 'tag2')
AND recorded_at >= NOW() - INTERVAL '3 hours'
GROUP BY bucket, tagname ORDER BY bucket, tagname
GROUP BY to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/120)*120), tagname
ORDER BY to_timestamp(FLOOR(EXTRACT(EPOCH FROM recorded_at)/120)*120), tagname
규칙:
- SELECT만 허용 (INSERT/UPDATE/DELETE/DROP 등 불가)
@@ -142,7 +143,12 @@ async def _generate_sql(natural_language: str) -> str:
" with GROUP BY bucket, tagname and AVG(value::double precision) AS avg_val\n"
" * If NO interval is specified: SELECT recorded_at, tagname, value — NO GROUP BY.\n"
"- Current year is 2026. '4월 27일' means 2026-04-27.\n"
"- All times in DB are UTC. Korean input is KST (UTC+9). Convert: KST 12:00 = UTC 03:00.\n"
"- All times in DB are UTC. Korean input is KST (UTC+9). Convert KST→UTC for WHERE: KST 12:00 = UTC 03:00.\n"
"- Display times in KST: always apply AT TIME ZONE 'Asia/Seoul' on time columns in SELECT.\n"
" * Non-aggregated: SELECT recorded_at AT TIME ZONE 'Asia/Seoul' AS recorded_at, ...\n"
" * Aggregated bucket: GROUP BY the raw UTC expression, then convert only in SELECT:\n"
" SELECT to_timestamp(...) AT TIME ZONE 'Asia/Seoul' AS bucket, AVG(...) AS avg_val\n"
" FROM ... GROUP BY to_timestamp(...), tagname ORDER BY to_timestamp(...), tagname\n"
"- value column is TEXT; cast with ::double precision only when aggregating.\n"
"- All tagnames are lowercase (e.g. 'ficq-6113.pv'). Match exactly.\n"
"- PostgreSQL LIKE: dot has no special meaning, no escaping needed.\n"