dingo-python 2.1.1__py3-none-any.whl → 2.2.1__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.
- dingo/config/input_args.py +0 -1
- dingo/exec/local.py +0 -26
- dingo/model/llm/agent/agent_article_fact_checker.py +22 -12
- dingo/model/llm/agent/tools/arxiv_search.py +73 -5
- dingo/run/cli.py +256 -16
- dingo/utils/exception.py +4 -6
- {dingo_python-2.1.1.dist-info → dingo_python-2.2.1.dist-info}/METADATA +77 -92
- {dingo_python-2.1.1.dist-info → dingo_python-2.2.1.dist-info}/RECORD +12 -92
- dingo_python-2.2.1.dist-info/entry_points.txt +2 -0
- app/.editorconfig +0 -9
- app/.eslintignore +0 -4
- app/.eslintrc.cjs +0 -13
- app/.gitignore +0 -5
- app/.npmrc +0 -2
- app/.prettierignore +0 -6
- app/.prettierrc.json +0 -9
- app/.prettierrc.yaml +0 -4
- app/README.md +0 -100
- app/README_ZH.md +0 -104
- app/app-static.py +0 -114
- app/app.py +0 -22
- app/dev-app-update.yml +0 -3
- app/electron-builder.yml +0 -45
- app/electron.vite.config.ts +0 -135
- app/package-lock.json +0 -11249
- app/package.json +0 -86
- app/pnpm-lock.yaml +0 -7504
- app/postcss.config.js +0 -8
- app/resources/icon.png +0 -0
- app/resources/logo.svg +0 -39
- app/src/main/index.ts +0 -343
- app/src/preload/index.d.ts +0 -21
- app/src/preload/index.ts +0 -51
- app/src/renderer/index.html +0 -17
- app/src/renderer/src/App.tsx +0 -49
- app/src/renderer/src/assets/base.css +0 -67
- app/src/renderer/src/assets/electron.svg +0 -20
- app/src/renderer/src/assets/iconfont.js +0 -1
- app/src/renderer/src/assets/main.css +0 -180
- app/src/renderer/src/assets/svg/empty.svg +0 -16
- app/src/renderer/src/assets/wavy-lines.svg +0 -25
- app/src/renderer/src/components/HightLightText/index.module.scss +0 -0
- app/src/renderer/src/components/HightLightText/index.tsx +0 -135
- app/src/renderer/src/components/Versions.tsx +0 -15
- app/src/renderer/src/components/detail-card/index.tsx +0 -126
- app/src/renderer/src/components/detail-table.tsx +0 -257
- app/src/renderer/src/components/ellipsis-text.tsx +0 -119
- app/src/renderer/src/components/empty.tsx +0 -31
- app/src/renderer/src/components/file-structure-table.tsx +0 -271
- app/src/renderer/src/components/filter-cascader/index.module.scss +0 -37
- app/src/renderer/src/components/filter-cascader/index.tsx +0 -93
- app/src/renderer/src/components/icon-font.tsx +0 -7
- app/src/renderer/src/components/readFileDir.tsx +0 -228
- app/src/renderer/src/components/text-tooltip/index.module.scss +0 -12
- app/src/renderer/src/components/text-tooltip/index.tsx +0 -103
- app/src/renderer/src/constant/Language.ts +0 -4
- app/src/renderer/src/constant/index.ts +0 -10
- app/src/renderer/src/constant/storage.ts +0 -1
- app/src/renderer/src/env.d.ts +0 -1
- app/src/renderer/src/locale/en.ts +0 -12
- app/src/renderer/src/locale/zh.ts +0 -15
- app/src/renderer/src/main.tsx +0 -11
- app/src/renderer/src/pages/index.module.scss +0 -28
- app/src/renderer/src/pages/index.tsx +0 -0
- app/src/renderer/src/pages/main-home/components/pieChart.tsx +0 -404
- app/src/renderer/src/pages/main-home/components/summary-data-table.tsx +0 -94
- app/src/renderer/src/pages/main-home/index.module.scss +0 -5
- app/src/renderer/src/pages/main-home/index.tsx +0 -17
- app/src/renderer/src/pages/sideBar.tsx +0 -226
- app/src/renderer/src/store/config.ts +0 -55
- app/src/renderer/src/store/dal.ts +0 -331
- app/src/renderer/src/store/language.tsx +0 -76
- app/src/renderer/src/styles/custom-antd.module.scss +0 -46
- app/src/renderer/src/typing.ts +0 -12
- app/src/renderer/src/utils/clone.ts +0 -12
- app/src/renderer/src/utils/env.ts +0 -3
- app/src/renderer/src/utils/index.ts +0 -32
- app/src/renderer/src/utils/indexedDB-storage.ts +0 -38
- app/src/renderer/src/utils/store.ts +0 -66
- app/tailwind.config.js +0 -73
- app/test.py +0 -55
- app/tsconfig.json +0 -4
- app/tsconfig.node.json +0 -8
- app/tsconfig.web.json +0 -23
- dingo/run/vsl.py +0 -432
- web-static/assets/main-BJ2wBIkh.js +0 -136425
- web-static/assets/main-ByTNTbJP.css +0 -1532
- web-static/directory-browser.html +0 -206
- web-static/index.html +0 -18
- web-static/src/assets/iconfont.js +0 -1
- {dingo_python-2.1.1.dist-info → dingo_python-2.2.1.dist-info}/WHEEL +0 -0
- {dingo_python-2.1.1.dist-info → dingo_python-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {dingo_python-2.1.1.dist-info → dingo_python-2.2.1.dist-info}/top_level.txt +0 -0
dingo/config/input_args.py
CHANGED
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
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
-
|
|
64
|
-
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
103
|
-
"
|
|
104
|
-
"
|
|
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
|
|
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
|
-
|
|
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(
|
|
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=
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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)
|