fix: added configuration of python_repl (#503)

* fix: added configuration of python_repl

* fix the lint and unit test errors

* fix the lint and unit test errors

* fix:the lint check errors
This commit is contained in:
Willem Jiang
2025-08-06 14:27:03 +08:00
committed by GitHub
parent 4218cddab5
commit 9e691ecf20
11 changed files with 194 additions and 77 deletions

View File

@@ -14,10 +14,14 @@ ALLOWED_ORIGINS=http://localhost:3000
# Enable or disable MCP server configuration, the default is false.
# Please enable this feature before securing your front-end and back-end in a managed environment.
# Otherwise, you system could be compromised.
ENABLE_MCP_SERVER_CONFIGURATION=false
# Enable or disable PYTHON_REPL configuration, the default is false.
# Please enable this feature before securing your in a managed environment.
# Otherwise, you system could be compromised.
ENABLE_PYTHON_REPL=false
# Search Engine, Supported values: tavily (recommended), duckduckgo, brave_search, arxiv
SEARCH_API=tavily
TAVILY_API_KEY=tvly-xxx

View File

@@ -420,6 +420,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> If you want to deploy the deer flow into production environments, please add authentication to the website and evaluate your security check of the MCPServer and Python Repl.
## Examples
The following examples demonstrate the capabilities of DeerFlow:

View File

@@ -11,7 +11,7 @@
**DeerFlow** (**D**eep **E**xploration and **E**fficient **R**esearch **Flow**) ist ein Community-getriebenes Framework für tiefgehende Recherche, das auf der großartigen Arbeit der Open-Source-Community aufbaut. Unser Ziel ist es, Sprachmodelle mit spezialisierten Werkzeugen für Aufgaben wie Websuche, Crawling und Python-Code-Ausführung zu kombinieren und gleichzeitig der Community, die dies möglich gemacht hat, etwas zurückzugeben.
Derzeit ist DeerFlow offiziell in das FaaS-Anwendungszentrum von Volcengine eingezogen. Benutzer können es über den Erfahrungslink online erleben, um seine leistungsstarken Funktionen und bequemen Operationen intuitiv zu spüren. Gleichzeitig unterstützt DeerFlow zur Erfüllung der Bereitstellungsanforderungen verschiedener Benutzer die Ein-Klick-Bereitstellung basierend auf Volcengine. Klicken Sie auf den Bereitstellungslink, um den Bereitstellungsprozess schnell abzuschließen und eine effiziente Forschungsreise zu beginnen.
Derzeit ist DeerFlow offiziell in das [FaaS-Anwendungszentrum von Volcengine](https://console.volcengine.com/vefaas/region:vefaas+cn-beijing/market) eingezogen. Benutzer können es über den [Erfahrungslink](https://console.volcengine.com/vefaas/region:vefaas+cn-beijing/market/deerflow/?channel=github&source=deerflow) online erleben, um seine leistungsstarken Funktionen und bequemen Operationen intuitiv zu spüren. Gleichzeitig unterstützt DeerFlow zur Erfüllung der Bereitstellungsanforderungen verschiedener Benutzer die Ein-Klick-Bereitstellung basierend auf Volcengine. Klicken Sie auf den [Bereitstellungslink](https://console.volcengine.com/vefaas/region:vefaas+cn-beijing/application/create?templateId=683adf9e372daa0008aaed5c&channel=github&source=deerflow), um den Bereitstellungsprozess schnell abzuschließen und eine effiziente Forschungsreise zu beginnen.
Besuchen Sie [unsere offizielle Website](https://deerflow.tech/) für weitere Details.
@@ -43,6 +43,7 @@ In dieser Demo zeigen wir, wie man DeerFlow nutzt, um:
- [🌟 Funktionen](#funktionen)
- [🏗️ Architektur](#architektur)
- [🛠️ Entwicklung](#entwicklung)
- [🐳 Docker](#docker)
- [🗣️ Text-zu-Sprache-Integration](#text-zu-sprache-integration)
- [📚 Beispiele](#beispiele)
- [❓ FAQ](#faq)
@@ -125,6 +126,7 @@ uv run main.py
### Web-UI
Dieses Projekt enthält auch eine Web-UI, die ein dynamischeres und ansprechenderes interaktives Erlebnis bietet.
> [!HINWEIS]
> Sie müssen zuerst die Abhängigkeiten der Web-UI installieren.
@@ -143,18 +145,20 @@ Weitere Details finden Sie im Verzeichnis [`web`](./web/).
## Unterstützte Suchmaschinen
### Websuche
DeerFlow unterstützt mehrere Suchmaschinen, die in Ihrer `.env`-Datei über die Variable `SEARCH_API` konfiguriert werden können:
- **Tavily** (Standard): Eine spezialisierte Such-API für KI-Anwendungen
- Erfordert `TAVILY_API_KEY` in Ihrer `.env`-Datei
- Registrieren Sie sich unter: <https://app.tavily.com/home>
- Registrieren Sie sich unter: https://app.tavily.com/home
- **DuckDuckGo**: Datenschutzorientierte Suchmaschine
- Kein API-Schlüssel erforderlich
- **Brave Search**: Datenschutzorientierte Suchmaschine mit erweiterten Funktionen
- Erfordert `BRAVE_SEARCH_API_KEY` in Ihrer `.env`-Datei
- Registrieren Sie sich unter: <https://brave.com/search/api/>
- Registrieren Sie sich unter: https://brave.com/search/api/
- **Arxiv**: Wissenschaftliche Papiersuche für akademische Forschung
- Kein API-Schlüssel erforderlich
@@ -167,6 +171,20 @@ Um Ihre bevorzugte Suchmaschine zu konfigurieren, setzen Sie die Variable `SEARC
SEARCH_API=tavily
```
### Private Wissensbasis
DeerFlow unterstützt private Wissensbasen wie RAGFlow und VikingDB, sodass Sie Ihre privaten Dokumente zur Beantwortung von Fragen verwenden können.
- **[RAGFlow](https://ragflow.io/docs/dev/)**Open-Source-RAG-Engine
```
# Beispiele in .env.example
RAG_PROVIDER=ragflow
RAGFLOW_API_URL="http://localhost:9388"
RAGFLOW_API_KEY="ragflow-xxx"
RAGFLOW_RETRIEVAL_SIZE=10
RAGFLOW_CROSS_LANGUAGES=English,Chinese,Spanish,French,German,Japanese,Korean
```
## Funktionen
### Kernfähigkeiten
@@ -183,6 +201,11 @@ SEARCH_API=tavily
- Websuche über Tavily, Brave Search und mehr
- Crawling mit Jina
- Fortgeschrittene Inhaltsextraktion
- Unterstützung für private Wissensbasis
- 📃 **RAG-Integration**
- Unterstützt die Erwähnung von Dateien aus [RAGFlow](https://github.com/infiniflow/ragflow) innerhalb der Eingabebox. [RAGFlow-Server starten](https://ragflow.io/docs/dev/).
- 🔗 **MCP Nahtlose Integration**
- Erweiterte Fähigkeiten für privaten Domänenzugriff, Wissensgraphen, Webbrowsing und mehr
@@ -211,6 +234,7 @@ SEARCH_API=tavily
DeerFlow implementiert eine modulare Multi-Agenten-Systemarchitektur, die für automatisierte Forschung und Codeanalyse konzipiert ist. Das System basiert auf LangGraph und ermöglicht einen flexiblen zustandsbasierten Workflow, bei dem Komponenten über ein klar definiertes Nachrichtenübermittlungssystem kommunizieren.
![Architekturdiagramm](./assets/architecture.png)
> Sehen Sie es live auf [deerflow.tech](https://deerflow.tech/#multi-agent-architecture)
Das System verwendet einen optimierten Workflow mit den folgenden Komponenten:
@@ -314,9 +338,9 @@ langgraph dev
Nach dem Start des LangGraph-Servers sehen Sie mehrere URLs im Terminal:
- API: <http://127.0.0.1:2024>
- Studio UI: <https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024>
- API-Dokumentation: <http://127.0.0.1:2024/docs>
- API: http://127.0.0.1:2024
- Studio UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
- API-Dokumentation: http://127.0.0.1:2024/docs
Öffnen Sie den Studio UI-Link in Ihrem Browser, um auf die Debugging-Schnittstelle zuzugreifen.
@@ -351,13 +375,49 @@ DeerFlow unterstützt LangSmith-Tracing, um Ihnen beim Debuggen und Überwachen
```
2. Starten Sie das Tracing mit LangSmith lokal, indem Sie folgenden Befehl ausführen:
```bash
langgraph dev
```
Dies aktiviert die Trace-Visualisierung in LangGraph Studio und sendet Ihre Traces zur Überwachung und Analyse an LangSmith.
## Docker
Sie können dieses Projekt auch mit Docker ausführen.
Zuerst müssen Sie die [Konfiguration](docs/configuration_guide.md) unten lesen. Stellen Sie sicher, dass die Dateien `.env` und `.conf.yaml` bereit sind.
Zweitens, um ein Docker-Image Ihres eigenen Webservers zu erstellen:
```bash
docker build -t deer-flow-api .
```
Schließlich starten Sie einen Docker-Container, der den Webserver ausführt:
```bash
# Ersetzen Sie deer-flow-api-app durch Ihren bevorzugten Container-Namen
docker run -d -t -p 8000:8000 --env-file .env --name deer-flow-api-app deer-flow-api
# Server stoppen
docker stop deer-flow-api-app
```
### Docker Compose (umfasst sowohl Backend als auch Frontend)
DeerFlow bietet ein docker-compose-Setup, um sowohl das Backend als auch das Frontend einfach zusammen auszuführen:
```bash
# Docker-Image erstellen
docker compose build
# Server starten
docker compose up
```
> [!WARNING]
> Wenn Sie DeerFlow in Produktionsumgebungen bereitstellen möchten, fügen Sie bitte Authentifizierung zur Website hinzu und bewerten Sie Ihre Sicherheitsüberprüfung des MCPServer und Python Repl.
## Beispiele
Die folgenden Beispiele demonstrieren die Fähigkeiten von DeerFlow:
@@ -487,6 +547,8 @@ Wir möchten unsere aufrichtige Wertschätzung den folgenden Projekten für ihre
- **[LangChain](https://github.com/langchain-ai/langchain)**: Ihr außergewöhnliches Framework unterstützt unsere LLM-Interaktionen und -Ketten und ermöglicht nahtlose Integration und Funktionalität.
- **[LangGraph](https://github.com/langchain-ai/langgraph)**: Ihr innovativer Ansatz zur Multi-Agenten-Orchestrierung war maßgeblich für die Ermöglichung der ausgeklügelten Workflows von DeerFlow.
- **[Novel](https://github.com/steven-tey/novel)**: Ihr Notion-artiger WYSIWYG-Editor unterstützt unsere Berichtbearbeitung und KI-unterstützte Umschreibung.
- **[RAGFlow](https://github.com/infiniflow/ragflow)**: Wir haben durch die Integration mit RAGFlow die Unterstützung für Forschung auf privaten Wissensdatenbanken der Benutzer erreicht.
Diese Projekte veranschaulichen die transformative Kraft der Open-Source-Zusammenarbeit, und wir sind stolz darauf, auf ihren Grundlagen aufzubauen.

View File

@@ -403,6 +403,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> Si desea implementar DeerFlow en entornos de producción, agregue autenticación al sitio web y evalúe su verificación de seguridad del MCPServer y Python Repl.
## Ejemplos
Los siguientes ejemplos demuestran las capacidades de DeerFlow:

View File

@@ -399,6 +399,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> DeerFlow を本番環境にデプロイする場合は、ウェブサイトに認証を追加し、MCPServer と Python Repl のセキュリティチェックを評価してください。
## テキスト読み上げ統合
DeerFlow には現在、研究レポートを音声に変換できるテキスト読み上げTTS機能が含まれています。この機能は火山引擎 TTS API を使用して高品質なテキストオーディオを生成します。速度、音量、ピッチなどの特性もカスタマイズ可能です。

View File

@@ -388,6 +388,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> Se você quiser implantar o DeerFlow em ambientes de produção, adicione autenticação ao site e avalie sua verificação de segurança do MCPServer e Python Repl.
## Exemplos
Os seguintes exemplos demonstram as capacidades do DeerFlow:

View File

@@ -403,6 +403,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> Если вы хотите развернуть DeerFlow в производственных средах, пожалуйста, добавьте аутентификацию к веб-сайту и оцените свою проверку безопасности MCPServer и Python Repl.
## Примеры
Следующие примеры демонстрируют возможности DeerFlow:

View File

@@ -424,6 +424,9 @@ docker compose build
docker compose up
```
> [!WARNING]
> 如果您想将 DeerFlow 部署到生产环境中,请为网站添加身份验证,并评估 MCPServer 和 Python Repl 的安全检查。
## 文本转语音集成
DeerFlow 现在包含一个文本转语音 (TTS) 功能,允许您将研究报告转换为语音。此功能使用火山引擎 TTS API 生成高质量的文本音频。速度、音量和音调等特性也可以自定义。

View File

@@ -2,13 +2,24 @@
# SPDX-License-Identifier: MIT
import logging
from typing import Annotated
import os
from typing import Annotated, Optional
from langchain_core.tools import tool
from langchain_experimental.utilities import PythonREPL
from .decorators import log_io
def _is_python_repl_enabled() -> bool:
"""Check if Python REPL tool is enabled from configuration."""
# Check environment variable first
env_enabled = os.getenv("ENABLE_PYTHON_REPL", "false").lower()
if env_enabled in ("true", "1", "yes", "on"):
return True
return False
# Initialize REPL and logger
repl = PythonREPL()
repl: Optional[PythonREPL] = PythonREPL() if _is_python_repl_enabled() else None
logger = logging.getLogger(__name__)
@@ -21,6 +32,13 @@ def python_repl_tool(
):
"""Use this to execute python code and do data analysis or calculation. If you want to see the output of a value,
you should print it out with `print(...)`. This is visible to the user."""
# Check if the tool is enabled
if not _is_python_repl_enabled():
error_msg = "Python REPL tool is disabled. Please enable it in environment configuration."
logger.warning(error_msg)
return f"Tool disabled: {error_msg}"
if not isinstance(code, str):
error_msg = f"Invalid input: code must be a string, got {type(code)}"
logger.error(error_msg)

View File

@@ -1,59 +0,0 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
from src.tools.python_repl import python_repl_tool
def test_python_repl_tool_success():
code = "print(1 + 1)"
result = python_repl_tool(code)
assert "Successfully executed" in result
assert "Stdout: 2" in result
def test_python_repl_tool_syntax_error():
code = "print(1 + )"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "SyntaxError" in result
def test_python_repl_tool_runtime_error():
code = "print(1 / 0)"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "ZeroDivisionError" in result
def test_python_repl_tool_name_error():
code = "print(undefined_variable)"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "NameError" in result
def test_python_repl_tool_type_error():
code = "'2' + 2"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "TypeError" in result
def test_python_repl_tool_import_error():
code = "from nonexistent_module import something"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "ModuleNotFoundError" in result
def test_python_repl_tool_exception():
code = "raise Exception('Test')"
result = python_repl_tool(code)
assert "Error executing code:" in result
assert code in result
assert "Exception" in result

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
import os
import pytest
from unittest.mock import patch
from src.tools.python_repl import python_repl_tool
@@ -8,6 +9,7 @@ from src.tools.python_repl import python_repl_tool
class TestPythonReplTool:
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_successful_code_execution(self, mock_logger, mock_repl):
@@ -26,24 +28,20 @@ class TestPythonReplTool:
assert code in result
assert expected_output in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_invalid_input_type(self, mock_logger, mock_repl):
# Arrange
invalid_code = 123
# Act & Assert - expect ValidationError from LangChain
with pytest.raises(Exception) as exc_info:
# Act & Assert - expect ValidationError when passing invalid input
with pytest.raises(Exception): # Could be ValidationError or similar
python_repl_tool(invalid_code)
# Verify that it's a validation error
assert "ValidationError" in str(
type(exc_info.value)
) or "validation error" in str(exc_info.value)
# The REPL should not be called since validation fails first
mock_repl.run.assert_not_called()
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_code_execution_with_error_in_result(self, mock_logger, mock_repl):
@@ -62,6 +60,7 @@ class TestPythonReplTool:
assert code in result
assert error_result in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_code_execution_with_exception_in_result(self, mock_logger, mock_repl):
@@ -80,6 +79,7 @@ class TestPythonReplTool:
assert code in result
assert exception_result in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_code_execution_raises_exception(self, mock_logger, mock_repl):
@@ -98,6 +98,7 @@ class TestPythonReplTool:
assert code in result
assert repr(exception) in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_successful_execution_with_calculation(self, mock_logger, mock_repl):
@@ -117,6 +118,7 @@ class TestPythonReplTool:
assert code in result
assert expected_output in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_empty_string_code(self, mock_logger, mock_repl):
@@ -132,6 +134,7 @@ class TestPythonReplTool:
mock_logger.info.assert_called_with("Code execution successful")
assert "Successfully executed:" in result
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "true"})
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_logging_calls(self, mock_logger, mock_repl):
@@ -145,3 +148,74 @@ class TestPythonReplTool:
# Assert
mock_logger.info.assert_any_call("Executing Python code")
mock_logger.info.assert_any_call("Code execution successful")
# New tests for configuration behavior
@patch.dict(os.environ, {"ENABLE_PYTHON_REPL": "false"})
@patch("src.tools.python_repl.logger")
def test_tool_disabled(self, mock_logger):
# Arrange
code = "print('test')"
# Act
result = python_repl_tool(code)
# Assert
mock_logger.warning.assert_called_with(
"Python REPL tool is disabled. Please enable it in environment configuration."
)
assert "Tool disabled:" in result
assert "Python REPL tool is disabled" in result
@patch.dict(os.environ, {}, clear=True)
@patch("src.tools.python_repl.logger")
def test_tool_disabled_by_default(self, mock_logger):
# Arrange - remove any existing ENABLE_PYTHON_REPL variable
if "ENABLE_PYTHON_REPL" in os.environ:
del os.environ["ENABLE_PYTHON_REPL"]
code = "print('test')"
# Act
result = python_repl_tool(code)
# Assert
mock_logger.warning.assert_called_with(
"Python REPL tool is disabled. Please enable it in environment configuration."
)
assert "Tool disabled:" in result
@pytest.mark.parametrize("env_value", ["true", "True", "TRUE", "1", "yes", "on"])
@patch("src.tools.python_repl.repl")
@patch("src.tools.python_repl.logger")
def test_tool_enabled_with_various_truthy_values(
self, mock_logger, mock_repl, env_value
):
# Arrange
with patch.dict(os.environ, {"ENABLE_PYTHON_REPL": env_value}):
code = "print('enabled')"
expected_output = "enabled\n"
mock_repl.run.return_value = expected_output
# Act
result = python_repl_tool(code)
# Assert
mock_repl.run.assert_called_once_with(code)
assert "Successfully executed:" in result
@pytest.mark.parametrize(
"env_value", ["false", "False", "FALSE", "0", "no", "off", ""]
)
@patch("src.tools.python_repl.logger")
def test_tool_disabled_with_various_falsy_values(self, mock_logger, env_value):
# Arrange
with patch.dict(os.environ, {"ENABLE_PYTHON_REPL": env_value}):
code = "print('disabled')"
# Act
result = python_repl_tool(code)
# Assert
mock_logger.warning.assert_called_with(
"Python REPL tool is disabled. Please enable it in environment configuration."
)
assert "Tool disabled:" in result