mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-02 16:55:42 +08:00
### What problem does this PR solve? Closes https://github.com/infiniflow/ragflow/issues/14571. Adds CAJAL as a first-class local scientific-writing option in RAGFlow: - registers `agnuxo/cajal-4b-p2pclaw` as a known Ollama chat model with a 32K context setting - adds a built-in “CAJAL scientific paper agent” template under the existing agent template catalog - preconfigures the agent for grounded scientific writing: retrieval first, citation traceability, LaTeX-ready output, and explicit limitations when evidence is missing - adds unit coverage to ensure the template normalizes through RAGFlow’s production template loader, keeps graph form data in sync, and exposes the Ollama model option Behavior/evidence gathered for the requested model: - Hugging Face model metadata for `Agnuxo/CAJAL-4B-P2PCLAW` reports `pipeline_tag=text-generation` and tags including `gguf`, `llama.cpp`, `vllm`, `scientific-research`, `papers`, `academic-writing`, `latex`, and `license:apache-2.0`. - The model card documents CAJAL as a 4B scientific paper generation model with 32K context, local inference, LaTeX/citation specialization, and CPU-only support around 5 tok/s on Ryzen 7 5800X. - Local CPU generation could not be completed on this machine because the advertised Ollama model name is not currently resolvable from Ollama’s registry: both `https://registry.ollama.ai/v2/agnuxo/cajal-4b-p2pclaw/manifests/latest` and `https://registry.ollama.ai/v2/library/agnuxo/cajal-4b-p2pclaw/manifests/latest` returned `404 Not Found`; the Hugging Face repo tree currently exposes an 8.4 GB `model.safetensors` but no GGUF artifact in `main`. The template therefore targets the documented Ollama model name for users who have the local CAJAL deployment/model file available. Verification run locally: ```bash python3 -m pytest test/test_cajal_template_unit.py -q # 3 passed in 0.34s python3 - <<'PY' import json, glob for f in sorted(glob.glob('agent/templates/*.json') + ['conf/llm_factories.json']): with open(f, encoding='utf-8') as fp: json.load(fp) print('json_ok') PY # json_ok python3 -m ruff check test/test_cajal_template_unit.py # All checks passed! git diff --check ``` `uv run pytest test/testcases/test_web_api/test_agent_app/test_cajal_template_unit.py -q` was also attempted first, but dependency setup failed before test collection while building `ormsgpack==1.5.0` from uv with a package metadata parse error. Clearing uv’s `ormsgpack` cache and retrying reproduced the same build failure, so the focused unit test was run with the system Python environment instead. ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe): --------- Co-authored-by: sxxtony <sxxtony@users.noreply.github.com> Co-authored-by: yzc <yzc@users.noreply.github.com> Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
80 lines
3.5 KiB
Python
80 lines
3.5 KiB
Python
#
|
|
# Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed 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 json
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
|
|
def _load_template_utils():
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
module_path = repo_root / "api" / "db" / "template_utils.py"
|
|
spec = importlib.util.spec_from_file_location("template_utils", module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def _load_cajal_template():
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
template_path = repo_root / "agent" / "templates" / "cajal_scientific_paper_agent.json"
|
|
with template_path.open(encoding="utf-8") as template_file:
|
|
return _load_template_utils().normalize_canvas_template_categories(json.load(template_file))
|
|
|
|
|
|
def test_cajal_template_exposes_local_ollama_model_and_agent_categories():
|
|
template = _load_cajal_template()
|
|
|
|
assert template["id"] == "41"
|
|
assert template["title"]["en"] == "CAJAL scientific paper agent"
|
|
assert template["canvas_type"] == "Agent"
|
|
assert template["canvas_types"] == ["Agent", "Recommended"]
|
|
|
|
agent_params = template["dsl"]["components"]["Agent:NewPumasLick"]["obj"]["params"]
|
|
assert agent_params["llm_id"] == "agnuxo/cajal-4b-p2pclaw@Ollama"
|
|
assert agent_params["max_tokens"] == 32768
|
|
assert "Agnuxo/CAJAL-4B-P2PCLAW" in agent_params["sys_prompt"]
|
|
assert "LaTeX" in agent_params["sys_prompt"]
|
|
|
|
|
|
def test_cajal_template_keeps_retrieval_grounding_and_graph_form_in_sync():
|
|
template = _load_cajal_template()
|
|
agent_params = template["dsl"]["components"]["Agent:NewPumasLick"]["obj"]["params"]
|
|
retrieval_tools = [tool for tool in agent_params["tools"] if tool["component_name"] == "Retrieval"]
|
|
|
|
assert len(retrieval_tools) == 1
|
|
assert retrieval_tools[0]["params"]["top_n"] == 10
|
|
assert "ground" in retrieval_tools[0]["params"]["description"].lower()
|
|
assert "{sys.query}" in agent_params["prompts"][0]["content"]
|
|
|
|
agent_node = next(node for node in template["dsl"]["graph"]["nodes"] if node["id"] == "Agent:NewPumasLick")
|
|
begin_node = next(node for node in template["dsl"]["graph"]["nodes"] if node["id"] == "begin")
|
|
|
|
assert agent_node["data"]["form"]["llm_id"] == agent_params["llm_id"]
|
|
assert agent_node["data"]["form"]["sys_prompt"] == agent_params["sys_prompt"]
|
|
assert "CAJAL" in begin_node["data"]["form"]["prologue"]
|
|
|
|
|
|
def test_cajal_is_registered_as_a_known_ollama_chat_model():
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
factories_path = repo_root / "conf" / "llm_factories.json"
|
|
factories = json.loads(factories_path.read_text(encoding="utf-8"))
|
|
ollama = next(factory for factory in factories["factory_llm_infos"] if factory["name"] == "Ollama")
|
|
cajal = next(model for model in ollama["llm"] if model["llm_name"] == "agnuxo/cajal-4b-p2pclaw")
|
|
|
|
assert cajal["model_type"] == "chat"
|
|
assert cajal["max_tokens"] == 32768
|
|
assert "SCIENTIFIC_WRITING" in cajal["tags"]
|