cli.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #!/usr/bin/env python3
  2. """
  3. KnowHub CLI - 知识管理命令行工具
  4. 使用方法:
  5. python -m knowhub.skill.cli search "查询内容"
  6. python -m knowhub.skill.cli save --task "任务" --content "内容" --types strategy
  7. python -m knowhub.skill.cli list --limit 10
  8. """
  9. import os
  10. import sys
  11. import json
  12. import argparse
  13. from pathlib import Path
  14. try:
  15. import httpx
  16. except ImportError:
  17. print("错误: 需要安装 httpx 库")
  18. print("运行: pip install httpx")
  19. sys.exit(1)
  20. # KnowHub API 默认地址(CLI 需要在开头声明,方便用户查看和修改)
  21. DEFAULT_API_URL = "http://43.106.118.91:9999"
  22. def get_api_base() -> str:
  23. """获取 API 地址"""
  24. return os.getenv("KNOWHUB_API", DEFAULT_API_URL)
  25. def search_knowledge(args):
  26. """搜索知识"""
  27. url = f"{get_api_base()}/api/knowledge/search"
  28. params = {
  29. "q": args.query,
  30. "top_k": args.top_k,
  31. "min_score": args.min_score,
  32. }
  33. if args.types:
  34. params["types"] = args.types
  35. try:
  36. response = httpx.get(url, params=params, timeout=30.0)
  37. response.raise_for_status()
  38. data = response.json()
  39. if data["count"] == 0:
  40. print("未找到相关知识")
  41. return
  42. print(f"找到 {data['count']} 条知识:\n")
  43. for i, item in enumerate(data["results"], 1):
  44. print(f"[{i}] {item['task']}")
  45. print(f" ID: {item['id']}")
  46. eval_data = item.get("eval", {})
  47. print(f" 评分: {eval_data.get('score', 3)} | 质量分: {item.get('quality_score', 'N/A')}")
  48. print(f" 类型: {', '.join(item.get('types', []))}")
  49. print(f" 内容: {item['content'][:100]}...")
  50. print()
  51. except httpx.HTTPError as e:
  52. print(f"请求失败: {e}")
  53. sys.exit(1)
  54. def save_knowledge(args):
  55. """保存知识"""
  56. url = f"{get_api_base()}/api/knowledge"
  57. data = {
  58. "message_id": args.message_id or f"cli-{os.getpid()}",
  59. "types": args.types.split(",") if args.types else ["strategy"],
  60. "task": args.task,
  61. "tags": json.loads(args.tags) if args.tags else {},
  62. "scopes": args.scopes.split(",") if args.scopes else ["org:cybertogether"],
  63. "owner": args.owner or "agent:cli",
  64. "content": args.content,
  65. "source": {
  66. "name": args.source_name or "cli",
  67. "category": args.source_category or "exp",
  68. "urls": args.urls.split(",") if args.urls else [],
  69. "agent_id": args.agent_id or "cli",
  70. "submitted_by": args.submitted_by or "cli-user",
  71. },
  72. "eval": {
  73. "score": args.score,
  74. "helpful": 1,
  75. "harmful": 0,
  76. "confidence": 0.5,
  77. }
  78. }
  79. try:
  80. response = httpx.post(url, json=data, timeout=30.0)
  81. response.raise_for_status()
  82. result = response.json()
  83. print(f"✅ 知识已保存: {result['knowledge_id']}")
  84. except httpx.HTTPError as e:
  85. print(f"保存失败: {e}")
  86. sys.exit(1)
  87. def update_knowledge(args):
  88. """更新知识"""
  89. url = f"{get_api_base()}/api/knowledge/{args.id}"
  90. data = {}
  91. if args.score:
  92. data["update_score"] = args.score
  93. if args.helpful_case:
  94. data["add_helpful_case"] = args.helpful_case
  95. if args.harmful_case:
  96. data["add_harmful_case"] = args.harmful_case
  97. if args.evolve_feedback:
  98. data["evolve_feedback"] = args.evolve_feedback
  99. if not data:
  100. print("错误: 至少需要提供一个更新参数")
  101. sys.exit(1)
  102. try:
  103. response = httpx.put(url, json=data, timeout=30.0)
  104. response.raise_for_status()
  105. print(f"✅ 知识已更新: {args.id}")
  106. except httpx.HTTPError as e:
  107. print(f"更新失败: {e}")
  108. sys.exit(1)
  109. def batch_update_knowledge(args):
  110. """批量更新知识"""
  111. url = f"{get_api_base()}/api/knowledge/batch_update"
  112. # 从文件读取反馈列表
  113. if args.file:
  114. with open(args.file, 'r') as f:
  115. feedback_list = json.load(f)
  116. else:
  117. print("错误: 需要提供 --file 参数")
  118. sys.exit(1)
  119. data = {"feedback_list": feedback_list}
  120. try:
  121. response = httpx.post(url, json=data, timeout=60.0)
  122. response.raise_for_status()
  123. result = response.json()
  124. print(f"✅ 批量更新完成: {result['updated']} 条知识")
  125. except httpx.HTTPError as e:
  126. print(f"批量更新失败: {e}")
  127. sys.exit(1)
  128. def list_knowledge(args):
  129. """列出知识"""
  130. url = f"{get_api_base()}/api/knowledge"
  131. params = {"limit": args.limit}
  132. if args.types:
  133. params["types"] = args.types
  134. if args.scopes:
  135. params["scopes"] = args.scopes
  136. try:
  137. response = httpx.get(url, params=params, timeout=30.0)
  138. response.raise_for_status()
  139. data = response.json()
  140. if data["count"] == 0:
  141. print("知识库为空")
  142. return
  143. print(f"共 {data['count']} 条知识:\n")
  144. for i, item in enumerate(data["results"], 1):
  145. print(f"[{i}] {item['task']}")
  146. print(f" ID: {item['id']}")
  147. eval_data = item.get("eval", {})
  148. print(f" 评分: {eval_data.get('score', 3)} | Helpful: {eval_data.get('helpful', 0)} | Harmful: {eval_data.get('harmful', 0)}")
  149. print(f" 类型: {', '.join(item.get('types', []))}")
  150. print(f" 所有者: {item.get('owner', 'N/A')}")
  151. print()
  152. except httpx.HTTPError as e:
  153. print(f"请求失败: {e}")
  154. sys.exit(1)
  155. def slim_knowledge(args):
  156. """知识瘦身"""
  157. url = f"{get_api_base()}/api/knowledge/slim"
  158. params = {"model": args.model}
  159. try:
  160. print("正在执行知识瘦身,这可能需要一些时间...")
  161. response = httpx.post(url, params=params, timeout=120.0)
  162. response.raise_for_status()
  163. result = response.json()
  164. print(f"✅ 瘦身完成: {result['before']} → {result['after']} 条知识")
  165. if result.get("report"):
  166. print(f" {result['report']}")
  167. except httpx.HTTPError as e:
  168. print(f"瘦身失败: {e}")
  169. sys.exit(1)
  170. def main():
  171. parser = argparse.ArgumentParser(description="KnowHub CLI - 知识管理工具")
  172. subparsers = parser.add_subparsers(dest="command", help="可用命令")
  173. # search 命令
  174. search_parser = subparsers.add_parser("search", help="搜索知识")
  175. search_parser.add_argument("query", help="查询文本")
  176. search_parser.add_argument("--top-k", type=int, default=5, help="返回结果数量")
  177. search_parser.add_argument("--min-score", type=int, default=3, help="最低评分")
  178. search_parser.add_argument("--types", help="类型过滤(逗号分隔)")
  179. # save 命令
  180. save_parser = subparsers.add_parser("save", help="保存知识")
  181. save_parser.add_argument("--task", required=True, help="任务描述")
  182. save_parser.add_argument("--content", required=True, help="知识内容")
  183. save_parser.add_argument("--types", default="strategy", help="类型(逗号分隔)")
  184. save_parser.add_argument("--tags", help="标签(JSON 格式)")
  185. save_parser.add_argument("--scopes", help="可见范围(逗号分隔)")
  186. save_parser.add_argument("--owner", help="所有者")
  187. save_parser.add_argument("--source-name", help="来源名称")
  188. save_parser.add_argument("--source-category", help="来源类别")
  189. save_parser.add_argument("--urls", help="相关 URL(逗号分隔)")
  190. save_parser.add_argument("--agent-id", help="Agent ID")
  191. save_parser.add_argument("--submitted-by", help="提交者")
  192. save_parser.add_argument("--message-id", help="消息 ID")
  193. save_parser.add_argument("--score", type=int, default=3, help="评分 (1-5)")
  194. # update 命令
  195. update_parser = subparsers.add_parser("update", help="更新知识")
  196. update_parser.add_argument("id", help="知识 ID")
  197. update_parser.add_argument("--score", type=int, help="更新评分")
  198. update_parser.add_argument("--helpful-case", help="添加有效案例")
  199. update_parser.add_argument("--harmful-case", help="添加有害案例")
  200. update_parser.add_argument("--evolve-feedback", help="知识进化反馈")
  201. # batch-update 命令
  202. batch_parser = subparsers.add_parser("batch-update", help="批量更新知识")
  203. batch_parser.add_argument("--file", required=True, help="反馈列表 JSON 文件")
  204. # list 命令
  205. list_parser = subparsers.add_parser("list", help="列出知识")
  206. list_parser.add_argument("--limit", type=int, default=10, help="返回数量")
  207. list_parser.add_argument("--types", help="类型过滤")
  208. list_parser.add_argument("--scopes", help="范围过滤")
  209. # slim 命令
  210. slim_parser = subparsers.add_parser("slim", help="知识瘦身")
  211. slim_parser.add_argument("--model", default="google/gemini-2.0-flash-001", help="使用的模型")
  212. args = parser.parse_args()
  213. if not args.command:
  214. parser.print_help()
  215. sys.exit(1)
  216. # 执行命令
  217. if args.command == "search":
  218. search_knowledge(args)
  219. elif args.command == "save":
  220. save_knowledge(args)
  221. elif args.command == "update":
  222. update_knowledge(args)
  223. elif args.command == "batch-update":
  224. batch_update_knowledge(args)
  225. elif args.command == "list":
  226. list_knowledge(args)
  227. elif args.command == "slim":
  228. slim_knowledge(args)
  229. if __name__ == "__main__":
  230. main()