diff --git a/README.md b/README.md index 60f8f67..046ddf9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Con ## Install ```bash -openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.1.0 +openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.3.0 ``` ## Configure @@ -55,12 +55,16 @@ Then in chat, try: > "Liệt kê các TBMT đã indexed" -The agent should call `list_entities(entity_type="TBMT", status="indexed")` and return a markdown table. +The agent should call `list_entities(entity_type="tbmt", status="indexed")` and return a markdown table. + +> **`entity_type` is case-sensitive and lowercase** (`tbmt` / `khlcnt` / `kqlcnt`). The server stores +> lowercase types and filters by exact match, so `entity_type="TBMT"` returns zero rows. Entity codes +> follow the eGP format `IB-` (e.g. `IB2600250575-00`), not `TBMT-2026-001`. ## Update ```bash -openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.2.0 +openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.3.0 openclaw plugins restart ``` diff --git a/openclaw.plugin.json b/openclaw.plugin.json index d477757..1b708c3 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "openclaw-egp", "name": "OpenClaw EGP", "description": "RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Connects an OpenClaw agent to a running openclaw-egp-server instance via remote MCP.", - "version": "0.2.0", + "version": "0.3.0", "configSchema": { "type": "object", "required": ["serverUrl"], diff --git a/skills/describe-entity/SKILL.md b/skills/describe-entity/SKILL.md index f595ec9..bbe491d 100644 --- a/skills/describe-entity/SKILL.md +++ b/skills/describe-entity/SKILL.md @@ -7,38 +7,53 @@ description: Xem chi tiết một hồ sơ theo entity_code — metadata, danh s ## Khi nào dùng -- "Cho tôi chi tiết hồ sơ TBMT-2026-001" +- "Cho tôi chi tiết hồ sơ IB2600250575-00" - "Hồ sơ X có bao nhiêu file?" -- "Trạng thái index của KHLCNT-... ?" +- "Trạng thái index của IB...-00 ?" +- "Gói này đóng thầu khi nào, ai là chủ đầu tư?" - "File chính của hồ sơ này tên gì?" ## Workflow -1. Cần `entity_code` chính xác. Nếu user nói mơ hồ ("hồ sơ A", "cái gói thầu hôm qua"): +1. Cần `entity_code` chính xác (mã eGP dạng `IB-`, vd. `IB2600250575-00`). + Nếu user nói mơ hồ ("hồ sơ A", "cái gói thầu hôm qua"): - Hỏi lại để clarify, hoặc - Dùng `list_entities` để tìm trước rồi xác nhận với user. 2. Gọi `describe_entity(entity_code="")`. 3. Xử lý lỗi: - Tool raise `entity_not_found` → thông báo user "Không tìm thấy hồ sơ ", gợi ý dùng `list_entities` để xem danh sách. 4. Nếu OK: trả lời gồm: - - Metadata: type, status, ngày tạo / cập nhật. - - Bảng files: mỗi row có `id` (cần cho `fetch-raw-file`), `file_name`, `file_type`, `size`. + - **Metadata** (entity giờ giàu trường — surface cái nào liên quan câu hỏi): + `entity_type`, `index_status`, `title` (tên gói), `investor_name` (chủ đầu tư), + `status_code`, `public_date` (ngày đăng), `version_no` (phiên bản/sửa đổi), + `bid_open_date` / `bid_close_date` (mở/đóng thầu — giờ VN), `created_at`/`updated_at`. + - **Bảng files**: mỗi row có `id` (cần cho `fetch-raw-file`), `file_name`, `file_type`, `size`, + `convert_status`, `index_status`. ## Ví dụ -**User:** "Chi tiết hồ sơ TBMT-2026-001" +**User:** "Chi tiết hồ sơ IB2600250575-00" **Agent:** ``` -→ describe_entity(entity_code="TBMT-2026-001") -← {entity: {...}, files: [{id: 10, file_name: "main.pdf", ...}, ...]} +→ describe_entity(entity_code="IB2600250575-00") +← {entity: {entity_type: "tbmt", title: "...", investor_name: "...", + bid_close_date: "2026-06-12T09:00:00", version_no: "00", ...}, + files: [{id: 10, file_name: "main.pdf", file_type: "pdf", ...}, ...]} ``` -**Reply:** metadata + bảng files với cột `id` để user/agent có thể tham chiếu khi cần download. +**Reply:** metadata (tên gói, chủ đầu tư, đóng thầu, phiên bản) + bảng files với cột `id` để tham chiếu khi cần download. + +## Đóng/mở thầu & phiên bản + +- `bid_close_date`/`bid_open_date` là ISO **giờ VN (UTC+7, không có tz suffix)** → tính đang mở / sắp đóng / đã đóng theo giờ VN, không theo giờ máy chủ. +- `version_no` > `"00"` ⇒ hồ sơ đã có sửa đổi/gia hạn (notice được đăng lại). Khi user hỏi "có sửa đổi không", dùng trường này thay vì đoán. ## Anti-patterns - ❌ Đừng đoán entity_code — phải hỏi nếu user mơ hồ. +- ❌ Đừng bịa mã kiểu `TBMT-2026-001` — mã thật là `IB-`. - ❌ Đừng tóm tắt nội dung file dựa vào file_name — đó là việc của `search_procurement_docs`. - ❌ Đừng giấu `id` của file — agent/user cần `id` để tải file gốc qua `fetch_raw_file_url`. +- ❌ Đừng kết luận đóng/mở thầu bằng giờ máy chủ — luôn tính theo giờ VN. diff --git a/skills/list-entities/SKILL.md b/skills/list-entities/SKILL.md index 9f64abd..a2acce6 100644 --- a/skills/list-entities/SKILL.md +++ b/skills/list-entities/SKILL.md @@ -9,16 +9,24 @@ description: Liệt kê các hồ sơ (TBMT/KHLCNT/KQLCNT) có trong kho dữ li - "Có những hồ sơ nào trong hệ thống?" - "Liệt kê các TBMT đã upload" -- "Bao nhiêu KHLCNT đang ở trạng thái indexed?" +- "Bao nhiêu hồ sơ đang ở trạng thái indexed?" - "Cho tôi xem 10 record mới nhất" +- "Gói nào đang mở / sắp đóng thầu?" ## Workflow 1. Gọi `list_entities()` — không tham số nếu user hỏi chung. -2. Lọc theo `entity_type` nếu user nêu loại cụ thể (`"TBMT"` / `"KHLCNT"` / `"KQLCNT"`). -3. Lọc theo `status` nếu user nêu trạng thái (`"indexed"`, `"uploaded"`, `"failed"`). +2. Lọc theo `entity_type` nếu user nêu loại cụ thể. **Giá trị phải LOWERCASE**: + `"tbmt"` / `"khlcnt"` / `"kqlcnt"`. Filter là exact-match — gọi `"TBMT"` (hoa) trả về **0 kết quả**. + - Corpus hiện tại chủ yếu là `tbmt`; ngoài ra còn `tbmst_pre` (thông báo mời sơ tuyển / pre-notice). + `khlcnt`/`kqlcnt` được hỗ trợ nhưng có thể chưa có dữ liệu. +3. Lọc theo `status` nếu user nêu trạng thái. Giá trị `index_status` thực tế: + `"indexed"`, `"changed"`, `"indexing"`, `"converting"`, `"converted"`, `"uploaded"`, `"deleted"`. + (Không có `"failed"` ở mức entity — `failed` chỉ tồn tại ở mức chunk.) 4. Mặc định `limit=50` — đủ cho hầu hết câu hỏi. -5. Trả lời dạng bảng markdown với các cột: `Code`, `Type`, `Status`, `Updated`. +5. Mỗi row trả về `SELECT *` nên giàu metadata. Surface các cột có ý nghĩa: + `entity_code` | `entity_type` | `title` (tên gói) | `investor_name` (chủ đầu tư) | + `status_code` | `bid_close_date` (đóng thầu) | `index_status` | `updated_at`. ## Ví dụ @@ -27,14 +35,24 @@ description: Liệt kê các hồ sơ (TBMT/KHLCNT/KQLCNT) có trong kho dữ li **Agent:** ``` -→ list_entities(entity_type="TBMT", status="indexed") -← [{entity_code: "TBMT-2026-001", ...}, ...] +→ list_entities(entity_type="tbmt", status="indexed") +← [{entity_code: "IB2600250575-00", title: "...", investor_name: "...", + bid_close_date: "2026-06-12T09:00:00", index_status: "indexed", ...}, ...] ``` -**Reply:** bảng markdown gồm các record + tổng số. +**Reply:** bảng markdown — cột `Mã` | `Tên gói` | `Chủ đầu tư` | `Đóng thầu` | `Trạng thái` + tổng số. + +## Đóng/mở thầu (bid status) + +`bid_close_date` / `bid_open_date` là **ISO giờ Việt Nam (UTC+7, không có hậu tố tz)**. Khi user hỏi +"gói nào đang mở / đã đóng", tính trạng thái theo **giờ VN** (Asia/Ho_Chi_Minh), không dùng giờ máy chủ: +`đang mở` (deadline tương lai) · `sắp đóng` (~trong 48h) · `đã đóng` (đã qua) · `không xác định` (null/invalid). ## Anti-patterns +- ❌ Đừng gọi `entity_type` viết HOA — DB lưu lowercase, sẽ trả rỗng. +- ❌ Đừng bịa mã kiểu `TBMT-2026-001` — mã thật là eGP `IB-` (vd. `IB2600250575-00`). - ❌ Đừng dùng `list_entities` rồi tự duyệt trả lời nội dung — đó là việc của `search_procurement_docs`. -- ❌ Đừng dump toàn bộ raw row cho user — chỉ surface các trường có ý nghĩa: code, type, status, updated. +- ❌ Đừng dump toàn bộ raw row cho user — chỉ surface các cột có ý nghĩa ở trên. +- ❌ Đừng kết luận đóng/mở thầu bằng giờ máy chủ — luôn tính theo giờ VN. - ❌ Đừng tăng `limit` lên rất lớn (>200) — gây nhiễu cho user. Nếu cần xem nhiều hơn, page bằng cách lọc theo `entity_type`. diff --git a/skills/procurement-analysis/SKILL.md b/skills/procurement-analysis/SKILL.md index 812d6dc..c5a9411 100644 --- a/skills/procurement-analysis/SKILL.md +++ b/skills/procurement-analysis/SKILL.md @@ -48,9 +48,10 @@ Phân loại câu hỏi vào 1 trong 4 intent: Khi `intent = DISCOVERY`: 1. Extract keyword/filter từ câu hỏi: ngành, hàng hóa/dịch vụ, địa điểm, trạng thái, thời gian, khoảng giá. -2. Gọi `list_entities(entity_type?)` (filter loại nếu user chỉ định) + `search_procurement_docs(query=, limit=10..20)`. +2. Gọi `list_entities(entity_type?)` (filter loại nếu user chỉ định — **giá trị LOWERCASE** `tbmt`/`khlcnt`/`kqlcnt`, viết hoa trả rỗng) + `search_procurement_docs(query=, limit=10..20)`. + - `list_entities` trả `SELECT *` nên đã có sẵn `title`, `investor_name`, `bid_close_date`, `status_code`, `version_no` → **điền thẳng** vào bảng candidate, KHÔNG cần moi lại từ search. 3. Trả **bảng candidate** với cột: - - `rank` | `entity_type` | `entity_code` | `tên gói/thông báo/kế hoạch` | `bên mời thầu/chủ đầu tư` (nếu có) | `giá gói thầu` (nếu có) | `deadline/đóng thầu` (nếu có) | `trạng thái` (nếu có) | `highlight ngắn` (lý do match hoặc điểm đáng chú ý) | `citation/source`. + - `rank` | `entity_type` | `entity_code` | `tên gói` (`title`) | `chủ đầu tư` (`investor_name`, nếu có) | `giá gói thầu` (nếu có) | `đóng thầu` (`bid_close_date`, giờ VN) | `trạng thái` (mở/sắp đóng/đã đóng tính theo giờ VN, hoặc `status_code`) | `highlight ngắn` (lý do match) | `citation/source`. 4. **KHÔNG run full 5-part analysis** cho từng candidate. 5. Trước bảng, nếu có candidate deadline gấp / rủi ro nổi: thêm mục "**Ưu tiên xem trước**". 6. Sau bảng: @@ -81,11 +82,12 @@ Khi intent thuộc 3 nhóm này, render đủ **schema 5 phần** ở dưới. #### Part 1 — Tóm tắt gói thầu -- Mã TBMT/E-TBMT +- Mã TBMT/E-TBMT (`entity_code`) - Mã KHLCNT -- Tên gói thầu -- Chủ đầu tư / Bên mời thầu -- **Trạng thái**: đang mở / đã đóng / đã hủy / đã gia hạn. **Chỉ kết luận khi** có `bidCloseDate`, `status`, hoặc trường nguồn ghi rõ. Thiếu dữ liệu → "không xác định từ nguồn đã truy xuất". +- Tên gói thầu (`title`) +- Chủ đầu tư / Bên mời thầu (`investor_name`) +- **Trạng thái đóng/mở thầu**: ưu tiên cột **`bid_close_date`/`bid_open_date`** trên entity (`describe_entity`) — đây là ISO **giờ VN (UTC+7, không có tz suffix)**. Tính theo giờ VN: `đang mở` (deadline tương lai) / `sắp đóng` (~trong 48h) / `đã đóng` (đã qua). Có thể đối chiếu thêm `status_code`. **Chỉ kết luận khi** có `bid_close_date` hợp lệ hoặc `status_code` rõ. Thiếu cả hai → "không xác định từ nguồn đã truy xuất". KHÔNG bao giờ tính bằng giờ máy chủ. +- **Phiên bản/sửa đổi**: `version_no` > `"00"` ⇒ notice đã được đăng lại (có sửa đổi/gia hạn) — nêu rõ. - 1 câu kết luận theo intent. #### Part 2 — Thông tin KHLCNT / TBMT / E-HSMT quan trọng @@ -155,7 +157,7 @@ Khi rút gọn: header thêm hậu tố `(không phải trọng tâm câu hỏi, - **Audit TBMT**: đủ mã / tên gói / bên mời / chủ đầu tư / mốc thời gian / bảo đảm / trạng thái? - **Audit E-HSMT**: đủ Chỉ dẫn NT / Bảng dữ liệu / Tiêu chuẩn ĐG / YC kỹ thuật / Biểu mẫu / Điều kiện HĐ / Mẫu HĐ? - **Khớp giữa 3 hồ sơ** (chỉ render khi CROSS_DOC_AUDIT hoặc đã tìm được related docs): bảng compare field-by-field, ✓ / ✗ / "không kiểm tra được". -- **Sửa đổi / gia hạn / làm rõ**: có/không, phiên bản mới nhất. +- **Sửa đổi / gia hạn / làm rõ**: dùng `version_no` trên entity (`"00"` = bản gốc; `> "00"` = đã đăng lại/sửa đổi). Nêu phiên bản hiện có. Lưu ý: hệ thống có thể chỉ giữ 1 snapshot — nếu web có phiên bản mới hơn (deadline đã đổi) mà entity còn `version_no` cũ, cảnh báo "có thể có bản cập nhật chưa crawl". - **Kết luận audit**: ① Đủ để công khai / ② Cần bổ sung / ③ Rủi ro cần rà soát pháp lý — kèm lý do. #### Part 5 — Rủi ro, điểm cần làm rõ & nguồn trích dẫn @@ -187,7 +189,7 @@ Khi rút gọn: header thêm hậu tố `(không phải trọng tâm câu hỏi, 3. **"Không thấy" ≠ "không có"**: thiếu trong retrieve KHÔNG có nghĩa thực tế gói thầu không có. Không chuyển nhãn. 4. **Đa bằng chứng**: check ≥ 3-5 results, không kết luận từ top-1. Nếu < 3 hits, expand query / rephrase trước khi nói "thiếu chứng cứ". 5. **Báo hit count khi < 3**: ghi rõ số hit thực tế, đánh dấu bằng chứng chưa đủ. -6. **Status conclusion gate**: KHÔNG kết luận "đang mở/đã đóng/đã hủy" nếu thiếu mốc thời gian / `status`. +6. **Status conclusion gate**: KHÔNG kết luận "đang mở/đã đóng/đã hủy" nếu thiếu mốc thời gian (`bid_close_date`) / `status_code`. Khi có `bid_close_date`, tính trạng thái theo **giờ VN (UTC+7)**, không bao giờ theo giờ máy chủ (lệch ~15h → sai kết luận). 7. **Conclusion strength gate**: cite yếu (≤ 1 cite, hoặc cite không match claim) → bỏ kết luận mạnh ("nên tham dự" / "đủ công khai" / "rủi ro pháp lý"), fallback sang "cần kiểm tra thêm" hoặc "không đủ dữ liệu để kết luận". 8. **Retrieval-weakness reporting**: results = [] hoặc `limitations` có warning → báo rõ giới hạn + đề xuất thêm file/keyword. 9. **Industry synonym expansion**: keyword mơ hồ (vd. "y tế" → thiết bị / dược / vật tư / dịch vụ) → có thể expand, NHƯNG bắt buộc log keyword đã dùng trong reply. diff --git a/skills/search-procurement-docs/SKILL.md b/skills/search-procurement-docs/SKILL.md index b89eb6f..a73279a 100644 --- a/skills/search-procurement-docs/SKILL.md +++ b/skills/search-procurement-docs/SKILL.md @@ -10,14 +10,15 @@ description: Tìm kiếm trong kho hồ sơ đấu thầu (TBMT, KHLCNT, KQLCNT) Khi user hỏi câu cụ thể đòi nội dung từ hồ sơ — ví dụ: - "Yêu cầu kinh nghiệm trong gói thầu X là gì?" -- "Tiêu chí đánh giá năng lực tài chính trong TBMT-2026-001?" -- "Phạm vi cung cấp hàng hoá của KHLCNT-...?" +- "Tiêu chí đánh giá năng lực tài chính trong IB2600250575-00?" +- "Phạm vi cung cấp hàng hoá của gói IB...?" ## Workflow 1. Gọi tool `search_procurement_docs(query=)`. - - Nếu user nêu rõ mã hồ sơ → thêm `entity_code=""`. - - Nếu user nêu loại hồ sơ → thêm `entity_type="TBMT"` / `"KHLCNT"` / `"KQLCNT"`. + - Nếu user nêu rõ mã hồ sơ → thêm `entity_code=""` (mã eGP dạng `IB-`, vd. `IB2600250575-00`). + - Nếu user nêu loại hồ sơ → thêm `entity_type` **LOWERCASE**: `"tbmt"` / `"khlcnt"` / `"kqlcnt"`. + Viết HOA (`"TBMT"`) sẽ không khớp filter và trả về rỗng. - Mặc định `limit=5`; nâng lên 10 khi user muốn tổng hợp rộng. 2. **Đọc `limitations` trước**: - `"collection_missing"` → trả lời: "Hệ thống chưa khởi tạo collection, vui lòng admin cấu hình trước." @@ -28,16 +29,16 @@ Khi user hỏi câu cụ thể đòi nội dung từ hồ sơ — ví dụ: ## Ví dụ -**User:** "Yêu cầu hợp đồng tương tự trong gói KHLCNT-2026-001 là gì?" +**User:** "Yêu cầu hợp đồng tương tự trong gói IB2600250575-00 là gì?" **Agent:** ``` -→ search_procurement_docs(query="yêu cầu hợp đồng tương tự", entity_code="KHLCNT-2026-001") +→ search_procurement_docs(query="yêu cầu hợp đồng tương tự", entity_code="IB2600250575-00") ← {results: [...], limitations: []} ``` -**Reply:** "Theo hồ sơ KHLCNT-2026-001, yêu cầu hợp đồng tương tự gồm: [tóm tắt]. Trích nguồn: [KHLCNT-2026-001, Chương III, page 12]." +**Reply:** "Theo hồ sơ IB2600250575-00, yêu cầu hợp đồng tương tự gồm: [tóm tắt]. Trích nguồn: [IB2600250575-00, Chương III, page 12]." ## Anti-patterns