khangPham-864/Dockerized-Tech-Domain-Intelligence-Agent

GitHub: khangPham-864/Dockerized-Tech-Domain-Intelligence-Agent

一个基于 LangGraph 的技术研究智能体,自动检索 GitHub 和 Hacker News 并利用本地模型生成有依据的回答,通过 Docker Compose 容器化实现脱离 Colab 的可重现运行。

Stars: 0 | Forks: 0

# 技术领域智能体 一个有状态的 LangGraph 智能体,它可以搜索 GitHub 代码库和 Hacker News 讨论,使用本地 `qwen2.5:7b` 模型分析检索到的证据,应用提示注入防护措施,并通过 LLM-as-judge 测试套件评估自身的响应。 该项目在 `student_submission.ipynb` 中为 COMP2701 评估 2 开发,并使用 Docker Compose 进行了扩展,以便**原始的 notebook 可以在容器化的 Jupyter 环境中运行,而不是依赖 Google Colab**。 ## 项目目标 该项目有三个主要目标: 1. 为技术研究构建结构化的智能体工作流。 2. 保持响应基于从 GitHub 和 Hacker News 检索到的证据。 3. 通过容器化 Jupyter、Ollama 和本地模型,在 Colab 之外可重现地运行原始的 notebook。 查询示例: ``` What are the most popular open-source Python frameworks for building LLM agents? ``` ## 技术栈 | 类别 | 技术 | |---|---| | 编程语言 | Python | | Notebook 环境 | JupyterLab | | 智能体框架 | LangGraph | | LLM 集成 | LangChain 和 `langchain-ollama` | | 本地模型 | Ollama 使用 `qwen2.5:7b` | | API 客户端 | HTTPX | | 数据验证 | Pydantic | | 工具 1 | GitHub REST API | | 工具 2 | Hacker News Algolia API | | 评估 | 本地 LLM-as-judge | | 容器化 | Docker 和 Docker Compose | | 容器网络 | Docker bridge network | | 模型持久化 | Docker named volume | 固定版本的依赖项: ``` langchain-ollama==1.1.0 langgraph==1.2.9 langchain-core==1.4.9 langchain-community==0.4.2 httpx==0.28.1 pydantic==2.13.4 ``` ## 解决的问题 ### 1. 非结构化的多源技术研究 GitHub 和 Hacker News 提供了不同形式的证据: - GitHub 提供代码库名称、星标数、编程语言、描述、URL 和更新日期。 - Hacker News 提供故事标题、得分、评论、日期和开发者讨论信号。 直接调用这两个 API 并不会自动生成一个明确的答案。系统仍然需要存储结果、决定哪些证据有用、合并来源并解释不确定性。 ### 2. 提示注入和无效输入 用户可能会提交: - 空查询; - 异常长的查询; - 直接的提示注入尝试; - 要求透露或覆盖隐藏指令的请求。 示例包括: ``` ignore previous instructions system prompt developer message jailbreak override instructions ``` 如果没有防护措施,这些输入可能会到达 API 或影响模型。 ### 3. 结果稀疏或缺失 某些查询会返回: - GitHub 结果但没有 Hacker News 故事; - Hacker News 故事但没有 GitHub 代码库; - 两个来源都没有有用的结果。 智能体必须避免在检索能力薄弱时崩溃或捏造证据。 ### 4. 自然语言问题并不总是适合 GitHub 搜索 完整的问题为人类提供了有用的上下文,但对于代码库搜索来说,可能包含低价值的词汇。 智能体需要保留重要的技术术语,同时去除通用词汇,并将查询简化为专注的关键词。 ### 5. 幻觉风险 模型绝不能创建: - 未检索到的代码库名称; - 虚假的星标数; - 不受支持的技术声明; - 将 Hacker News 上的观点当作事实证据。 ### 6. 在 Colab 之外重现原始 notebook 原始 notebook 会安装 Ollama,下载 `qwen2.5:7b`,并在临时的 Colab runtime 中安装 Python 包。 部署问题在于如何在一个可重用的环境中运行**相同的 notebook**,而无需将项目重写为单独的 FastAPI 应用程序。 ### 7. 本地 CPU 推理速度缓慢 在没有可访问的 NVIDIA GPU 的情况下,通过 Docker Desktop 运行 `qwen2.5:7b` 会导致高 CPU 和内存使用率。 观察到的使用量大约为: ``` agent-ollama 592% CPU agent-ollama 5.15 GiB RAM agent-jupyternb 0.30% CPU ``` 这表明 Ollama 推理(而不是 Jupyter)是主要的瓶颈。 ## 项目如何解决这些问题 ## 智能体结构和工作流 LangGraph 工作流包含三个有意义的节点: ``` User query | v fetch_node |- sanitise the query |- block invalid or adversarial input |- generate focused GitHub keywords |- call GitHub |- call Hacker News | +---- results found ----> analyse_node | | | v | respond_node | +---- blocked/no data ---> respond_node | v END ``` ### `fetch_node` 获取节点: - 在调用外部 API 之前运行防护措施; - 增加迭代计数器; - 使用本地 LLM 创建简短的 GitHub 关键词查询; - 将经过净化处理的自然语言查询发送到 Hacker News; - 将两组结果集存储在状态中; - 选择图是应该分析证据还是返回后备响应。 ### `analyse_node` 分析节点: - 读取 GitHub 和 Hacker News 结果; - 提取诸如星标数、编程语言、日期、得分和评论等证据; - 合并来自两个来源的信号; - 识别不确定性; - 将 Hacker News 视为社区讨论,而非事实证据。 ### `respond_node` 响应节点: - 生成最终的有依据的回答; - 针对被阻止的输入返回安全的拒绝; - 在检索结果为空时返回后备响应; - 避免捏造不受支持的事实。 ## 智能体状态 图通过一个共享的 `TypedDict` 传递信息: ``` class AgentState(TypedDict): query: str final_response: str errors: Annotated[List[str], operator.add] iteration: int current_node: str github_results: List[dict] hn_stories: List[dict] analysis: str sanitised_query: str ``` | 字段 | 用途 | |---|---| | `query` | 原始用户查询 | | `sanitised_query` | 经过验证和长度限制的查询 | | `github_results` | GitHub 代码库数据 | | `hn_stories` | Hacker News 故事数据 | | `analysis` | 证据综合 | | `final_response` | 最终答案 | | `errors` | 累积的错误消息 | | `iteration` | 循环防护和未来重试支持 | | `current_node` | 路由信号 | 该图使用 `MemorySaver` 编译,每次运行都使用一个 `thread_id`。 ## 防护机制设计 防护措施在任何外部 API 调用之前执行。 它: - 将 `None` 转换为空字符串; - 拒绝空输入或仅包含空格的输入; - 执行不区分大小写的阻止模式匹配; - 截断超过 300 个字符的查询; - 返回结构化的拒绝路径。 这可以防止无效或对抗性输入到达 GitHub 或 Hacker News。 当前的黑名单主要集中在直接攻击上。它可能无法捕获每一个改写后的提示注入尝试。 ## 搜索策略 ### GitHub 完整的用户问题将被转换为简短的关键词查询。 关键词提示指示模型: - 仅返回关键词; - 避免完整的句子; - 保留技术名称; - 删除诸如 `find`、`popular` 和 `best` 之类的通用词汇; - 返回不超过六个词。 ### Hacker News Hacker News 接收净化后的自然语言查询,因为故事搜索可以从完整的意图中受益。 两个工具搜索相同的主题,但查询格式会适应各自的 API。 ## 基于事实的响应设计 最终响应提示要求: 1. 直接回答; 2. 支持性证据; 3. 在证据稀少时表达不确定性。 它还要求模型: - 仅使用提供的分析; - 在相关时提及 GitHub 和 Hacker News; - 避免将 Hacker News 观点视为证据; - 避免捏造证据; - 使用清晰专业的语言。 ## 评估 评估套件包含 10 个用例: - 4 个正常用例; - 3 个边缘用例; - 3 个对抗性用例。 每个用例都会运行通过智能体,并由本地的 LLM 评估器进行评分。 评估器返回结构化的 JSON,其中包含: - 总分; - 摘要; - 每个评估标准的通过/失败值。 记录的结果: | 类别 | 分数 | 用例 | |---|---:|---:| | 正常 | 85% | 4 | | 边缘 | 85% | 3 | | 对抗性 | 90% | 3 | | **总计** | **86%** | **10** | 结果显示出强大的安全性和后备处理能力。主要的限制在于针对宽泛或稀疏查询的检索质量。 ## 为什么原始 notebook 被容器化 部署选择是容器化 **notebook 环境本身**,而不是将项目重写为 Web API。 这保留了: - 原始的 `student_submission.ipynb`; - 图构建单元; - 工具冒烟测试; - 评估单元; - 报告单元; - 自我检查单元。 职责划分如下: ``` Dockerfile -> installs Jupyter and Python dependencies Ollama service -> runs the local model server model-pull service -> downloads qwen2.5:7b once Jupyter service -> opens and runs student_submission.ipynb Docker volume -> preserves the downloaded model Bind mount -> preserves notebook changes on the host ``` ## Docker Compose 架构 ``` Browser | | localhost:8888 v Jupyter container | | http://ollama:11434 v Ollama container | v qwen2.5:7b model volume Jupyter container |- HTTPS -> GitHub API `- HTTPS -> Hacker News API ``` ### 服务 | 服务 | 任务 | |---|---| | `ollama` | 运行 Ollama 服务器 | | `model-pull` | 下载 `qwen2.5:7b`,然后退出 | | `jupyter-notebook` | 运行 JupyterLab 和原始 notebook | 预期状态: ``` agent-ollama Up (healthy) agent-model-pull Exited (0) agent-jupyternb Up (healthy) ``` `model-pull` 的 `Exited (0)` 是正常的,因为它是一个一次性的设置服务。 ## 项目结构 ``` workspace/ ├── Dockerfile ├── docker-compose.yml ├── requirements.txt ├── agent.env ├── .gitignore └── notebooks/ └── student_submission.ipynb ``` 推荐的 `.gitignore`: ``` agent.env .ipynb_checkpoints/ __pycache__/ ``` ## 环境文件 在 `docker-compose.yml` 旁边创建 `agent.env`: ``` MODEL=qwen2.5:7b OLLAMA_BASE_URL=http://ollama:11434 JUPYTER_TOKEN=replace_with_a_private_token GITHUB_TOKEN=replace_with_a_github_token ``` | 变量 | 用途 | |---|---| | `MODEL` | 选择 Ollama 模型 | | `OLLAMA_BASE_URL` | 将 notebook 连接到 Ollama 服务 | | `JUPYTER_TOKEN` | 保护 Jupyter 登录 | | `GITHUB_TOKEN` | 对 GitHub API 搜索进行身份验证 | GitHub token 是可选的。没有它仍然可以进行公开代码库搜索,但身份验证可以提高代码库搜索的速率配额。 ## Dockerfile ``` FROM quay.io/jupyter/minimal-notebook:latest COPY requirements.txt /tmp/requirements.txt RUN pip install --no-cache-dir -r /tmp/requirements.txt WORKDIR /home/jovyan/work ``` 它: 1. 从现成的 Jupyter 镜像开始; 2. 复制依赖列表; 3. 在镜像构建期间安装 notebook 所需的包; 4. 设置默认工作目录。 ## Docker Compose 配置 ``` volumes: ollama_models: networks: agent-network: driver: bridge services: ollama: image: ollama/ollama:latest container_name: agent-ollama restart: unless-stopped volumes: - ollama_models:/root/.ollama networks: - agent-network healthcheck: test: ["CMD", "ollama", "list"] interval: 10s timeout: 5s retries: 12 start_period: 20s model-pull: image: ollama/ollama:latest container_name: agent-model-pull restart: "no" depends_on: ollama: condition: service_healthy environment: OLLAMA_HOST: http://ollama:11434 command: ["pull", "qwen2.5:7b"] networks: - agent-network jupyter-notebook: build: context: . container_name: agent-jupyternb restart: unless-stopped depends_on: model-pull: condition: service_completed_successfully ports: - "8888:8888" volumes: - ./notebooks:/home/jovyan/work/notebooks env_file: ./agent.env command: - bash - -c - >- exec start-notebook.py --ServerApp.root_dir=/home/jovyan/work/notebooks --IdentityProvider.token="$${JUPYTER_TOKEN}" networks: - agent-network ``` ## 针对 Docker 的 notebook 更改 通过具备环境感知能力的 Ollama URL,同一个 notebook 可以同时支持 Colab 和 Docker: ``` MODEL = os.getenv( "MODEL", "qwen2.5:7b" ) OLLAMA_BASE_URL = os.getenv( "OLLAMA_BASE_URL", "http://127.0.0.1:11434" ) llm = ChatOllama( model=MODEL, base_url=OLLAMA_BASE_URL, temperature=0 ) llm_creative = ChatOllama( model=MODEL, base_url=OLLAMA_BASE_URL, temperature=0.3 ) ``` 行为: ``` Colab -> no OLLAMA_BASE_URL environment variable -> use http://127.0.0.1:11434 Docker -> agent.env defines OLLAMA_BASE_URL -> use http://ollama:11434 ``` 在 Docker 内运行时,跳过以下单元: - 安装 Ollama; - 启动 `ollama serve`; - 拉取模型; - 安装 Python 包。 这些任务已经由 Docker 处理。 ## 如何使用该项目 ### 1. 放置 notebook 将原始 notebook 放在: ``` notebooks/student_submission.ipynb ``` ### 2. 创建 `agent.env` 使用上面的环境文件示例。 ### 3. 验证 Compose 配置 ``` docker compose config --no-env-resolution ``` ### 4. 构建并启动 ``` docker compose up -d --build ``` 启动顺序: ``` ollama starts -> health check passes -> model-pull downloads qwen2.5:7b -> model-pull exits successfully -> Jupyter starts ``` ### 5. 检查服务 ``` docker compose ps docker compose ps -a ``` ### 6. 打开 Jupyter 打开: ``` http://localhost:8888 ``` 输入 `agent.env` 中的 `JUPYTER_TOKEN`。 ### 7. 运行 notebook 跳过仅适用于 Colab 的安装单元,并从具备环境感知能力的模型设置单元开始运行。 ### 8. 停止环境 ``` docker compose down ``` 这将保留命名的模型 volume。 仅在需要时删除模型 volume: ``` docker compose down -v ``` ## 使用示例 ``` result = run_agent( "Find GitHub repositories related to retrieval-augmented generation", thread_id="demo-rag" ) print(result["final_response"]) ``` ## Docker 部署挑战 ### NVIDIA GPU 预留在本地失败 初始配置: ``` deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: ["gpu"] ``` 错误: ``` WSL environment detected but no adapters were found ``` #### 原因 本地 Docker Desktop 环境无法提供 NVIDIA GPU。 #### 解决方案 默认的 Compose 文件通过移除 GPU 预留更改为兼容 CPU 的操作。 对于配置了 NVIDIA GPU 的云虚拟机,请使用单独的覆盖文件: ``` services: ollama: deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: ["gpu"] ``` ### Jupyter token 未展开 不正确: ``` --IdentityProvider.token='$${JUPYTER_TOKEN}' ``` 单引号阻止了 Bash 变量展开。 正确: ``` --IdentityProvider.token="$${JUPYTER_TOKEN}" ``` ### Jupyter 启动命令生成了格式错误的路径 错误: ``` No such file or directory: /home/jovyan/work/ --ServerApp.root_dir=/home/jovyan/work/notebooks ``` 反斜杠和空格导致命令选项被解释为路径的一部分。 解决方案是使用折叠的 YAML 命令,去掉不必要的反斜杠。 ### Jupyter 进入重启循环 错误: ``` bash: unexpected EOF while looking for matching `'' ``` 不匹配的单引号导致启动命令失败。由于该服务使用了: ``` restart: unless-stopped ``` Docker 不断重启它。 正确的命令: ``` command: - bash - -c - >- exec start-notebook.py --ServerApp.root_dir=/home/jovyan/work/notebooks --IdentityProvider.token="$${JUPYTER_TOKEN}" ``` ### CPU 评估太慢 十用例评估对每个用例执行多次本地 LLM 调用: 1. 关键词提取; 2. 证据分析; 3. 最终响应生成; 4. LLM-as-judge 评估。 在 CPU 上,Ollama 使用了多个 CPU 核心和超过 5 GiB 的 RAM。在等待模型推理时,Jupyter 基本处于空闲状态。 实用方法: - 使用 CPU 模式进行开发; - 测试时运行较少的评估用例; - 使用 NVIDIA 云虚拟机运行完整的测试套件; - 在最终提交前运行所有十个用例一次。 ## 故障排除 ### 检查状态 ``` docker compose ps -a ``` ### 跟踪 Jupyter 日志 ``` docker compose logs -f jupyter-notebook ``` ### 跟踪模型下载日志 ``` docker compose logs -f model-pull ``` ### 检查 Ollama 模型 ``` docker exec agent-ollama ollama list ``` ### 检查模型处理器 ``` docker exec agent-ollama ollama ps ``` ### 检查资源使用情况 ``` docker stats agent-ollama agent-jupyternb ``` ###重新创建 Jupyter ``` docker compose up -d --force-recreate --no-deps jupyter-notebook ``` ### 更改依赖项后重新构建 ``` docker compose up -d --build ``` ## 挑战和经验教训 - 防护措施应该在外部工具之前运行。 - 不同的 API 可能需要不同的查询格式。 - 有状态的图使路由和故障处理变得明确。 - 稀疏的检索应该产生安全的后备方案,而不是捏造的证据。 - 加载环境变量是不够的;应用程序必须实际使用它。 - 原始的 notebook 可以被容器化,而无需重写为单独的应用程序。 - 当容器通信时,Docker 服务名称会替换 `localhost`。 - YAML 结构、Compose 插值和 Bash 引号是独立的问题。 - 单引号会阻止 shell 变量展开。 - 重启循环是一种症状;容器日志会显示根本错误。 - Docker 卷在重启之间保留大型模型文件。 - 本地 CPU 推理可行,但明显慢于 GPU 推理。 ## 未来改进 ### 重试和查询扩展 当两个来源都没有返回结果时,未来的图分支可以使用更广泛的关键词重试一次。 现有的 `iteration` 字段可以防止无限循环。 ### 持久化检查点 `MemorySaver` 满足了作业要求,但将状态存储在内存中。未来的版本可以使用 SQLite 或 PostgreSQL 检查点。 ### 云 GPU 部署 Docker Compose 环境可以移动到具有以下条件的 Linux 虚拟机上: - NVIDIA GPU; - NVIDIA 驱动程序; - Docker; - NVIDIA Container Toolkit。 只有 Ollama 服务需要 GPU 访问权限。 ## 致谢 **开发者:** Le Nguyen Khang Pham **GitHub:** [khangPham-864](https://github.com/khangPham-864) COMP2701 教学团队提供了初始 notebook、评估结构和预构建的公开工具库。 项目实现包括: - 技术领域智能体工作流; - 状态设计; - 图节点和路由; - 防护逻辑; - 评估用例; - LLM-judge 解析; - GitHub token 集成; - Dockerfile 和 Docker Compose 部署; - 容器故障排除和 CPU/GPU 部署规划。 ## 结论 该项目展示了基于 notebook 的 LangGraph 智能体如何结合公开技术来源、应用安全检查、生成有依据的响应、评估自身输出,并在容器化环境中可重现地运行。 Docker 部署保持了原始 notebook 工作流的完整,同时将 Jupyter、Ollama、模型下载、网络和持久化分离为清晰的职责。
标签:AI风险缓解, DLL 劫持, Docker, LLM评估, Ollama, Python, RAG, 人工智能, 大语言模型, 安全防御评估, 无后门, 版权保护, 用户模式Hook绕过, 请求拦截, 逆向工具