# Python project configuration for DeepTutor # This file configures Black, Ruff, and other Python tools [build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "deeptutor" dynamic = ["version"] description = "An agent-native intelligent learning companion with multi-agent collaboration and RAG" requires-python = ">=3.11" readme = "README.md" license = {text = "Apache-2.0"} dependencies = [ # Core runtime "PyYAML>=6.0", "jinja2>=3.1.0", "openai>=1.30.0", "tiktoken>=0.5.0", "aiohttp>=3.9.4", "httpx>=0.27.0", "requests>=2.32.2", "ddgs>=9.9.1", "nest_asyncio>=1.5.8", "tenacity>=8.0.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "aiosqlite>=0.19.0", "typer[all]>=0.9.0", "rich>=13.0.0", "prompt_toolkit>=3.0.36", "pyte>=0.8.1", # in-memory terminal emulator: scrape Claude Code's /model TUI on sync # Full app dependencies included by default for `pip install deeptutor`. "anthropic>=0.30.0", "dashscope>=1.14.0", "perplexityai>=0.1.0", "oauth-cli-kit>=0.1.1", "llama-index>=0.14.12", "llama-index-retrievers-bm25>=0.7.1,<0.8.0", # FAISS vector store: vectorized ANN retrieval that replaces SimpleVectorStore's # brute-force per-query scan for large knowledge bases (issue #552). The # pipeline imports it lazily and falls back to SimpleVectorStore if absent. "llama-index-vector-stores-faiss>=0.4.0,<1.0.0", "faiss-cpu>=1.8.0,<2.0.0", "PyMuPDF>=1.26.0", "numpy>=1.24.0,<3.0.0", "arxiv>=2.0.0", "python-docx>=1.1.0", "openpyxl>=3.1.0", "python-pptx>=1.0.0", "pypdf>=4.0.0", "pdfplumber>=0.11.0,<0.11.8", # 0.11.8+ pins pdfminer.six==20251230, conflicts with mineru (via raganything) "reportlab>=4.0.0", "defusedxml>=0.7.1", "fastapi>=0.100.0", "uvicorn[standard]>=0.24.0", "websockets>=12.0", "python-multipart>=0.0.6", "bcrypt>=4.0.0", "python-jose[cryptography]>=3.3.0", "pocketbase>=0.12.0", "loguru>=0.7.3,<1.0.0", "json-repair>=0.57.0,<1.0.0", ] [project.scripts] deeptutor = "deeptutor_cli.main:main" [project.optional-dependencies] # Compatibility extra for source installs and older docs. These packages are # already included in the public `deeptutor` wheel; the local CLI-only project # under `packaging/deeptutor-cli` uses the same list without Web assets/server deps. cli = [ # LLM provider SDKs "anthropic>=0.30.0", "dashscope>=1.14.0", "perplexityai>=0.1.0", "oauth-cli-kit>=0.1.1", # RAG (LlamaIndex) "llama-index>=0.14.12", "llama-index-retrievers-bm25>=0.7.1,<0.8.0", # FAISS vector store: vectorized ANN retrieval that replaces SimpleVectorStore's # brute-force per-query scan for large knowledge bases (issue #552). The # pipeline imports it lazily and falls back to SimpleVectorStore if absent. "llama-index-vector-stores-faiss>=0.4.0,<1.0.0", "faiss-cpu>=1.8.0,<2.0.0", "PyMuPDF>=1.26.0", "numpy>=1.24.0,<3.0.0", "arxiv>=2.0.0", # Document text extraction (chat attachments) + office-skill authoring libs "python-docx>=1.1.0", "openpyxl>=3.1.0", "python-pptx>=1.0.0", "pypdf>=4.0.0", "pdfplumber>=0.11.0,<0.11.8", # 0.11.8+ pins pdfminer.six==20251230, conflicts with mineru (via raganything) "reportlab>=4.0.0", "defusedxml>=0.7.1", ] # Compatibility extra for source installs and older docs. These packages are # already included in the public `deeptutor` wheel. server = [ "deeptutor[cli]", "fastapi>=0.100.0", "uvicorn[standard]>=0.24.0", "websockets>=12.0", "python-multipart>=0.0.6", "bcrypt>=4.0.0", "python-jose[cryptography]>=3.3.0", "pocketbase>=0.12.0", "loguru>=0.7.3,<1.0.0", "json-repair>=0.57.0,<1.0.0", "croniter>=6.0.0,<7.0.0", ] # Partner channel SDKs + MCP client. Mirrors requirements/partners.txt. # Partners run on the chat agent loop, so there is no engine here — just the # IM platform SDKs and the MCP client used for deferred tools. partners = [ "deeptutor[server]", "mcp>=1.26.0,<2.0.0", "python-telegram-bot[socks]>=22.6,<23.0", "wecom-aibot-sdk>=1.0.8,<2.0.0", "lark-oapi>=1.5.0,<2.0.0", "dingtalk-stream>=0.24.0,<1.0.0", "slack-sdk>=3.39.0,<4.0.0", "slackify-markdown>=0.2.0,<1.0.0", "qq-botpy>=1.2.0,<2.0.0", "python-socketio>=5.16.0,<6.0.0", "msgpack>=1.1.0,<2.0.0", "python-socks[asyncio]>=2.8.0,<3.0.0", "socksio>=1.0.0,<2.0.0", "websocket-client>=1.9.0,<2.0.0", "zulip>=0.8.0,<1.0.0", # slack-sdk Socket Mode + napcat need the async `websockets` lib — pin it # explicitly instead of relying on dingtalk-stream/lark-oapi transitives. "websockets>=12.0", # napcat (OneBot v11) media downloads "aiohttp>=3.10.0,<4.0.0", # msteams Bot Framework token validation "PyJWT[crypto]>=2.8.0,<3.0.0", # weixin QR-code login "qrcode>=7.4.0,<9.0.0", ] # Optional document-parsing engines for the shared parse layer # (deeptutor/services/parsing). MinerU is an external CLI / hosted API and is # not a pip dependency. Engines import lazily, so these stay opt-in and an # absent engine simply reports unavailable in Settings → Document Parsing. parse-markitdown = ["markitdown[pdf,docx,pptx,xlsx]>=0.0.1a2"] parse-docling = ["docling>=2.0.0"] # PyMuPDF4LLM rides on PyMuPDF (already a core dep); the extra adds only the # thin Markdown layer, so it is the lightest image-capable engine. Pinned to the # pre-1.0 line on purpose: 1.x defaults to an onnxruntime layout-AI path that # pulls a heavy dep, downloads a model, and can't extract images — defeating the # "lightweight, runs on a Pi, extracts images" point of this engine. parse-pymupdf4llm = ["pymupdf4llm>=0.0.17,<1.0"] parse = [ "deeptutor[parse-markitdown]", "deeptutor[parse-docling]", "deeptutor[parse-pymupdf4llm]", ] # Legacy alias kept for one release: `pip install deeptutor[tutorbot]`. tutorbot = ["deeptutor[partners]"] # Matrix (Element) channel for partners. Mirrors requirements/matrix.txt. # Default Matrix install is non-E2EE so it works without libolm/python-olm. matrix = [ "matrix-nio>=0.25.2,<1.0.0", "mistune>=3.0.0,<4.0.0", "nh3>=0.2.18,<1.0.0", ] # Optional Matrix E2EE addon. Mirrors requirements/matrix-e2e.txt and requires libolm. matrix-e2e = [ "deeptutor[matrix]", "matrix-nio[e2e]>=0.25.2,<1.0.0", ] # Math Animator addon (Manim). Mirrors requirements/math-animator.txt. # System prerequisites (installed outside pip): LaTeX, pkg-config, cairo, cmake, ffmpeg. math-animator = ["manim>=0.19.0"] # GraphRAG knowledge-base engine (microsoft/graphrag). Optional, heavy # (LiteLLM + lancedb + graspologic) and kept out of `all` so it stays a # deliberate opt-in. The pipeline imports lazily and the adapter targets the # 3.x API (see services/rag/pipelines/graphrag/engine.py). Every graphrag 3.x # release is capped at python <3.14; the `python_version` marker mirrors that # so on 3.14+ the extra resolves to a no-op instead of an unsatisfiable hard # dep — otherwise pip backtracks deeptutor to the last extra-free version # (1.4.5) and silently downgrades. Floor is 3.0.1 (3.0.0/3.0.3 were yanked). graphrag = ["graphrag>=3.0.1,<4.0.0; python_version < '3.14'"] # LightRAG knowledge-base engine (HKUDS/LightRAG) with multimodal parsing via # RAG-Anything (HKUDS/RAG-Anything). Optional, heavy (pulls MinerU transitively — # see the pdfplumber pin above). The pipeline imports lazily and the adapter is # isolated in services/rag/pipelines/lightrag/engine.py. raganything pulls # `mineru[core]`, which is capped at python <3.14, so this carries the same # `python_version` marker as graphrag to avoid a silent backtrack/downgrade on # 3.14+. Floor is 1.0.1 (no 1.0.0 was ever published) with a <2.0.0 ceiling so # a future API-breaking 2.x isn't pulled in silently. rag-lightrag = ["raganything>=1.0.1,<2.0.0; python_version < '3.14'"] # Dev/test tooling. Mirrors requirements/dev.txt. dev = [ "deeptutor[server]", "pytest>=7.0.0", "pytest-asyncio>=0.23.0", "pre-commit>=3.0.0", "safety<3.0.0", "bandit>=1.8.0", # Test fixture builders for document-loader tests. Runtime pulls these via # cli/server, but dev keeps them explicit for pytest collection. "python-docx>=1.1.0", "openpyxl>=3.1.0", "python-pptx>=1.0.0", "pypdf>=4.0.0", "defusedxml>=0.7.1", ] # Everything: server + partners + non-E2EE matrix + math-animator + dev tools. all = [ "deeptutor[partners]", "deeptutor[matrix]", "deeptutor[math-animator]", "deeptutor[dev]", ] [tool.setuptools] include-package-data = true [tool.setuptools.dynamic] version = {attr = "deeptutor.__version__.__version__"} [tool.setuptools.packages.find] where = ["."] include = ["deeptutor*", "deeptutor_cli*"] [tool.setuptools.package-data] # Runtime data shipped inside the deeptutor package: prompt YAMLs (loaded by # PromptManager from PACKAGE_ROOT/deeptutor/.../prompts/*.yaml), markdown # templates (builtin SKILL.md files, vision_solver prompts), and helper shell # scripts. Without these, ``pip install deeptutor`` ships a wheel missing # every agent prompt and capability fails at first use. deeptutor = [ "**/*.yaml", "**/*.yml", "**/*.md", "**/*.json", "**/*.sh", "**/*.j2", "**/*.jinja", ] deeptutor_cli = ["**/*.md"] deeptutor_web = ["**/*", ".next/**/*"] # ============================================ # Black code formatter configuration # ============================================ [tool.black] line-length = 100 target-version = ['py311', 'py312', 'py313', 'py314'] include = '\.pyi?$' extend-exclude = ''' /( # Exclude directories \.eggs | \.git | \.hg | \.mypy_cache | \.tox | \.venv | venv | _build | buck-out | build | dist | __pycache__ | node_modules | \.next )/ ''' # ============================================ # Ruff linter and formatter configuration # ============================================ [tool.ruff] # Target Python version target-version = "py311" line-length = 100 [tool.ruff.lint] # Enable only foundational lint rules (intentionally relaxed) select = [ "E", # pycodestyle errors (语法错误) "F", # pyflakes (未使用的导入、变量等) "I", # isort (import 排序) # Keep only essential checks; other rules stay relaxed ] extend-select = [ "B006", # Do not use mutable data structures for argument defaults ] # Ignore specific rules (keep the lint profile relaxed) ignore = [ "E501", # Line too long (handled by formatter) "B008", # Do not perform function calls in argument defaults "PLR0911", # Too many return statements "PLR0912", # Too many branches "PLR0913", # Too many arguments "PLR0915", # Too many statements "PLR2004", # Magic value used in comparison "TRY003", # Avoid specifying long messages outside exception class "T201", # print found (allowed for debugging scripts) "E722", # Do not use bare except (kept relaxed for legacy paths) "E402", # Module level import not at top of file (kept relaxed) "PLC0415", # import should be at top-level (kept relaxed) "PTH123", # open() should be replaced by Path.open() (standard open allowed) "PTH103", # os.makedirs() should be replaced by Path.mkdir() (os.makedirs allowed) "PTH118", # os.path.join() should be replaced by Path (os.path.join allowed) "EM101", # Exception must not use a string literal "EM102", # Exception must not use an f-string literal "TRY002", # Create your own exception "TRY300", # Consider moving this statement to an else block "DTZ005", # datetime.now() called without tz argument "RET504", # Unnecessary assignment before return "PLW2901", # for loop variable overwritten by assignment target "F841", # Local variable assigned but never used "ERA001", # Found commented-out code (kept relaxed) ] # Exclude patterns exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache", ".nox", ".pants.d", ".pyenv", ".pytest_cache", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", ".vscode", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", ".next", "out", "venv", "__pycache__", ] # Allow autofix for all enabled rules fixable = ["ALL"] unfixable = [] # Allow unused variables when underscore-prefixed dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.format] # Use double quotes for strings quote-style = "double" # Indent with spaces indent-style = "space" # Respect magic trailing comma skip-magic-trailing-comma = false # Automatically detect the appropriate line ending line-ending = "auto" # ============================================ # Ruff import sorting (isort replacement) # ============================================ [tool.ruff.lint.isort] known-first-party = ["deeptutor", "deeptutor_cli", "scripts"] force-sort-within-sections = true split-on-trailing-comma = true # ============================================ # Ruff per-file-ignores # ============================================ [tool.ruff.lint.per-file-ignores] # Allow longer lines in test files # F401: Allow unused imports for availability checks in tests "**/test_*.py" = ["E501", "PLR2004", "F401"] "**/__init__.py" = ["F401"] # Allow unused imports in __init__.py # ============================================ # Ruff mccabe complexity # ============================================ [tool.ruff.lint.mccabe] max-complexity = 10 # ============================================ # Pytest configuration # ============================================ [tool.pytest.ini_options] testpaths = ["tests", "deeptutor/learning/tests"] pythonpath = ["."] markers = [ "asyncio: mark async tests that require pytest-asyncio", ] asyncio_default_fixture_loop_scope = "function" addopts = [ "--strict-markers", "--strict-config", "--disable-warnings", "--tb=short", "--import-mode=importlib", ] # ============================================ # MyPy type checking configuration # ============================================ [tool.mypy] python_version = "3.11" # Relaxed type checking for gradual adoption pretty = false show_error_context = false warn_return_any = false warn_no_return = false show_error_codes = true ignore_missing_imports = true follow_imports = "silent" # Disable strict checks that generate too many errors check_untyped_defs = false disallow_untyped_defs = false disallow_incomplete_defs = false no_implicit_optional = false # Module-specific overrides [[tool.mypy.overrides]] module = "tests.*" ignore_errors = true [[tool.mypy.overrides]] module = "deeptutor.tools.*" ignore_errors = true # Some tools may have dynamic imports # ============================================ # Bandit security linting configuration # ============================================ [tool.bandit] exclude_dirs = ["tests", "scripts"] # B101=assert, B311=random, B403/B404=pickle/subprocess imports # B110=try_except_pass (intentional for non-critical error handling) # B104=hardcoded_bind_all_interfaces (required for server binding) # B112=try_except_continue (intentional for iteration) # B105=hardcoded_password_string (false positive on empty string initialization) # B301=pickle (used for internal embedding cache, not untrusted data) # B501=request_with_no_cert_validation (opt-in via env var for dev/testing) # B603=subprocess_without_shell_equals_true (controlled execution) # B202=tarfile_unsafe_members (already using safe_members filter) skips = ["B101", "B311", "B403", "B404", "B110", "B104", "B112", "B105", "B301", "B501", "B603", "B202"]