使用Flask + Ollama + NVD API构建本地CVE分析器
在本教程中,我们将利用已在GPU上运行的Ollama容器来构建一个Web应用程序。
这不仅仅是一个“向LLM提问的聊天机器人”,而是配置为从外部API获取实际数据,并让本地LLM分析这些数据。我们将构建的示例应用程序是一个本地CVE分析器。
当用户输入CVE ID或关键词时,Flask应用程序会从NVD API查询漏洞信息,然后Ollama上运行的本地LLM会用韩语总结和分析该漏洞。
Ollama的/api/chat端点可以接收model和messages作为输入,生成对话式响应,并且默认使用流式响应。在本教程中,为了方便Flask处理,我们使用stream: false。

1. 完成的应用程序结构
我们将构建的结构如下:
사용자 브라우저
↓
Flask Web UI
↓
Flask Backend
├── NVD CVE API 호출
└── Ollama API 호출
↓
로컬 GPU LLM 응답 생성
↓
사용자 화면에 CVE 원본 정보 + AI 분석 결과 출력
关键在于Flask服务器在中间扮演了两个角色。
首先,它使用用户输入的CVE ID或关键词调用NVD API。
其次,它将从NVD获取的漏洞信息传递给Ollama以获取分析结果。
在本教程中,我们不会让LLM直接调用API;相反,Flask会明确调用API,然后将结果传递给LLM。这样可以使初学者更容易理解流程,并减少实践中的错误。

2. 前提条件
本教程假设以下状态:
1. Ollama 컨테이너가 이미 실행 중이다.
2. Ollama 컨테이너가 GPU를 사용하도록 구성되어 있다.
3. Ollama API 포트 11434에 접근할 수 있다.
4. 사용할 모델이 이미 pull 되어 있거나, pull 할 수 있다.
例如,以下命令应在主机上正常运行:
curl http://localhost:11434/api/tags
响应示例如下:
{
"models": [
{
"name": "llama3.1:latest"
}
]
}
我们还会检查一个简单的聊天调用。
curl http://localhost:11434/api/chat
-H "Content-Type: application/json"
-d '{
"model": "llama3.1",
"messages": [
{
"role": "user",
"content": "Ollama가 무엇인지 한 문장으로 설명해줘."
}
],
"stream": false
}'
使用stream: false会将响应作为单个JSON对象返回,而不是分成多个片段,这使得在Flask应用程序中处理起来更容易。
3. 创建项目目录
首先,创建项目目录。
mkdir ollama-flask-cve-app
cd ollama-flask-cve-app
整体结构如下:
ollama-flask-cve-app/
├── app.py
├── requirements.txt
├── Dockerfile
├── docker-compose.app.yml
├── templates/
│ └── index.html
└── static/
└── style.css
Flask默认在templates目录中查找HTML模板,并使用render_template()函数渲染HTML页面。
4. 定义Python包
创建requirements.txt文件。
Flask==3.1.1
requests==2.32.4
Flask负责Web UI和路由,而requests用于调用Ollama API和NVD API。
5. 编写Flask后端
现在,我们来编写app.py。
import os
import re
from typing import Any, Dict, List, Optional
import requests
from flask import Flask, render_template, request
app = Flask(__name__)
# 如果Ollama容器在主机上通过11434端口暴露,则使用localhost
# 如果Flask应用也作为容器运行并连接到与Ollama容器相同的网络
# 例如:http://lab-ollama:11434/api/chat 或 http://ollama:11434/api/chat
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434/api/chat")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3.1")
# NVD CVE API 2.0
NVD_API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
# 可选:NVD API Key
# 如果有API Key,请将其指定为环境变量。
# export NVD_API_KEY="your-api-key"
NVD_API_KEY = os.getenv("NVD_API_KEY", "")
# 关键词搜索时获取的最大CVE数量
NVD_RESULTS_PER_PAGE = int(os.getenv("NVD_RESULTS_PER_PAGE", "5"))
def is_cve_id(keyword: str) -> bool:
"""
입력값이 CVE ID 형식인지 확인합니다.
예: CVE-2024-3094
"""
return bool(re.fullmatch(r"CVE-d{4}-d{4,}", keyword.strip(), re.IGNORECASE))
def call_nvd_api(keyword: str) -> Dict[str, Any]:
"""
NVD API에서 CVE 정보를 가져옵니다.
CVE ID가 입력되면 cveIds 파라미터를 사용하고,
일반 키워드가 입력되면 keywordSearch 파라미터를 사용합니다.
"""
keyword = keyword.strip()
if is_cve_id(keyword):
params = {
"cveIds": keyword.upper()
}
else:
params = {
"keywordSearch": keyword,
"resultsPerPage": NVD_RESULTS_PER_PAGE
}
headers = {}
if NVD_API_KEY:
headers["apiKey"] = NVD_API_KEY
response = requests.get(
NVD_API_URL,
params=params,
headers=headers,
timeout=30
)
response.raise_for_status()
return response.json()
def get_english_description(cve: Dict[str, Any]) -> str:
"""
CVE 설명 중 영어 설명을 우선 추출합니다.
"""
descriptions = cve.get("descriptions", [])
for item in descriptions:
if item.get("lang") == "en":
return item.get("value", "")
if descriptions:
return descriptions[0].get("value", "")
return ""
def get_cvss_info(cve: Dict[str, Any]) -> Dict[str, str]:
"""
CVSS 정보를 추출합니다.
CVSS v4.0, v3.1, v3.0, v2 순서로 확인합니다.
"""
metrics = cve.get("metrics", {})
metric_order = [
"cvssMetricV40",
"cvssMetricV31",
"cvssMetricV30",
"cvssMetricV2",
]
for metric_key in metric_order:
metric_items = metrics.get(metric_key)
if not metric_items:
continue
metric = metric_items[0]
cvss_data = metric.get("cvssData", {})
return {
"version": str(cvss_data.get("version", metric_key)),
"score": str(cvss_data.get("baseScore", "UNKNOWN")),
"severity": str(
metric.get("baseSeverity")
or cvss_data.get("baseSeverity")
or "UNKNOWN"
),
"vector": str(cvss_data.get("vectorString", "UNKNOWN")),
}
return {
"version": "UNKNOWN",
"score": "UNKNOWN",
"severity": "UNKNOWN",
"vector": "UNKNOWN",
}
def get_cwe_list(cve: Dict[str, Any]) -> List[str]:
"""
CWE 목록을 추출합니다.
"""
weaknesses = cve.get("weaknesses", [])
cwe_list = []
for weakness in weaknesses:
for desc in weakness.get("description", []):
value = desc.get("value")
if value:
cwe_list.append(value)
return sorted(set(cwe_list))
def get_reference_urls(cve: Dict[str, Any], limit: int = 5) -> List[str]:
"""
참고 URL을 일부만 추출합니다.
"""
references = cve.get("references", {}).get("referenceData", [])
urls = []
for ref in references[:limit]:
url = ref.get("url")
if url:
urls.append(url)
return urls
def extract_cve_items(nvd_data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
NVD API 응답에서 화면과 프롬프트에 사용할 정보만 추출합니다.
"""
vulnerabilities = nvd_data.get("vulnerabilities", [])
result = []
for item in vulnerabilities:
cve = item.get("cve", {})
cvss = get_cvss_info(cve)
result.append(
{
"id": cve.get("id", "UNKNOWN"),
"published": cve.get("published", "UNKNOWN"),
"last_modified": cve.get("lastModified", "UNKNOWN"),
"status": cve.get("vulnStatus", "UNKNOWN"),
"description": get_english_description(cve),
"cvss_version": cvss["version"],
"cvss_score": cvss["score"],
"severity": cvss["severity"],
"vector": cvss["vector"],
"cwe": get_cwe_list(cve),
"references": get_reference_urls(cve),
}
)
return result
def format_cve_items_for_prompt(cve_items: List[Dict[str, Any]]) -> str:
"""
Ollama에게 전달할 텍스트를 만듭니다.
원본 JSON 전체를 넘기지 않고 필요한 필드만 전달합니다.
"""
if not cve_items:
return "검색 결과가 없습니다."
lines = []
for item in cve_items:
cwe_text = ", ".join(item["cwe"]) if item["cwe"] else "UNKNOWN"
ref_text = "
".join(item["references"]) if item["references"] else "NONE"
lines.append(
f"""
CVE ID: {item["id"]}
Published: {item["published"]}
Last Modified: {item["last_modified"]}
Status: {item["status"]}
Severity: {item["severity"]}
CVSS Version: {item["cvss_version"]}
CVSS Score: {item["cvss_score"]}
CVSS Vector: {item["vector"]}
CWE: {cwe_text}
Description:
{item["description"]}
References:
{ref_text}
"""
)
return "
---
".join(lines)
def ask_ollama(cve_text: str) -> str:
"""
Ollama API를 호출해 CVE 분석 결과를 생성합니다.
"""
prompt = f"""
너는 보안 분석가다.
아래 CVE 정보를 바탕으로 한국어로 분석해라.
반드시 지켜야 할 규칙:
1. 제공된 CVE 정보에 근거해서만 설명한다.
2. 원문에 없는 공격 절차를 과장해서 만들지 않는다.
3. 초보자도 이해할 수 있게 설명한다.
4. 운영자 관점의 대응 방안을 제시한다.
5. 출력은 다음 형식을 따른다.
출력 형식:
## 요약
## 취약점 설명
## 악용 가능성
## 위험도 판단
## 운영자 대응 방안
## 참고할 점
CVE 정보:
{cve_text}
"""
payload = {
"model": OLLAMA_MODEL,
"messages": [
{
"role": "user",
"content": prompt
}
],
"stream": False,
"options": {
"temperature": 0.2
}
}
response = requests.post(
OLLAMA_URL,
json=payload,
timeout=180
)
response.raise_for_status()
data = response.json()
return data.get("message", {}).get("content", "")
@app.route("/", methods=["GET", "POST"])
def index():
"""
메인 페이지입니다.
GET 요청이면 검색 폼만 보여주고,
POST 요청이면 NVD API 조회 후 Ollama 분석 결과를 보여줍니다.
"""
query = ""
cve_items = []
cve_prompt_text = ""
analysis_result = ""
error = ""
if request.method == "POST":
query = request.form.get("query", "").strip()
if not query:
error = "CVE ID 또는 검색 키워드를 입력하세요."
else:
try:
nvd_data = call_nvd_api(query)
cve_items = extract_cve_items(nvd_data)
cve_prompt_text = format_cve_items_for_prompt(cve_items)
if cve_items:
analysis_result = ask_ollama(cve_prompt_text)
else:
error = "NVD API에서 검색 결과를 찾지 못했습니다."
except requests.exceptions.HTTPError as e:
error = f"HTTP 오류가 발생했습니다: {e}"
except requests.exceptions.Timeout:
error = "요청 시간이 초과되었습니다. 모델이 너무 크거나 API 응답이 느릴 수 있습니다."
except requests.exceptions.RequestException as e:
error = f"API 호출 중 오류가 발생했습니다: {e}"
except Exception as e:
error = f"처리 중 예상하지 못한 오류가 발생했습니다: {e}"
return render_template(
"index.html",
query=query,
model=OLLAMA_MODEL,
ollama_url=OLLAMA_URL,
cve_items=cve_items,
cve_prompt_text=cve_prompt_text,
analysis_result=analysis_result,
error=error,
)
if __name__ == "__main__":
app.run(
host="0.0.0.0",
port=5000,
debug=True
)
NVD CVE API 2.0的基本URL是
此外,NVD API Key以apiKey:{key value}的形式在请求头中传递。如果没有API Key,限制为每30秒5次请求;如果有API Key,限制为每30秒50次请求。
6. 编写HTML UI
现在,创建templates/index.html文件。
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Local CVE Analyst</title>
<link
rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}"
/>
</head>
<body>
<main class="page">
<section class="hero">
<div class="badge">Ollama + Flask + NVD API</div>
<h1>Local CVE Analyst</h1>
<p>
GPU 기반 Ollama 컨테이너와 NVD API를 연동해
CVE 정보를 로컬 LLM으로 분석하는 보안 AI 웹앱입니다.
</p>
</section>
<section class="panel">
<form method="POST" class="search-form">
<label for="query">CVE ID 또는 검색 키워드</label>
<div class="input-row">
<input
id="query"
name="query"
type="text"
value="{{ query }}"
placeholder="예: CVE-2024-3094 또는 openssl"
autocomplete="off"
/>
<button type="submit">
분석하기
</button>
</div>
</form>
<div class="runtime-info">
<span>Model: {{ model }}</span>
<span>Ollama API: {{ ollama_url }}</span>
</div>
</section>
{% if error %}
<section class="alert">
{{ error }}
</section>
{% endif %}
{% if cve_items %}
<section class="grid">
<div class="panel">
<h2>NVD 검색 결과</h2>
{% for item in cve_items %}
<article class="cve-card">
<div class="cve-header">
<h3>{{ item.id }}</h3>
<span class="severity">{{ item.severity }}</span>
</div>
<dl>
<div>
<dt>CVSS</dt>
<dd>{{ item.cvss_score }} / {{ item.cvss_version }}</dd>
</div>
<div>
<dt>Status</dt>
<dd>{{ item.status }}</dd>
</div>
<div>
<dt>Published</dt>
<dd>{{ item.published }}</dd>
</div>
<div>
<dt>Last Modified</dt>
<dd>{{ item.last_modified }}</dd>
</div>
</dl>
<p class="description">
{{ item.description }}
</p>
{% if item.cwe %}
<div class="chips">
{% for cwe in item.cwe %}
<span>{{ cwe }}</span>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</div>
<div class="panel">
<h2>Ollama 분석 결과</h2>
{% if analysis_result %}
<pre class="analysis">{{ analysis_result }}</pre>
{% else %}
<p class="muted">분석 결과가 없습니다.</p>
{% endif %}
</div>
</section>
<section class="panel">
<h2>LLM에 전달한 요약 데이터</h2>
<pre class="raw">{{ cve_prompt_text }}</pre>
</section>
{% endif %}
</main>
</body>
</html>
这里重要的一点是analysis_result没有进行安全处理。将LLM响应或外部API响应作为HTML信任可能会导致XSS问题。因此,我们使用
标签和CSS的white-space处理来自然地显示换行符。7. 编写CSS
创建static/style.css文件。
:root { --bg: #f3f4f6; --panel: #ffffff; --text: #111827; --muted: #6b7280; --border: #e5e7eb; --primary: #111827; --primary-hover: #374151; --danger-bg: #fee2e2; --danger-text: #991b1b; --code-bg: #0f172a; --code-text: #e5e7eb; } * { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans KR", Arial, sans-serif; background: var(--bg); color: var(--text); } .page { width: 1120px; max-width: 94%; margin: 40px auto; } .hero { margin-bottom: 24px; } .badge { display: inline-block; padding: 6px 10px; margin-bottom: 12px; border-radius: 999px; background: #e5e7eb; color: #374151; font-size: 13px; font-weight: 700; } .hero h1 { margin: 0 0 12px; font-size: 42px; letter-spacing: -0.04em; } .hero p { margin: 0; color: var(--muted); font-size: 18px; line-height: 1.6; } .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 18px; padding: 24px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06); } .search-form label { display: block; margin-bottom: 10px; font-weight: 800; } .input-row { display: flex; gap: 10px; } .input-row input { flex: 1; padding: 14px 16px; border: 1px solid var(--border); border-radius: 12px; font-size: 16px; } .input-row button { padding: 0 22px; border: none; border-radius: 12px; background: var(--primary); color: white; font-size: 16px; font-weight: 800; cursor: pointer; } .input-row button:hover { background: var(--primary-hover); } .runtime-info { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; color: var(--muted); font-size: 13px; } .runtime-info span { padding: 6px 10px; background: #f9fafb; border: 1px solid var(--border); border-radius: 999px; } .alert { padding: 16px; margin-bottom: 20px; border-radius: 14px; background: var(--danger-bg); color: var(--danger-text); font-weight: 700; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; } h2 { margin-top: 0; margin-bottom: 16px; font-size: 22px; } .cve-card { padding: 18px; border: 1px solid var(--border); border-radius: 14px; margin-bottom: 14px; background: #f9fafb; } .cve-header { display: flex; justify-content: space-between; gap: 10px; align-items: center; margin-bottom: 12px; } .cve-header h3 { margin: 0; } .severity { padding: 5px 9px; border-radius: 999px; background: #111827; color: #ffffff; font-size: 12px; font-weight: 800; } dl { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 0 0 14px; } dt { color: var(--muted); font-size: 12px; } dd { margin: 2px 0 0; font-weight: 700; font-size: 14px; } .description { line-height: 1.6; color: #374151; } .chips { display: flex; flex-wrap: wrap; gap: 6px; } .chips span { padding: 5px 8px; border-radius: 999px; background: #e5e7eb; color: #374151; font-size: 12px; font-weight: 700; } pre { margin: 0; white-space: pre-wrap; word-break: break-word; line-height: 1.6; } .analysis { min-height: 300px; color: var(--text); font-family: inherit; } .raw { padding: 16px; border-radius: 12px; background: var(--code-bg); color: var(--code-text); font-size: 13px; } .muted { color: var(--muted); } @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } .input-row { flex-direction: column; } .input-row button { padding: 14px 22px; } .hero h1 { font-size: 34px; } }
8. 本地运行
安装软件包。
pip install -r requirements.txt指定Ollama API URL和模型名称。
export OLLAMA_URL="http://localhost:11434/api/chat" export OLLAMA_MODEL="llama3.1"如果拥有NVD API Key,可以选择性地指定。
export NVD_API_KEY="your-nvd-api-key"运行Flask应用程序。
python app.py在浏览器中访问以下地址:
http://localhost:5000尝试在搜索栏中输入以下内容:
CVE-2024-3094或者您也可以按关键词搜索:
openssl屏幕上会显示三个主要信息:
1. NVD API에서 가져온 CVE 기본 정보 2. Ollama가 생성한 한국어 취약점 분석 3. Ollama에게 실제로 전달한 요약 데이터
9. 使用容器运行
如果您想将Flask应用程序也作为容器运行,请创建Dockerfile。
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD ["python", "app.py"]构建应用程序镜像。
docker build -t ollama-flask-cve-app:latest .如果Ollama容器在主机上暴露了11434端口,Docker在某些情况下可以使用以下地址:
http://host.docker.internal:11434/api/chat然而,在Linux服务器或实践环境中,host.docker.internal可能无法始终保证。最简洁的方法是将Ollama容器和Flask应用程序容器放在同一个Docker网络中。
例如,假设Ollama容器的名称是lab-ollama。
docker network create ollama-net docker network connect ollama-net lab-ollama现在,将Flask应用程序容器连接到同一个网络。
docker run --rm -it --name local-cve-analyst --network ollama-net -p 5000:5000 -e OLLAMA_URL="http://lab-ollama:11434/api/chat" -e OLLAMA_MODEL="llama3.1" ollama-flask-cve-app:latest现在,在浏览器中访问:
http://localhost:5000
10. Docker Compose示例
如果Ollama容器已经单独运行,您也可以只用Compose管理应用程序。
创建docker-compose.app.yml文件。
services: cve-app: build: . container_name: local-cve-analyst ports: - "5000:5000" environment: OLLAMA_URL: "http://lab-ollama:11434/api/chat" OLLAMA_MODEL: "llama3.1" NVD_RESULTS_PER_PAGE: "5" # NVD_API_KEY: "your-nvd-api-key" networks: - ollama-net networks: ollama-net: external: true运行它。
docker compose -f docker-compose.app.yml up --build如果Ollama容器的名称是ollama,您可以按如下方式更改:
OLLAMA_URL: "http://ollama:11434/api/chat"重要的一点是,当Flask应用程序在容器内部运行时,localhost指的是Flask应用程序容器本身,而不是Ollama容器。因此,要从应用程序容器访问Ollama容器,最容易理解的方法是将其连接到同一个网络,并使用容器名称作为主机名。
11. 在Podman环境中运行
如果您的实践脚本基于Podman,也可以类似地进行配置。
假设Ollama容器以lab-ollama的名称运行。
podman network create ollama-net podman network connect ollama-net lab-ollama构建镜像。
podman build -t ollama-flask-cve-app:latest .运行应用程序容器。
podman run --rm -it --name local-cve-analyst --network ollama-net -p 5000:5000 -e OLLAMA_URL="http://lab-ollama:11434/api/chat" -e OLLAMA_MODEL="llama3.1" ollama-flask-cve-app:latest在浏览器中访问以下地址:
http://localhost:5000
12. 测试场景
第一个测试是单个CVE ID查询。
CVE-2024-3094在这种情况下,Flask应用程序会以cveIds=CVE-2024-3094的形式向NVD API发出请求。
第二个测试是关键词搜索。
openssl在这种情况下,Flask应用程序会以keywordSearch=openssl的形式向NVD API发出请求。
第三个测试是针对不存在的CVE ID。
CVE-2099-0000在这种情况下,应该显示一条消息,指示没有搜索结果。
13. 常见错误
13.1 Ollama API连接失败
错误示例如下:
Connection refused需要检查的内容如下:
curl http://localhost:11434/api/tags如果主机上可以,但在应用程序容器内部不行,则OLLAMA_URL很可能不正确。
如果在本地运行Flask应用程序,请使用以下内容:
export OLLAMA_URL="http://localhost:11434/api/chat"如果在容器中运行Flask应用程序,并且Ollama容器的名称是lab-ollama,请使用以下内容:
export OLLAMA_URL="http://lab-ollama:11434/api/chat"
13.2 模型不存在错误
如果Ollama响应显示模型不存在,您必须首先拉取该模型。
ollama pull llama3.1如果Ollama在容器内部运行,请按如下方式运行:
docker exec -it lab-ollama ollama pull llama3.1或者,在Podman环境中,如下所示:
podman exec -it lab-ollama ollama pull llama3.1
13.3 NVD API 403或限制错误
NVD API即使对于公共访问也有速率限制。如果没有API Key,限制为每30秒5次请求;如果有API Key,限制为每30秒50次请求。
在实践中,多次点击按钮可能会触发限制。在这种情况下,请稍等片刻再试。
为了使其达到生产级别,建议添加以下功能:
1. NVD API 응답 캐싱 2. 동일 검색어 반복 요청 제한 3. API Key 사용 4. 사용자별 요청 제한
13.4 响应时间过长
如果本地LLM模型较大或GPU内存不足,响应时间可能会很长。
在这种情况下,请调整以下内容:
1. 더 작은 모델 사용 2. NVD_RESULTS_PER_PAGE 값을 1~3으로 축소 3. 프롬프트 길이 축소 4. Ollama keep_alive 설정 조정例如,将结果数量减少到3个。
export NVD_RESULTS_PER_PAGE="3"
14. 此应用程序的学习要点
本次实践的核心是“LLM应用程序不仅仅是附加一个聊天窗口”。
此应用程序包含以下所有概念:
1. 웹 UI 2. Flask 백엔드 3. 외부 REST API 연동 4. 로컬 LLM API 호출 5. 프롬프트 구성 6. 컨테이너 네트워크 7. 보안 데이터 분석这种结构在安全教育中特别有用。它不仅仅是简单地显示CVE、CWE、CVSS等安全数据,而是让LLM从操作员的角度将其转换为解释。
15. 下一步:扩展到工具调用结构
在本教程中,Flask首先调用NVD API,然后将结果传递给Ollama。
사용자 입력 ↓ Flask가 NVD API 호출 ↓ Flask가 Ollama 호출 ↓ 최종 답변 출력在高级版本中,您可以使用Ollama的工具调用功能。Ollama支持模型调用工具并将其结果反映到响应中的工具调用功能。
高级结构如下:
사용자 입력 ↓ Ollama가 필요한 도구 판단 ↓ Flask가 해당 도구 함수 실행 ↓ 도구 실행 결과를 Ollama에 다시 전달 ↓ 최종 답변 출력然而,如果一开始就采用这种结构,解释会变得复杂。因此,对于入门教程来说,像现在这样“Flask调用外部API,Ollama只负责分析的结构”更合适。
16. 总结
在本教程中,我们假设基于GPU的Ollama容器已经准备就绪,并使用Flask创建了一个具有实际Web UI的本地LLM应用程序。
完成的应用程序按以下流程运行:
CVE ID 또는 키워드 입력 ↓ NVD API에서 취약점 정보 조회 ↓ 필요한 필드만 추출 ↓ Ollama 로컬 LLM에 분석 요청 ↓ 웹 화면에 원본 정보와 AI 분석 결과 출력这个示例比简单的聊天机器人具有更高的实践价值。因为它展示了连接外部API和本地LLM的基本模式。应用此模式,可以扩展到GitHub仓库分析器、日志分析助手、安全检查报告生成器、内部文档搜索型RAG应用程序等。
发表回复