{"repo": "bzanniupoiiu/be-ai-chat", "files": {"ai-chat-service/pkg/config/config.go": {"0353cb3fe18ac22fb31445ef7ce2195c57358b35": {"t": 1785745644.6893995, "n": 2309, "h": "dbb32347a13d5f52", "c": "package config\n\nimport (\n\t\"github.com/spf13/viper\"\n\t\"log\"\n\t\"strings\"\n)\n\ntype Config struct {\n\tServer struct {\n\t\tIP string\n\t\tPort int\n\t\tAccessToken string\n\t}\n\tLog struct {\n\t\tLevel string\n\t\tLogPath string `mapstructure:\"logPath\"`\n\t} `mapstructure:\"log\"`\n\tChat struct {\n\t\tApiKey string `mapstructure:\"api_key\"`\n\t\tBaseUrl string `mapstructure:\"base_url\"`\n\t\tModel string `mapstructure:\"model\"`\n\t\tMaxTokens int `mapstructure:\"max_tokens\"`\n\t\tTemperature float32 `mapstructure:\"temperature\"`\n\t\tTopP float32 `mapstructure:\"top_p\"`\n\t\tPresencePenalty float32 `mapstructure:\"presence_penalty\"`\n\t\tFrequencyPenalty float32 `mapstructure:\"frequency_penalty\"`\n\t\tBotDesc string `mapstructure:\"bot_desc\"`\n\t\tMinResponseTokens int `mapstructure:\"min_response_tokens\"`\n\t\tContextTTL int `mapstructure:\"context_ttl\"`\n\t\tContextLen int `mapstructure:\"context_len\"`\n\t}\n\n\tKVStore struct {\n\t\tMasterAddr string `mapstructure:\"master_addr\"`\n\t\tSlaveAddr string `mapstructure:\"slave_addr\"`\n\t\tModelPath string `mapstructure:\"model_path\"`\n\t\tONNXFilename string `mapstructure:\"onnx_filename\"`\n\t\tSearchTopK int `mapstructure:\"search_top_k\"`\n\t\tMinScore float64 `mapstructure:\"min_score\"`\n\t\tMinScoreMargin float64 `mapstructure:\"min_score_margin\"`\n\t} `mapstructure:\"kvstore\"`\n\n\tRAG struct {\n\t\tBaseURL string `mapstructure:\"base_url\"`\n\t\tTimeoutMS int `mapstructure:\"timeout_ms\"`\n\t\tTopK int `mapstructure:\"top_k\"`\n\t\tEvidenceTopK int `mapstructure:\"evidence_top_k\"`\n\t} `mapstructure:\"rag\"`\n\n\tDependOn struct {\n\t\tSensitive struct {\n\t\t\tAddress string\n\t\t\tAccessToken string\n\t\t}\n\t\tKeywords struct {\n\t\t\tAddress string\n\t\t\tAccessToken string\n\t\t}\n\t\tTokenizer struct {\n\t\t\tAddress string\n\t\t}\n\t}\n}\n\nvar conf *Config\n\nfunc InitConfig(filePath string, typ ...string) {\n\tv := viper.New()\n\tv.SetConfigFile(filePath)\n\tv.SetEnvKeyReplacer(strings.NewReplacer(\".\", \"_\"))\n\tv.AutomaticEnv()\n\t_ = v.BindEnv(\"chat.api_key\", \"DEEPSEEK_API_KEY\")\n\tif len(typ) > 0 {\n\t\tv.SetConfigType(typ[0])\n\t}\n\terr := v.ReadInConfig()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tconf = &Config{}\n\terr = v.Unmarshal(conf)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n}\n\nfunc GetConfig() *Config {\n\treturn conf\n}\n"}, "90a5be9893665dd63afa1803ee06fb6deeffa978": {"t": 1785755267.536035, "n": 1660, "h": "8b89c3044c63106d", "c": "package config\n\nimport (\n\t\"github.com/spf13/viper\"\n\t\"log\"\n)\n\ntype Config struct {\n\tServer struct {\n\t\tIP string\n\t\tPort int\n\t\tAccessToken string\n\t}\n\tLog struct {\n\t\tLevel string\n\t\tLogPath string `mapstructure:\"logPath\"`\n\t} `mapstructure:\"log\"`\n\tChat struct {\n\t\tApiKey string `mapstructure:\"api_key\"`\n\t\tBaseUrl string `mapstructure:\"base_url\"`\n\t\tModel string `mapstructure:\"model\"`\n\t\tMaxTokens int `mapstructure:\"max_tokens\"`\n\t\tTemperature float32 `mapstructure:\"temperature\"`\n\t\tTopP float32 `mapstructure:\"top_p\"`\n\t\tPresencePenalty float32 `mapstructure:\"presence_penalty\"`\n\t\tFrequencyPenalty float32 `mapstructure:\"frequency_penalty\"`\n\t\tBotDesc string `mapstructure:\"bot_desc\"`\n\t\tMinResponseTokens int `mapstructure:\"min_response_tokens\"`\n\t\tContextTTL int `mapstructure:\"context_ttl\"`\n\t\tContextLen int `mapstructure:\"context_len\"`\n\t}\n\t\n\tKVStore struct {\n\t\tMasterAddr string `mapstructure:\"master_addr\"`\n\t\tSlaveAddr string `mapstructure:\"slave_addr\"`\n\t} `mapstructure:\"kvstore\"`\n\n\n\tDependOn struct {\n\t\tSensitive struct {\n\t\t\tAddress string\n\t\t\tAccessToken string\n\t\t}\n\t\tKeywords struct {\n\t\t\tAddress string\n\t\t\tAccessToken string\n\t\t}\n\t\tTokenizer struct {\n\t\t\tAddress string\n\t\t}\n\t}\n\t\n}\n\nvar conf *Config\n\nfunc InitConfig(filePath string, typ ...string) {\n\tv := viper.New()\n\tv.SetConfigFile(filePath)\n\tif len(typ) > 0 {\n\t\tv.SetConfigType(typ[0])\n\t}\n\terr := v.ReadInConfig()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tconf = &Config{}\n\terr = v.Unmarshal(conf)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n}\n\nfunc GetConfig() *Config {\n\treturn conf\n}\n"}}, "knowledge-agent/README.md": {"0353cb3fe18ac22fb31445ef7ce2195c57358b35": {"t": 1786068581.994027, "n": 3048, "h": "461513245b33fb29", "c": "# knowledge-agent\n\nPDF-only SCI 论文知识服务。生产链路固定为 GROBID -> 结构化切块 -> BGE-M3 1024 维向量 -> Qdrant Top-20 -> BGE reranker -> 严格证据门控。\n\n## 目录\n\n| 文件/目录 | 责任 |\n| --- | --- |\n| `grobid_client.py` | 调用 GROBID Fulltext API,并把 TEI XML 转成论文结构 |\n| `paper_chunker.py` | 按摘要/章节/段落切分父子块,保存页码和内容类型 |\n| `qdrant_store.py` | 本地或服务模式 Qdrant 持久化索引 |\n| `reranker.py` | `BAAI/bge-reranker-v2-m3` 精排 |\n| `paper_repository.py` | SQLite 保存论文、入库任务和 chunk 元数据 |\n| `document_ingest.py` | PDF-only 入库编排与命令行入口 |\n| `agent_core.py` | Top-20 召回、阈值、rerank、证据与 prompt 编排 |\n| `server.py` | 上传、任务状态、论文列表、evidence 和 ask API |\n| `sci_paper/` | 上传和手动放置的 PDF 论文目录 |\n\n## 入库过程\n\n1. 校验 `.pdf` 后缀、`%PDF-` 文件签名和上传大小。\n2. 计算 SHA-256,前 32 位作为稳定 `paper_id`。\n3. GROBID `/api/processFulltextDocument` 返回 TEI XML。\n4. 提取标题、作者、DOI、摘要、层级章节、正文块和可获得的页码标记。\n5. 按章节切块:目标 380 tokens、最大 480、重叠 60;不会跨章节拼接。\n6. embedding 输入包含论文标题、章节路径和原文,BGE-M3 输出并归一化为 1024 维。\n7. 同一 `paper_id` 在 Qdrant 中采用先删除后写入,避免重复 chunk。\n8. SQLite 原子更新论文/chunk 元数据,任务状态变为 `READY`。\n\nGROBID 页码来自 TEI page-break 标记,扫描版或版式复杂论文可能无法可靠映射。回答端会显示“页码待校验”,不会编造页码。\n\n## 检索过程\n\n```text\nquestion\n -> BGE-M3 query vector (1024)\n -> Qdrant cosine Top-20\n -> dense_score >= RAG_MIN_DENSE_SCORE\n -> BGE reranker\n -> evidence Top-6\n -> top1 rerank_score >= RAG_MIN_RERANK_SCORE\n -> grounded prompt with [E1]...[E6]\n```\n\n没有可靠证据时返回:\n\n```json\n{\n \"answerable\": false,\n \"reason\": \"NO_RELEVANT_EVIDENCE\",\n \"sources\": [],\n \"prompt\": null\n}\n```\n\n此分支不会调用 DeepSeek。\n\n## 启动\n\n```bash\ndocker compose up -d\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements-bge.txt\n\ncp .env.example .env\nset -a\nsource .env\nset +a\npython server.py\n```\n\nCPU 环境保持 `RAG_BGE_USE_FP16=0`。支持 CUDA 的环境可设为 `1`,但上线前必须验证模型输出与召回指标。\n\n## 手动批量入库\n\n把论文放进 `sci_paper/` 后执行:\n\n```bash\npython document_ingest.py\n```\n\n也可以指定一个或多个文件:\n\n```bash\npython document_ingest.py sci_paper/paper-a.pdf sci_paper/paper-b.pdf\n```\n\n## HTTP API\n\n上传 PDF:\n\n```bash\ncurl -F \"file=@paper.pdf\" http://127.0.0.1:8090/v1/papers\n```\n\n任务状态:\n\n```bash\ncurl http://127.0.0.1:8090/v1/ingest-jobs/\n```\n\n证据检索:\n\n```bash\ncurl http://127.0.0.1:8090/internal/v1/evidence \\\n -H 'Content-Type: application/json' \\\n -d '{\"question\":\"论文如何融合光谱和空间信息?\",\"top_k\":20,\"evidence_top_k\":6}'\n```\n\n严格问答:\n\n```bash\ncurl http://127.0.0.1:8090/v1/ask \\\n -H 'Content-Type: application/json' \\\n -d '{\"question\":\"论文如何融合光谱和空间信息?\"}'\n```\n\n`/v1/ask` 需要 `DEEPSEEK_API_KEY`;`/internal/v1/evidence` 不需要大模型 key。\n\n## 关键配置\n\n| 环境变量 | 默认值 |\n| --- | --- |\n| `GROBID_URL` | `http://127.0.0.1:8070` |\n| `QDRANT_URL` | `http://127.0.0.1:6333` |\n| `QDRANT_COLLECTION` | `sci_paper_chunks_bge_m3_v1` |\n| `RAG_EMBEDDING_MODEL` | `BAAI/bge-m3` |\n| `RAG_RERANKER_MODEL` | `BAAI/bge-reranker-v2-m3` |\n| `RAG_MIN_DENSE_SCORE` | `0.25` |\n| `RAG_MIN_RERANK_SCORE` | `0.55` |\n| `RAG_MAX_PDF_BYTES` | `104857600` |\n| `DEEPSEEK_API_KEY` | 空,必须由运行环境填写 |\n\n默认阈值只是冷启动值。应使用自己的科研问答标注集调参,优先控制错误引用和错误回答,而不是追求命中率。\n\n## 测试\n\n```bash\nmake check\nmake test\ndocker compose config --quiet\n```\n\n测试覆盖 GROBID TEI 解析、页码、结构化切块、Top-20 限制、rerank 门控、无证据不调用 LLM,以及真实本地 Qdrant 的写入和过滤检索。\n"}}}}