dingo-python 2.1.1__py3-none-any.whl → 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. dingo/config/input_args.py +0 -1
  2. dingo/exec/local.py +0 -26
  3. dingo/model/llm/agent/agent_article_fact_checker.py +22 -12
  4. dingo/model/llm/agent/tools/arxiv_search.py +73 -5
  5. dingo/run/cli.py +256 -16
  6. dingo/utils/exception.py +4 -6
  7. {dingo_python-2.1.1.dist-info → dingo_python-2.2.0.dist-info}/METADATA +76 -91
  8. {dingo_python-2.1.1.dist-info → dingo_python-2.2.0.dist-info}/RECORD +12 -92
  9. dingo_python-2.2.0.dist-info/entry_points.txt +2 -0
  10. app/.editorconfig +0 -9
  11. app/.eslintignore +0 -4
  12. app/.eslintrc.cjs +0 -13
  13. app/.gitignore +0 -5
  14. app/.npmrc +0 -2
  15. app/.prettierignore +0 -6
  16. app/.prettierrc.json +0 -9
  17. app/.prettierrc.yaml +0 -4
  18. app/README.md +0 -100
  19. app/README_ZH.md +0 -104
  20. app/app-static.py +0 -114
  21. app/app.py +0 -22
  22. app/dev-app-update.yml +0 -3
  23. app/electron-builder.yml +0 -45
  24. app/electron.vite.config.ts +0 -135
  25. app/package-lock.json +0 -11249
  26. app/package.json +0 -86
  27. app/pnpm-lock.yaml +0 -7504
  28. app/postcss.config.js +0 -8
  29. app/resources/icon.png +0 -0
  30. app/resources/logo.svg +0 -39
  31. app/src/main/index.ts +0 -343
  32. app/src/preload/index.d.ts +0 -21
  33. app/src/preload/index.ts +0 -51
  34. app/src/renderer/index.html +0 -17
  35. app/src/renderer/src/App.tsx +0 -49
  36. app/src/renderer/src/assets/base.css +0 -67
  37. app/src/renderer/src/assets/electron.svg +0 -20
  38. app/src/renderer/src/assets/iconfont.js +0 -1
  39. app/src/renderer/src/assets/main.css +0 -180
  40. app/src/renderer/src/assets/svg/empty.svg +0 -16
  41. app/src/renderer/src/assets/wavy-lines.svg +0 -25
  42. app/src/renderer/src/components/HightLightText/index.module.scss +0 -0
  43. app/src/renderer/src/components/HightLightText/index.tsx +0 -135
  44. app/src/renderer/src/components/Versions.tsx +0 -15
  45. app/src/renderer/src/components/detail-card/index.tsx +0 -126
  46. app/src/renderer/src/components/detail-table.tsx +0 -257
  47. app/src/renderer/src/components/ellipsis-text.tsx +0 -119
  48. app/src/renderer/src/components/empty.tsx +0 -31
  49. app/src/renderer/src/components/file-structure-table.tsx +0 -271
  50. app/src/renderer/src/components/filter-cascader/index.module.scss +0 -37
  51. app/src/renderer/src/components/filter-cascader/index.tsx +0 -93
  52. app/src/renderer/src/components/icon-font.tsx +0 -7
  53. app/src/renderer/src/components/readFileDir.tsx +0 -228
  54. app/src/renderer/src/components/text-tooltip/index.module.scss +0 -12
  55. app/src/renderer/src/components/text-tooltip/index.tsx +0 -103
  56. app/src/renderer/src/constant/Language.ts +0 -4
  57. app/src/renderer/src/constant/index.ts +0 -10
  58. app/src/renderer/src/constant/storage.ts +0 -1
  59. app/src/renderer/src/env.d.ts +0 -1
  60. app/src/renderer/src/locale/en.ts +0 -12
  61. app/src/renderer/src/locale/zh.ts +0 -15
  62. app/src/renderer/src/main.tsx +0 -11
  63. app/src/renderer/src/pages/index.module.scss +0 -28
  64. app/src/renderer/src/pages/index.tsx +0 -0
  65. app/src/renderer/src/pages/main-home/components/pieChart.tsx +0 -404
  66. app/src/renderer/src/pages/main-home/components/summary-data-table.tsx +0 -94
  67. app/src/renderer/src/pages/main-home/index.module.scss +0 -5
  68. app/src/renderer/src/pages/main-home/index.tsx +0 -17
  69. app/src/renderer/src/pages/sideBar.tsx +0 -226
  70. app/src/renderer/src/store/config.ts +0 -55
  71. app/src/renderer/src/store/dal.ts +0 -331
  72. app/src/renderer/src/store/language.tsx +0 -76
  73. app/src/renderer/src/styles/custom-antd.module.scss +0 -46
  74. app/src/renderer/src/typing.ts +0 -12
  75. app/src/renderer/src/utils/clone.ts +0 -12
  76. app/src/renderer/src/utils/env.ts +0 -3
  77. app/src/renderer/src/utils/index.ts +0 -32
  78. app/src/renderer/src/utils/indexedDB-storage.ts +0 -38
  79. app/src/renderer/src/utils/store.ts +0 -66
  80. app/tailwind.config.js +0 -73
  81. app/test.py +0 -55
  82. app/tsconfig.json +0 -4
  83. app/tsconfig.node.json +0 -8
  84. app/tsconfig.web.json +0 -23
  85. dingo/run/vsl.py +0 -432
  86. web-static/assets/main-BJ2wBIkh.js +0 -136425
  87. web-static/assets/main-ByTNTbJP.css +0 -1532
  88. web-static/directory-browser.html +0 -206
  89. web-static/index.html +0 -18
  90. web-static/src/assets/iconfont.js +0 -1
  91. {dingo_python-2.1.1.dist-info → dingo_python-2.2.0.dist-info}/WHEEL +0 -0
  92. {dingo_python-2.1.1.dist-info → dingo_python-2.2.0.dist-info}/licenses/LICENSE +0 -0
  93. {dingo_python-2.1.1.dist-info → dingo_python-2.2.0.dist-info}/top_level.txt +0 -0
@@ -132,7 +132,6 @@ class InputArgs(BaseModel):
132
132
  output_path: str = "outputs/"
133
133
 
134
134
  log_level: str = "WARNING"
135
- use_browser: bool = False
136
135
 
137
136
  dataset: DatasetArgs = DatasetArgs()
138
137
  executor: ExecutorArgs = ExecutorArgs()
dingo/exec/local.py CHANGED
@@ -3,8 +3,6 @@ import copy
3
3
  import itertools
4
4
  import json
5
5
  import os
6
- import subprocess
7
- import sys
8
6
  import time
9
7
  import uuid
10
8
  from typing import Generator, List, Optional
@@ -154,30 +152,6 @@ class LocalExecutor(ExecProto):
154
152
  self.summary = self.summarize(self.summary)
155
153
  self.write_summary(self.summary.output_path, self.input_args, self.summary)
156
154
 
157
- # Open browser if use_browser is True
158
- if self.input_args.use_browser:
159
- try:
160
- # 使用 sys.executable 获取当前 Python 解释器路径
161
- # 将命令作为列表传递,避免 shell 注入风险
162
- cmd = [sys.executable, "-m", "dingo.run.vsl", "--input", self.summary.output_path]
163
- log.warning(f"Opening browser with command: {' '.join(cmd)}")
164
-
165
- # 使用 subprocess.Popen 在后台启动服务器
166
- # start_new_session=True 让子进程独立运行,不受父进程退出影响
167
- # stdout/stderr=DEVNULL 避免管道缓冲区死锁问题
168
- subprocess.Popen(
169
- cmd,
170
- stdout=subprocess.DEVNULL,
171
- stderr=subprocess.DEVNULL,
172
- start_new_session=True
173
- )
174
-
175
- # 给服务器一点时间启动
176
- time.sleep(1)
177
- log.warning("Browser server started in background")
178
- except Exception as e:
179
- log.warning(f"Failed to open browser: {e}")
180
-
181
155
  return self.summary
182
156
 
183
157
  def evaluate_single_data(self, dingo_id: str, eval_fields: dict, eval_type: str, map_data: dict, eval_list: list) -> ResultInfo:
@@ -55,13 +55,16 @@ Available Tools:
55
55
 
56
56
  2. arxiv_search: Search academic papers and verify metadata
57
57
  - Use for claims about research papers, academic publications
58
- - Provides paper metadata: title, authors, abstract, publication date
59
- - Authors in papers often indicate institutional affiliations in abstracts
60
- - NOTE: Affiliations are in unstructured text, not dedicated fields
61
- - Best for: paper titles, author names, publication dates, and
62
- institutional claims when a related paper exists
63
- - For institutional claims: use arxiv_search FIRST to find the paper,
64
- then tavily_search to cross-verify affiliations
58
+ - Provides paper metadata: title, authors list, abstract, publication date
59
+ - Results may include 'affiliations_text': a compact author+institution string
60
+ parsed from the HTML paper page (e.g. "1 Shanghai AI Laboratory 2 Abaka AI").
61
+ When present, use it as the AUTHORITATIVE source for institutional claims —
62
+ it lists every author's institution, making affiliation verification direct.
63
+ - Best for: paper titles, author names, publication dates, institutional claims
64
+ - For institutional/attribution claims:
65
+ 1. Call arxiv_search to find the paper
66
+ 2. Check 'affiliations_text' in the result — if present, verify directly
67
+ 3. If 'affiliations_text' is absent, fall back to tavily_search
65
68
 
66
69
  3. tavily_search: General web search for fact verification
67
70
  - Use for general factual claims, current events, companies, products
@@ -95,11 +98,12 @@ STEP 2: Verify Each Claim (Autonomous Tool Selection)
95
98
 
96
99
  Claim-Type Specific Rules:
97
100
  - INSTITUTIONAL/ATTRIBUTION claims (e.g., "released by X University and Y Lab"):
98
- You MUST use arxiv_search FIRST to find the actual paper and check author
99
- affiliations, THEN use tavily_search to cross-verify. Do NOT rely on
100
- tavily_search alone for institutional claims web sources often give
101
- vague or incomplete attribution. The paper's author list is the
102
- authoritative source for institutional affiliations.
101
+ You MUST use arxiv_search FIRST to find the actual paper.
102
+ Check the 'affiliations_text' field in the result: when present it lists every
103
+ author's institution (e.g. "1 Shanghai AI Laboratory 2 Abaka AI") and is the
104
+ AUTHORITATIVE source use it directly to confirm or refute the claim.
105
+ If 'affiliations_text' is absent, fall back to tavily_search to cross-verify.
106
+ Do NOT rely on tavily_search alone — web sources often give vague attribution.
103
107
  For CHINESE institution names: translate to English before arxiv_search
104
108
  (e.g., "清华大学" → "Tsinghua University", "达摩院" → "Alibaba DAMO Academy",
105
109
  "上海人工智能实验室" → "Shanghai AI Laboratory")
@@ -278,6 +282,12 @@ Verdict Rules:
278
282
  - FALSE: Found specific evidence CONTRADICTING the claim
279
283
  - UNVERIFIABLE: Could not find clear confirming OR contradicting evidence
280
284
 
285
+ For INSTITUTIONAL/ATTRIBUTION claims (e.g. "paper released by X University"):
286
+ Use arxiv_search first. If the result contains an 'affiliations_text' field,
287
+ it lists every author's institution (e.g. "1 Shanghai AI Lab 2 MIT") and is
288
+ the authoritative source — use it to confirm or refute the claim directly.
289
+ Fall back to tavily_search only when 'affiliations_text' is absent.
290
+
281
291
  CRITICAL: Start with search, then produce JSON only. No text outside the JSON."""
282
292
 
283
293
  @classmethod
@@ -15,6 +15,9 @@ Configuration:
15
15
  rate_limit_delay: Delay between requests in seconds (default: 3.0)
16
16
  timeout: Request timeout in seconds (default: 30)
17
17
  api_key: Not required for arXiv (public API)
18
+ fetch_affiliations: Fetch author+institution text from arXiv HTML paper page (default: False).
19
+ When enabled, adds an ``affiliations_text`` field to each result by scraping
20
+ the ``ltx_authors`` section. Silently skipped for papers without HTML version.
18
21
  """
19
22
 
20
23
  import re
@@ -22,6 +25,7 @@ import threading
22
25
  import time
23
26
  from typing import Any, Dict, List, Optional
24
27
 
28
+ import requests as _requests
25
29
  from pydantic import Field
26
30
 
27
31
  from dingo.io.input import RequiredField
@@ -38,6 +42,14 @@ class ArxivConfig(ToolConfig):
38
42
  sort_order: str = Field(default="descending", pattern="^(ascending|descending)$")
39
43
  rate_limit_delay: float = Field(default=3.0, ge=0.0)
40
44
  timeout: int = Field(default=30, ge=1)
45
+ fetch_affiliations: bool = Field(
46
+ default=False,
47
+ description=(
48
+ "Fetch author+institution text from arXiv HTML page for each result. "
49
+ "Adds 'affiliations_text' field. Requires one extra HTTP request per result. "
50
+ "Enable for institutional/attribution claim verification."
51
+ ),
52
+ )
41
53
 
42
54
 
43
55
  @tool_register
@@ -99,9 +111,14 @@ class ArxivSearch(BaseTool):
99
111
  name = "arxiv_search"
100
112
  description = (
101
113
  "Search arXiv for academic papers by ID, DOI, title, or author. "
102
- "Returns comprehensive paper metadata including title, authors, abstract, "
103
- "publication date, PDF URL, and citations. Useful for verifying academic "
104
- "claims, finding research papers, and checking paper details."
114
+ "Returns paper metadata: title, authors list, abstract, publication date, "
115
+ "PDF URL, and categories. "
116
+ "When fetch_affiliations is enabled, results also include 'affiliations_text': "
117
+ "a compact author+institution string parsed from the HTML paper page "
118
+ "(e.g. '1 Shanghai AI Laboratory 2 Abaka AI 3 2077AI'), which is the "
119
+ "authoritative source for institutional attribution claims. "
120
+ "Use this tool for verifying claims about academic papers, author names, "
121
+ "publication dates, and institutional/organizational affiliations."
105
122
  )
106
123
  config: ArxivConfig = ArxivConfig()
107
124
 
@@ -216,9 +233,23 @@ class ArxivSearch(BaseTool):
216
233
 
217
234
  # Execute search and collect results
218
235
  results = []
236
+ entry_ids = []
219
237
  client = arxiv.Client()
220
238
  for paper in client.results(search):
221
239
  results.append(cls._format_paper(paper))
240
+ entry_ids.append(paper.entry_id)
241
+
242
+ # Optionally enrich results with author+institution text from HTML pages.
243
+ # The arXiv Atom API rarely includes affiliation data; the HTML page is
244
+ # the only reliable machine-readable source.
245
+ if cls.config.fetch_affiliations and results:
246
+ for i, entry_id in enumerate(entry_ids):
247
+ html_url = entry_id.replace('/abs/', '/html/')
248
+ affiliations_text = cls._fetch_html_affiliations(
249
+ html_url, timeout=cls.config.timeout
250
+ )
251
+ if affiliations_text is not None:
252
+ results[i]['affiliations_text'] = affiliations_text
222
253
 
223
254
  # Format response
224
255
  result = {
@@ -241,8 +272,8 @@ class ArxivSearch(BaseTool):
241
272
  error_msg = "Search request timed out"
242
273
  elif "network" in error_str or "connection" in error_str:
243
274
  error_msg = "Network connection error"
244
- elif "rate limit" in error_str:
245
- error_msg = "Rate limit exceeded"
275
+ elif "rate limit" in error_str or "429" in error_str:
276
+ error_msg = "Rate limit exceeded (HTTP 429) — arXiv API throttled this request"
246
277
  else:
247
278
  error_msg = f"Search failed: {type(e).__name__}"
248
279
 
@@ -458,6 +489,43 @@ class ArxivSearch(BaseTool):
458
489
  'dois': dois
459
490
  }
460
491
 
492
+ @classmethod
493
+ def _fetch_html_affiliations(cls, html_url: str, timeout: int = 15) -> Optional[str]:
494
+ """
495
+ Fetch author+institution text from an arXiv HTML paper page.
496
+
497
+ Extracts the ``ltx_authors`` div from the LaTeXML-rendered HTML page,
498
+ which contains author names with footnoted institution identifiers
499
+ (e.g. "1 Shanghai AI Laboratory 2 Abaka AI").
500
+
501
+ Args:
502
+ html_url: Full URL, e.g. ``https://arxiv.org/html/2412.07626v2``.
503
+ timeout: HTTP request timeout in seconds.
504
+
505
+ Returns:
506
+ Cleaned plain-text of the ``ltx_authors`` section, or ``None`` if
507
+ unavailable (no HTML version, network error, or missing element).
508
+ """
509
+ try:
510
+ resp = _requests.get(
511
+ html_url,
512
+ headers={"user-agent": "dingo-arxiv-tool/1.0"},
513
+ timeout=timeout,
514
+ )
515
+ if resp.status_code != 200:
516
+ log.debug("arXiv HTML page unavailable (%d): %s", resp.status_code, html_url)
517
+ return None
518
+ match = re.search(r'class="ltx_authors"[^>]*>(.*?)</div>', resp.text, re.DOTALL)
519
+ if not match:
520
+ log.debug("ltx_authors section not found in HTML page: %s", html_url)
521
+ return None
522
+ text = re.sub(r'<[^>]+>', ' ', match.group(1))
523
+ text = re.sub(r'\s+', ' ', text).strip()
524
+ return text if text else None
525
+ except Exception as exc:
526
+ log.debug("Failed to fetch HTML affiliations from %s: %s", html_url, exc)
527
+ return None
528
+
461
529
  @classmethod
462
530
  def validate_config(cls):
463
531
  """
dingo/run/cli.py CHANGED
@@ -1,41 +1,281 @@
1
1
  import argparse
2
2
  import json
3
- import os
3
+ import sys
4
4
 
5
5
  import prettytable as pt
6
6
 
7
7
  from dingo.config import InputArgs
8
8
  from dingo.exec import Executor
9
9
  from dingo.model import Model
10
- from dingo.utils import log
10
+
11
+ EXIT_OK = 0
12
+ EXIT_CONFIG_ERROR = 1
13
+ EXIT_EVAL_ERROR = 2
14
+ EXIT_IO_ERROR = 3
11
15
 
12
16
 
13
17
  def parse_args():
14
- parser = argparse.ArgumentParser("dingo run with local script. (CLI)")
18
+ parser = argparse.ArgumentParser(
19
+ prog="dingo",
20
+ description="Dingo: A Comprehensive AI Data, Model and Application Quality Evaluation Tool",
21
+ )
22
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
23
+
24
+ # --- dingo eval ---
25
+ eval_parser = subparsers.add_parser("eval", help="Run data quality evaluation")
26
+ eval_parser.add_argument(
27
+ "-i", "--input",
28
+ type=str,
29
+ required=True,
30
+ help="Path to JSON config file",
31
+ )
32
+ eval_parser.add_argument(
33
+ "--json",
34
+ action="store_true",
35
+ default=False,
36
+ help="Output result as JSON to stdout (for agent/automation consumption)",
37
+ )
38
+
39
+ # --- dingo info ---
40
+ info_parser = subparsers.add_parser("info", help="List available evaluators and rule groups")
41
+ info_parser.add_argument(
42
+ "--rules", action="store_true", help="List rule-based evaluators",
43
+ )
44
+ info_parser.add_argument(
45
+ "--llm", action="store_true", help="List LLM-based evaluators",
46
+ )
47
+ info_parser.add_argument(
48
+ "--groups", action="store_true", help="List rule groups",
49
+ )
50
+ info_parser.add_argument(
51
+ "--json",
52
+ action="store_true",
53
+ default=False,
54
+ help="Output as JSON",
55
+ )
56
+
57
+ # --- dingo serve ---
58
+ serve_parser = subparsers.add_parser("serve", help="Start MCP server for AI agent integration")
59
+ serve_parser.add_argument(
60
+ "--transport",
61
+ type=str,
62
+ choices=["sse", "stdio"],
63
+ default="sse",
64
+ help="MCP transport type (default: sse)",
65
+ )
66
+ serve_parser.add_argument(
67
+ "--host",
68
+ type=str,
69
+ default="0.0.0.0",
70
+ help="Host to bind (default: 0.0.0.0)",
71
+ )
72
+ serve_parser.add_argument(
73
+ "--port",
74
+ type=int,
75
+ default=8000,
76
+ help="Port to listen on (default: 8000)",
77
+ )
78
+
79
+ # Backward compatibility: bare `dingo --input config.json`
15
80
  parser.add_argument(
16
- "-i",
17
- "--input",
81
+ "-i", "--input",
18
82
  type=str,
19
83
  default=None,
20
- help="Please input config file path",
84
+ help=argparse.SUPPRESS,
21
85
  )
22
86
 
23
- return parser.parse_args()
87
+ return parser.parse_args(), parser
24
88
 
25
89
 
26
90
  def get_config(path: str) -> dict:
27
91
  with open(path, 'r', encoding='utf-8') as f:
28
92
  data = json.load(f)
29
-
30
93
  return data
31
94
 
32
95
 
96
+ def _json_error(error_type: str, message: str, exit_code: int):
97
+ """Output error as JSON and exit with the given code."""
98
+ json.dump({
99
+ "status": "error",
100
+ "error_type": error_type,
101
+ "message": message,
102
+ }, sys.stderr, indent=2, ensure_ascii=False)
103
+ sys.stderr.write("\n")
104
+ sys.exit(exit_code)
105
+
106
+
107
+ def cmd_eval(args):
108
+ """Run evaluation from a config file."""
109
+ use_json = args.json
110
+
111
+ try:
112
+ input_data = get_config(args.input)
113
+ except FileNotFoundError:
114
+ if use_json:
115
+ _json_error("ConfigError", f"Config file not found: {args.input}", EXIT_IO_ERROR)
116
+ raise
117
+ except json.JSONDecodeError as e:
118
+ if use_json:
119
+ _json_error("ConfigError", f"Invalid JSON in {args.input}: {e}", EXIT_CONFIG_ERROR)
120
+ raise
121
+
122
+ try:
123
+ input_args = InputArgs(**input_data)
124
+ except Exception as e:
125
+ if use_json:
126
+ _json_error("ConfigError", f"Invalid config: {e}", EXIT_CONFIG_ERROR)
127
+ raise
128
+
129
+ try:
130
+ executor = Executor.exec_map["local"](input_args)
131
+ result = executor.execute()
132
+ except Exception as e:
133
+ if use_json:
134
+ _json_error("EvalError", str(e), EXIT_EVAL_ERROR)
135
+ raise
136
+
137
+ if use_json:
138
+ json.dump(result.to_dict(), sys.stdout, indent=2, ensure_ascii=False)
139
+ sys.stdout.write("\n")
140
+ else:
141
+ print(result)
142
+
143
+
144
+ def cmd_info(args):
145
+ """List available evaluators and groups."""
146
+ Model.load_model()
147
+
148
+ show_all = not (args.rules or args.llm or args.groups)
149
+
150
+ info = {}
151
+
152
+ if show_all or args.rules:
153
+ rules = {}
154
+ for name, cls in sorted(Model.rule_name_map.items()):
155
+ metric_type = getattr(cls, 'metric_type', '')
156
+ groups = getattr(cls, 'group', [])
157
+ required = [f.value for f in getattr(cls, '_required_fields', [])]
158
+ rules[name] = {
159
+ "metric_type": metric_type,
160
+ "groups": groups,
161
+ "required_fields": required,
162
+ }
163
+ info["rules"] = rules
164
+
165
+ if show_all or args.llm:
166
+ llm_evals = {}
167
+ for name, cls in sorted(Model.llm_name_map.items()):
168
+ required = [f.value for f in getattr(cls, '_required_fields', [])]
169
+ llm_evals[name] = {
170
+ "required_fields": required,
171
+ }
172
+ info["llm_evaluators"] = llm_evals
173
+
174
+ if show_all or args.groups:
175
+ groups = {}
176
+ for group_name, rule_list in sorted(Model.rule_groups.items()):
177
+ groups[group_name] = [cls.__name__ for cls in rule_list]
178
+ info["groups"] = groups
179
+
180
+ if args.json:
181
+ json.dump(info, sys.stdout, indent=2, ensure_ascii=False)
182
+ sys.stdout.write("\n")
183
+ else:
184
+ _print_info_table(info)
185
+
186
+
187
+ def _print_info_table(info):
188
+ """Pretty-print evaluator info as tables."""
189
+ if "rules" in info:
190
+ table = pt.PrettyTable()
191
+ table.field_names = ["Rule", "Metric Type", "Groups", "Required Fields"]
192
+ table.align = "l"
193
+ for name, meta in info["rules"].items():
194
+ table.add_row([
195
+ name,
196
+ meta["metric_type"],
197
+ ", ".join(meta["groups"]) if meta["groups"] else "-",
198
+ ", ".join(meta["required_fields"]) if meta["required_fields"] else "-",
199
+ ])
200
+ print(f"\n=== Rule Evaluators ({len(info['rules'])}) ===")
201
+ print(table)
202
+
203
+ if "llm_evaluators" in info:
204
+ table = pt.PrettyTable()
205
+ table.field_names = ["LLM Evaluator", "Required Fields"]
206
+ table.align = "l"
207
+ for name, meta in info["llm_evaluators"].items():
208
+ table.add_row([
209
+ name,
210
+ ", ".join(meta["required_fields"]) if meta["required_fields"] else "-",
211
+ ])
212
+ print(f"\n=== LLM Evaluators ({len(info['llm_evaluators'])}) ===")
213
+ print(table)
214
+
215
+ if "groups" in info:
216
+ table = pt.PrettyTable()
217
+ table.field_names = ["Group", "Rules"]
218
+ table.align = "l"
219
+ table.max_width["Rules"] = 80
220
+ for group_name, rule_names in info["groups"].items():
221
+ table.add_row([group_name, ", ".join(rule_names)])
222
+ print(f"\n=== Rule Groups ({len(info['groups'])}) ===")
223
+ print(table)
224
+
225
+
226
+ def cmd_serve(args):
227
+ """Start the MCP server for AI agent integration."""
228
+ import importlib.util
229
+ import os
230
+
231
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
232
+ mcp_server_path = os.path.join(project_root, "mcp_server.py")
233
+
234
+ if not os.path.exists(mcp_server_path):
235
+ print(f"Error: mcp_server.py not found at {mcp_server_path}", file=sys.stderr)
236
+ print("Make sure you are running from the dingo project directory.", file=sys.stderr)
237
+ sys.exit(1)
238
+
239
+ spec = importlib.util.spec_from_file_location("mcp_server", mcp_server_path)
240
+ mcp_module = importlib.util.module_from_spec(spec)
241
+ spec.loader.exec_module(mcp_module)
242
+
243
+ mcp = mcp_module.mcp
244
+
245
+ print(f"Starting Dingo MCP server (transport={args.transport})")
246
+ print("Available tools: run_dingo_evaluation, list_dingo_components, get_rule_details, "
247
+ "get_llm_details, get_prompt_details, run_quick_evaluation")
248
+
249
+ if args.transport == "sse":
250
+ mcp.settings.host = args.host
251
+ mcp.settings.port = args.port
252
+ print(f"Listening on {args.host}:{args.port}")
253
+ mcp.run(transport="sse")
254
+ else:
255
+ mcp.run(transport="stdio")
256
+
257
+
258
+ def main():
259
+ args, parser = parse_args()
260
+
261
+ # Backward compatibility: `dingo --input config.json` (no subcommand)
262
+ if args.command is None and args.input:
263
+ input_data = get_config(args.input)
264
+ input_args = InputArgs(**input_data)
265
+ executor = Executor.exec_map["local"](input_args)
266
+ result = executor.execute()
267
+ print(result)
268
+ return
269
+
270
+ if args.command == "eval":
271
+ cmd_eval(args)
272
+ elif args.command == "info":
273
+ cmd_info(args)
274
+ elif args.command == "serve":
275
+ cmd_serve(args)
276
+ else:
277
+ parser.print_help()
278
+
279
+
33
280
  if __name__ == "__main__":
34
- args = parse_args()
35
- input_data = get_config(args.input)
36
- # print(args.input)
37
- # print(input_data)
38
- input_args = InputArgs(**input_data)
39
- executor = Executor.exec_map["local"](input_args)
40
- result = executor.execute()
41
- print(result)
281
+ main()
dingo/utils/exception.py CHANGED
@@ -1,28 +1,26 @@
1
- from fastapi import HTTPException
2
-
3
1
  # tokens
4
2
 
5
3
 
6
- class TokensException(HTTPException):
4
+ class TokensException(Exception):
7
5
  pass
8
6
 
9
7
 
10
8
  class ExceedMaxTokens(TokensException):
11
- status_code = 400
12
9
 
13
10
  def __init__(self, detail="Exceeded maximum allowed tokens."):
14
11
  self.detail = detail
12
+ super().__init__(detail)
15
13
 
16
14
 
17
15
  # convert
18
16
 
19
17
 
20
- class ConvertError(HTTPException):
18
+ class ConvertError(Exception):
21
19
  pass
22
20
 
23
21
 
24
22
  class ConvertJsonError(ConvertError):
25
- status_code = 500
26
23
 
27
24
  def __init__(self, detail="Failed to convert JSON data."):
28
25
  self.detail = detail
26
+ super().__init__(detail)