feat: 集成Hermes智能体系统,增强聊天和差旅报销功能

This commit is contained in:
caoxiaozhu
2026-05-16 06:14:08 +00:00
parent 763afa0ee2
commit 212c935308
46 changed files with 8802 additions and 5372 deletions

View File

@@ -0,0 +1,61 @@
---
name: x-financial-callback
description: Use when a Hermes task for X-Financial must report progress or completion back to the backend through the single generic callback endpoint.
---
# X-Financial Callback
Use this skill for every X-Financial task that must notify the backend after Hermes finishes work.
## Callback contract
Send exactly one HTTP `POST` request to the callback URL provided in the task payload.
Use:
- Header: `Authorization: Bearer <callback_token>`
- Header: `Content-Type: application/json`
- Body:
```json
{
"type": "task_type_from_input",
"run_id": "agent_run_id_from_input",
"status": "succeeded",
"summary": "short human-readable summary",
"payload": {}
}
```
## Rules
- Always preserve the incoming `type` and `run_id`.
- Execute the callback directly for server-dispatched tasks; do not ask the user for a second confirmation.
- Use `scripts/send_callback.py` for the actual HTTP request so large JSON bodies, quotes, and multilingual text are encoded safely.
- For normal tasks, prefer letting the script build the generic envelope for you via
`--type`, `--run-id`, `--status`, and `--summary`; then the JSON file on stdin should contain only the
task-specific business payload.
- Prefer sending a validated JSON file for non-trivial payloads:
`python3 ~/.hermes/skills/domain/x-financial-callback/scripts/send_callback.py --url "$CALLBACK_URL" --token "$CALLBACK_TOKEN" < /tmp/x-financial-callback.json`
- Before sending a large payload, validate it with `python3 -m json.tool /tmp/x-financial-callback.json >/dev/null`.
- For large multilingual payloads, write `/tmp/x-financial-callback.json` with the `write_file` tool first.
Do not generate helper Python source files or shell heredocs merely to build JSON.
- Use `status: "running"` only for optional progress updates.
- Use `status: "succeeded"` once when the task is complete.
- Use `status: "failed"` once when the task cannot be completed, and include an `error` string.
- Put task-specific business data only inside `payload`.
- Do not invent extra callback endpoints. X-Financial accepts Hermes callbacks through one shared endpoint only.
- If the callback fails, retry the same request up to 3 times before returning failure.
## Preferred command
```bash
python3 ~/.hermes/skills/domain/x-financial-callback/scripts/send_callback.py \
--url "$CALLBACK_URL" \
--token "$CALLBACK_TOKEN" \
--type "$TASK_TYPE" \
--run-id "$RUN_ID" \
--status succeeded \
--summary "short human-readable summary" \
< /tmp/x-financial-callback.json
```

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Send one generic X-Financial Hermes callback.")
parser.add_argument("--url", required=True)
parser.add_argument("--token", required=True)
parser.add_argument("--retries", type=int, default=3)
parser.add_argument("--type", dest="task_type", default="")
parser.add_argument("--run-id", default="")
parser.add_argument("--status", choices=("running", "succeeded", "failed"), default="")
parser.add_argument("--summary", default="")
parser.add_argument("--error", default="")
return parser.parse_args()
def main() -> int:
args = parse_args()
payload = json.load(sys.stdin)
if args.task_type and args.run_id and args.status:
payload = {
"type": args.task_type,
"run_id": args.run_id,
"status": args.status,
"summary": args.summary,
"payload": payload,
**({"error": args.error} if args.error else {}),
}
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
last_error = ""
for attempt in range(1, max(1, args.retries) + 1):
request = Request(
args.url,
data=body,
headers={
"Authorization": f"Bearer {args.token}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urlopen(request, timeout=30) as response:
response_body = response.read().decode("utf-8")
if 200 <= response.status < 300:
print(response_body)
return 0
last_error = f"HTTP {response.status}: {response_body}"
except HTTPError as exc:
last_error = f"HTTP {exc.code}: {exc.read().decode('utf-8', errors='replace')}"
except URLError as exc:
last_error = str(exc.reason)
if attempt < max(1, args.retries):
time.sleep(min(attempt, 3))
print(last_error or "callback failed", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,134 @@
---
name: x-financial-llm-wiki-ingest
description: "Use for X-Financial制度文档归纳任务。Read the full source documents provided by the service, use the llm-wiki workflow for synthesis, and actively callback the X-Financial backend with one structured batch result."
---
# X-Financial LLM Wiki Ingest
Use this skill together with the built-in `llm-wiki` skill for X-Financial制度文档归纳任务。
## Workflow
1. Treat each provided `absolute_path` as the authoritative whole source document.
2. Read the original files directly. Do **not** ask the caller to pre-split them into chunks.
3. If a source is too large for one `read_file` call, do **not** retry the same full read. Continue with
line-ranged reads using `offset` and `limit` until the whole source has been covered. This is still
whole-document processing; it is only an internal reading strategy for the Hermes tool safety limit.
4. Use the built-in `llm-wiki` workflow to synthesize the documents as one batch.
5. Use the `x-financial-callback` skill to POST the completed result back to the callback URL from the task payload.
6. After the callback succeeds, return only a short acknowledgement to the caller.
## Large-file handling
- A `read_file` response such as “exceeds the safety limit” means the document is large, not unreadable.
- Re-read it in bounded windows, for example 300-500 lines per call, until every line range has been examined.
- Keep one running synthesis for the whole document; do not emit one callback per window and do not treat each
window as an independent document.
- For monetary tables and approval matrices, make sure you read enough surrounding content to understand the decision
dimensions before summarizing them. Do not infer missing rows from one partial visible fragment.
- If a PDF table is noisy or flattened, prioritize producing a stable wiki description of:
`who/what it applies to`, `which dimensions affect the answer`, `what values exist`, and `which exceptions apply`.
- If a table contains both domestic and overseas columns, reflect those dimensions clearly in the wiki summary.
The server may reconstruct the final display table from your wiki wording, so the wording must keep the axes clear.
## Callback Payload Contract
Send this object inside the generic callback body's `payload` field:
```json
{
"ok": true,
"summary": "本次批量归纳的简要结果",
"documents": [
{
"document_id": "原样返回输入中的 document_id",
"knowledge_summary_markdown": "# 知识总结\n\n...",
"knowledge_candidates": [
{
"title": "知识点标题",
"content": "可直接作为 wiki 页面片段被问答系统引用的制度知识",
"scenario": "reimbursement_policy",
"tags": ["报销", "审批"],
"evidence": ["来自原文的短证据"],
"confidence": 0.0,
"source_chunk_ids": []
}
],
"rule_candidates": [
{
"template_key": "general_policy_v1",
"suggested_rule_name": "规则草稿名称",
"summary": "规则草稿摘要",
"scenario": "reimbursement_policy",
"purpose": "规则目标",
"scope": "适用范围",
"inputs": ["输入字段"],
"judgement_logic": ["判断逻辑"],
"outputs": ["输出动作"],
"admin_note": "管理员审核备注",
"runtime_rule": {},
"evidence": ["来自原文的短证据"],
"confidence": 0.0,
"source_chunk_ids": []
}
]
}
]
}
```
## Rules
- Preserve every input `document_id` in the callback payload.
- The downstream knowledge assistant is allowed to answer only from the compiled wiki output. Treat every
`knowledge_candidate.content` as a reusable wiki section, not as a loose abstract.
- Prefer fewer, self-contained, reviewable wiki sections over many weak summaries.
- Each wiki section must preserve enough context for a later reader to answer questions without reopening the
raw source document: who it applies to, when it applies, what the rule is, exceptions, thresholds, and required
conditions when those facts exist in the source.
- Preserve the original decision dimensions from the source document. If a policy depends on both `职级` and `地区`,
or any other multi-axis table, keep all axes in `content` instead of collapsing them into a single generic summary.
- If a document contains a travel policy, the wiki output must cover the three core dimensions separately when the
source contains them: `交通费标准``住宿费标准``出差补贴标准`. Do not return only one or two of them.
- If the source contains a table whose rows/columns affect the answer, prefer a Markdown table when you can produce one
confidently. If the PDF extraction is noisy, a structured wiki description is acceptable, but it must keep the
answer dimensions explicit enough for the server to reconstruct a table later.
- Table-backed sections do not need OCR-grade cell preservation. What matters is that the wiki wording keeps:
- the decision axes explicit;
- the row groups explicit;
- the applicable values explicit;
- the exceptions and approval rules explicit.
- For monetary standards, do **not** use slash shorthand such as `700/450/400`, `600/400/350`, or similar compressed
sequences. Write the explicit row/column table so every amount remains bound to its original dimension.
- Do **not** paraphrase a multi-axis source table into prose if that would force the downstream QA model to guess
which number belongs to which row or column.
- Do not replace precise source distinctions with a generic "highest" or "default" amount if the source provides
multiple applicable rows.
- If the source does not contain enough information to answer a likely question safely, preserve that limitation in
`content` instead of silently filling the gap.
- Only emit rules that are supported by the source files.
- `template_key` must be one of:
- `travel_standard_v1`
- `expense_amount_limit_v1`
- `attachment_requirement_v1`
- `general_policy_v1`
- If a document has no reliable rule candidate, return an empty `rule_candidates` list.
- Keep `evidence` short and directly grounded in the original source.
- Never invent missing numeric thresholds or unsupported制度要求.
- If the batch cannot be processed, callback with `status: "failed"` and an `error` string instead of partial prose.
## Safe callback construction
- Do **not** hand-write a Python source file that embeds long Chinese prose inside quoted string literals.
- Do **not** use `execute_code`, inline Python, or shell heredocs to assemble the callback payload.
- Use the `write_file` tool to write the finished payload directly as plain UTF-8 JSON to
`/tmp/x-financial-callback.json`.
- Build the callback as plain JSON, save it to a UTF-8 `.json` file, validate it with
`python3 -m json.tool <payload.json>`, then send that file through the callback skill.
- When prose contains Chinese quotation marks, tables, or long paragraphs, keep them as JSON string values in the
JSON file itself; do not interpolate them into shell commands or Python source code.
- Prefer one validated payload file for the whole batch over piecemeal shell heredocs.
- After `python3 -m json.tool` passes, do one final content review of all table-backed candidates before sending.
- If the callback endpoint returns HTTP 400, read the response body, repair **only** the rejected candidates, validate
the JSON again, and resend. Do not treat the first 400 response as terminal failure when the server has provided
actionable correction feedback.