UIengF commented on code in PR #368: URL: https://github.com/apache/hugegraph-ai/pull/368#discussion_r3563343771
########## hugegraph-llm/src/hugegraph_llm/api/thin_api.py: ########## @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import time +from typing import Any +from uuid import uuid4 + +from fastapi import APIRouter, status + +from hugegraph_llm.api.models.rag_requests import ( + GraphExtractRequest, + GraphImportRequest, + VidEmbeddingsRefreshRequest, +) +from hugegraph_llm.api.models.rag_response import ThinAPIResponse +from hugegraph_llm.flows import FlowName +from hugegraph_llm.flows.scheduler import SchedulerSingleton +from hugegraph_llm.utils.log import log + +thin_router = APIRouter() + + +def _generate_request_id() -> str: + return f"req-{uuid4().hex[:12]}" + + +def _envelope_ok( + data: Any, *, warnings: list[str] | None = None, next_actions: list[str] | None = None +) -> dict[str, Any]: + return { + "ok": True, + "data": data, + "error": None, + "warnings": warnings or [], + "next_actions": next_actions or [], + "meta": { + "request_id": _generate_request_id(), + "duration_ms": 0, + }, + } + + +def _envelope_err( + error_type: str, message: str, *, suggestion: str | None = None, details: Any = None +) -> dict[str, Any]: + return { + "ok": False, + "data": None, + "error": { + "type": error_type, + "message": message, + "suggestion": suggestion, + "retryable": False, + "source": "hugegraph-llm", + "details": details if details is not None else {}, + }, + "warnings": [], + "next_actions": [], + "meta": { + "request_id": _generate_request_id(), + "duration_ms": 0, + }, + } + + +def _wrap_flow_call(flow_name: FlowName, *args: Any) -> dict[str, Any]: + start = time.perf_counter() + try: + result = SchedulerSingleton.get_instance().schedule_flow(flow_name, *args) + envelope = _envelope_ok(result) + envelope["meta"]["duration_ms"] = (time.perf_counter() - start) * 1000.0 + return envelope + except Exception as exc: + log.error("Thin API flow execution failed: %s", exc, exc_info=True) + envelope = _envelope_err( + "FLOW_EXECUTION_FAILED", + "An internal error occurred during flow execution.", + suggestion="Check HugeGraph-AI service logs for details.", + ) + envelope["meta"]["duration_ms"] = (time.perf_counter() - start) * 1000.0 + return envelope + + +@thin_router.post("/graph-extract", status_code=status.HTTP_200_OK, response_model=ThinAPIResponse) +def graph_extract_api(req: GraphExtractRequest): + return _wrap_flow_call( + FlowName.GRAPH_EXTRACT, + req.graph_schema, + req.text, + req.example_prompt, + "property_graph", + req.language, Review Comment: Fixed in `1833b36`. `/graph-extract` now passes `split_type="document"` and `language=req.language` as explicit keywords. Tests assert both the scheduler call and the real `GraphExtractFlow.prepare()` contract. ########## hugegraph-mcp/hugegraph_mcp/plan_hash.py: ########## @@ -0,0 +1,210 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Target-bound plan hash — 防止跨图、跨用户、过期重放。 + +PlanContext 将工具名、模式、图目标、主体、readonly、payload 摘要、 +schema 摘要、nonce 和过期时间绑定到一个哈希中。 +confirm 时重新计算并比较,拒绝不匹配或过期的计划。 +""" + +from __future__ import annotations + +import hashlib +import json +import time +from dataclasses import asdict, dataclass, field +from typing import Any + +from hugegraph_mcp.config import MCPConfig +from hugegraph_mcp.envelope import ErrorType + + +# 默认计划有效期(秒) +DEFAULT_PLAN_TTL_SECONDS = 600 # 10 分钟 + + +@dataclass(frozen=True) +class PlanContext: + """计划上下文 — 绑定到特定工具、目标和时间窗口。""" + + tool_name: str + mode: str + graph_url: str + graph_name: str + graphspace: str + principal: str + readonly: bool + payload_digest: str + schema_hash: str | None + nonce: str + expires_at: int + extra_context: dict[str, Any] = field(default_factory=dict) + + +def compute_plan_hash(context: PlanContext) -> str: Review Comment: Fixed in `1833b36` with a durable server-issued plan ledger. Writable dry-run persists nonce-digest/hash/expiry; confirm atomically validates and consumes the exact issued record, enforces the server 600-second maximum TTL, and rejects unissued client-computed hashes. Concurrency, restart persistence, replay, expiry, and old-DB migration tests pass. Real-image verification confirmed an unissued computed hash and replay both produce zero writes. ########## hugegraph-llm/src/hugegraph_llm/api/thin_api.py: ########## @@ -0,0 +1,122 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import time +from typing import Any +from uuid import uuid4 + +from fastapi import APIRouter, status + +from hugegraph_llm.api.models.rag_requests import ( + GraphExtractRequest, + GraphImportRequest, + VidEmbeddingsRefreshRequest, +) +from hugegraph_llm.api.models.rag_response import ThinAPIResponse +from hugegraph_llm.flows import FlowName +from hugegraph_llm.flows.scheduler import SchedulerSingleton +from hugegraph_llm.utils.log import log + +thin_router = APIRouter() + + +def _generate_request_id() -> str: + return f"req-{uuid4().hex[:12]}" + + +def _envelope_ok( + data: Any, *, warnings: list[str] | None = None, next_actions: list[str] | None = None +) -> dict[str, Any]: + return { + "ok": True, + "data": data, + "error": None, + "warnings": warnings or [], + "next_actions": next_actions or [], + "meta": { + "request_id": _generate_request_id(), + "duration_ms": 0, + }, + } + + +def _envelope_err( + error_type: str, message: str, *, suggestion: str | None = None, details: Any = None +) -> dict[str, Any]: + return { + "ok": False, + "data": None, + "error": { + "type": error_type, + "message": message, + "suggestion": suggestion, + "retryable": False, + "source": "hugegraph-llm", + "details": details if details is not None else {}, + }, + "warnings": [], + "next_actions": [], + "meta": { + "request_id": _generate_request_id(), + "duration_ms": 0, + }, + } + + +def _wrap_flow_call(flow_name: FlowName, *args: Any) -> dict[str, Any]: + start = time.perf_counter() + try: + result = SchedulerSingleton.get_instance().schedule_flow(flow_name, *args) + envelope = _envelope_ok(result) + envelope["meta"]["duration_ms"] = (time.perf_counter() - start) * 1000.0 + return envelope + except Exception as exc: + log.error("Thin API flow execution failed: %s", exc, exc_info=True) + envelope = _envelope_err( + "FLOW_EXECUTION_FAILED", + "An internal error occurred during flow execution.", + suggestion="Check HugeGraph-AI service logs for details.", + ) + envelope["meta"]["duration_ms"] = (time.perf_counter() - start) * 1000.0 + return envelope + + +@thin_router.post("/graph-extract", status_code=status.HTTP_200_OK, response_model=ThinAPIResponse) +def graph_extract_api(req: GraphExtractRequest): + return _wrap_flow_call( + FlowName.GRAPH_EXTRACT, + req.graph_schema, + req.text, + req.example_prompt, + "property_graph", + req.language, + ) + + +@thin_router.post("/graph-import", status_code=status.HTTP_200_OK, response_model=ThinAPIResponse) Review Comment: Fixed in `1833b36`. Both legacy Thin API write endpoints are disabled by default and require `ENABLE_LOGIN=true` plus `HUGEGRAPH_LLM_ENABLE_THIN_WRITES=true`. The production router applies Bearer authentication. Tests cover missing, wrong, and correct tokens for both routes and verify only the correct `USER_TOKEN` schedules a write. Authentication errors no longer echo submitted tokens. ########## .github/workflows/hugegraph-mcp.yml: ########## @@ -0,0 +1,145 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: HugeGraph-MCP CI + +on: + push: + branches: + - "main" + - "release-*" + paths: + - "hugegraph-mcp/**" + - "hugegraph-python-client/**" + - "pyproject.toml" + - "uv.lock" + - ".github/workflows/hugegraph-mcp.yml" + pull_request: + paths: + - "hugegraph-mcp/**" + - "hugegraph-python-client/**" + - "pyproject.toml" + - "uv.lock" + - ".github/workflows/hugegraph-mcp.yml" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + key: ${{ runner.os }}-mcp-uv-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml', 'uv.lock') }} + restore-keys: | + ${{ runner.os }}-mcp-uv-${{ matrix.python-version }}- + + - name: Install MCP dependencies + run: | + uv sync --extra mcp --extra dev + + - name: Check MCP formatting + working-directory: hugegraph-mcp + run: | + uv run ruff format --check hugegraph_mcp tests + + - name: Lint MCP + working-directory: hugegraph-mcp + run: | + uv run ruff check hugegraph_mcp tests + + - name: Run MCP tests + working-directory: hugegraph-mcp + run: | + uv run pytest -m "not live and not integration and not llm" + + real-hugegraph-write-path: Review Comment: Fixed in the fourth commit and retained in `1833b36`: the workflow now declares `permissions: contents: read`. Local YAML/pre-commit validation passes. ########## .github/workflows/hugegraph-mcp.yml: ########## @@ -0,0 +1,145 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name: HugeGraph-MCP CI + +on: + push: + branches: + - "main" + - "release-*" + paths: + - "hugegraph-mcp/**" + - "hugegraph-python-client/**" + - "pyproject.toml" + - "uv.lock" + - ".github/workflows/hugegraph-mcp.yml" + pull_request: + paths: + - "hugegraph-mcp/**" + - "hugegraph-python-client/**" + - "pyproject.toml" + - "uv.lock" + - ".github/workflows/hugegraph-mcp.yml" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + key: ${{ runner.os }}-mcp-uv-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml', 'uv.lock') }} + restore-keys: | + ${{ runner.os }}-mcp-uv-${{ matrix.python-version }}- + + - name: Install MCP dependencies + run: | + uv sync --extra mcp --extra dev + + - name: Check MCP formatting + working-directory: hugegraph-mcp + run: | + uv run ruff format --check hugegraph_mcp tests + + - name: Lint MCP + working-directory: hugegraph-mcp + run: | + uv run ruff check hugegraph_mcp tests + + - name: Run MCP tests + working-directory: hugegraph-mcp + run: | + uv run pytest -m "not live and not integration and not llm" + + real-hugegraph-write-path: + runs-on: ubuntu-latest + services: + hugegraph: + image: hugegraph/hugegraph:1.7.0 + env: + PASSWORD: admin + options: >- + --health-cmd="curl -f http://localhost:8080/versions || exit 1" + --health-interval=10s + --health-timeout=5s + --health-retries=12 + ports: + - 8080:8080 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install uv + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + key: ${{ runner.os }}-mcp-real-hugegraph-uv-${{ hashFiles('**/pyproject.toml', 'uv.lock') }} + restore-keys: | + ${{ runner.os }}-mcp-real-hugegraph-uv- + + - name: Install MCP dependencies + run: | + uv sync --extra mcp --extra dev + + - name: Run real HugeGraph write-path tests + working-directory: hugegraph-mcp + env: + RUN_MCP_REAL_HUGEGRAPH_TESTS: "1" + HUGEGRAPH_URL: http://127.0.0.1:8080 + HUGEGRAPH_GRAPH_PATH: DEFAULT/hugegraph + HUGEGRAPH_USER: admin + HUGEGRAPH_PASSWORD: admin + HUGEGRAPH_MCP_READONLY: "false" + HUGEGRAPH_MCP_ALLOW_AI: "false" + run: | + uv run pytest tests/integration/test_real_write_path.py -m real_hugegraph Review Comment: Fixed in the fourth commit and retained in `1833b36`: the workflow-level `permissions: contents: read` applies to this job as well. Local YAML/pre-commit validation passes. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
