@roll-agent/octopus-agent 0.0.1 → 0.0.2

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.
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import time
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
6
7
  from typing import Any
@@ -41,3 +42,8 @@ class SchemaCache:
41
42
  version = schema.get("schemaVersion")
42
43
  return str(version) if version is not None else None
43
44
 
45
+ def is_fresh(self, max_age_minutes: int) -> bool:
46
+ if max_age_minutes <= 0 or not self.path.exists():
47
+ return False
48
+ age_seconds = time.time() - self.path.stat().st_mtime
49
+ return age_seconds <= max_age_minutes * 60
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from dataclasses import dataclass
4
5
  from typing import Any
5
6
 
@@ -13,17 +14,69 @@ class SchemaContext:
13
14
  examples: list[dict[str, Any]]
14
15
 
15
16
 
17
+ LOCATION_RECRUITING_TABLES = (
18
+ "province",
19
+ "city",
20
+ "region",
21
+ "job_basic_info",
22
+ "job_address",
23
+ "job_store",
24
+ "store",
25
+ )
26
+
27
+ WORK_ORDER_DETAIL_TABLES = (
28
+ "mvp_applied_jobs_by_helped_account",
29
+ "brand",
30
+ "job_basic_info",
31
+ "sponge_project",
32
+ )
33
+
34
+
16
35
  def retrieve_schema_context(schema: dict[str, Any], question: str, *, max_tables: int = 8) -> SchemaContext:
17
36
  glossary = _matching_items(schema.get("glossary", []), question, keys=("term", "description"))
18
37
  metrics = _matching_items(schema.get("metrics", []), question, keys=("name", "metric", "description", "definition"))
19
38
  examples = _matching_items(schema.get("examples", []), question, keys=("question", "description"))
20
39
  table_names_from_context = _target_table_names(glossary + metrics + examples)
21
- tables = _rank_tables(schema.get("tables", []), question, table_names_from_context)[:max_tables]
40
+ tables = _select_tables(schema.get("tables", []), question, table_names_from_context, max_tables)
22
41
  selected_table_names = {_table_name(table) for table in tables}
23
42
  joins = _filter_joins(schema.get("joins", []), selected_table_names)
24
43
  return SchemaContext(tables=tables, joins=joins, glossary=glossary, metrics=metrics, examples=examples)
25
44
 
26
45
 
46
+ def _select_tables(tables: Any, question: str, preferred_names: set[str], max_tables: int) -> list[dict[str, Any]]:
47
+ ranked = _rank_tables(tables, question, preferred_names)
48
+ if _needs_work_order_detail_tables(question) and isinstance(tables, list):
49
+ return _prepend_named_tables(tables, ranked, WORK_ORDER_DETAIL_TABLES, max_tables)
50
+ if not _needs_location_recruiting_tables(question) or not isinstance(tables, list):
51
+ return ranked[:max_tables]
52
+
53
+ return _prepend_named_tables(tables, ranked, LOCATION_RECRUITING_TABLES, max_tables)
54
+
55
+
56
+ def _prepend_named_tables(
57
+ tables: Any,
58
+ ranked: list[dict[str, Any]],
59
+ names: tuple[str, ...],
60
+ max_tables: int,
61
+ ) -> list[dict[str, Any]]:
62
+ by_name = {_table_name(table): table for table in tables if isinstance(table, dict)}
63
+ selected: list[dict[str, Any]] = []
64
+ seen: set[str] = set()
65
+ for name in names:
66
+ table = by_name.get(name)
67
+ if table is not None:
68
+ selected.append(table)
69
+ seen.add(name)
70
+ for table in ranked:
71
+ name = _table_name(table)
72
+ if name not in seen:
73
+ selected.append(table)
74
+ seen.add(name)
75
+ if len(selected) >= max_tables:
76
+ break
77
+ return selected[:max_tables]
78
+
79
+
27
80
  def _rank_tables(tables: Any, question: str, preferred_names: set[str]) -> list[dict[str, Any]]:
28
81
  if not isinstance(tables, list):
29
82
  return []
@@ -33,6 +86,8 @@ def _rank_tables(tables: Any, question: str, preferred_names: set[str]) -> list[
33
86
  continue
34
87
  name = _table_name(table)
35
88
  score = 5 if name in preferred_names else 0
89
+ if _needs_work_order_detail_tables(question) and name in WORK_ORDER_DETAIL_TABLES:
90
+ score += 10
36
91
  text = " ".join(
37
92
  [
38
93
  name,
@@ -53,14 +108,29 @@ def _matching_items(items: Any, question: str, *, keys: tuple[str, ...]) -> list
53
108
  if not isinstance(items, list):
54
109
  return []
55
110
  tokens = _question_tokens(question)
56
- matches: list[dict[str, Any]] = []
111
+ matches: list[tuple[int, dict[str, Any]]] = []
57
112
  for item in items:
58
113
  if not isinstance(item, dict):
59
114
  continue
60
- text = " ".join(str(item.get(key) or "") for key in keys).lower()
61
- if any(token and token in text for token in tokens):
62
- matches.append(item)
63
- return matches[:8]
115
+ text = " ".join(str(item.get(key) or "") for key in keys + ("sql",)).lower()
116
+ score = sum(1 for token in tokens if token and token in text)
117
+ if _needs_work_order_detail_tables(question):
118
+ for term in (
119
+ "mvp_applied_jobs_by_helped_account",
120
+ "work_order_status",
121
+ "current_helper_status_id",
122
+ "job_basic_info.job_id",
123
+ "报名",
124
+ "工单",
125
+ "候选人状态",
126
+ "面试成功",
127
+ ):
128
+ if term.lower() in text:
129
+ score += 5
130
+ if score > 0:
131
+ matches.append((score, item))
132
+ matches.sort(key=lambda match: match[0], reverse=True)
133
+ return [item for _, item in matches[:8]]
64
134
 
65
135
 
66
136
  def _target_table_names(items: list[dict[str, Any]]) -> set[str]:
@@ -70,6 +140,9 @@ def _target_table_names(items: list[dict[str, Any]]) -> set[str]:
70
140
  value = item.get(key)
71
141
  if isinstance(value, list):
72
142
  names.update(str(name) for name in value)
143
+ sql = item.get("sql")
144
+ if isinstance(sql, str):
145
+ names.update(referenced_table for referenced_table in _referenced_tables(sql))
73
146
  targets = item.get("targets")
74
147
  if isinstance(targets, list):
75
148
  for target in targets:
@@ -108,8 +181,40 @@ def _question_tokens(question: str) -> list[str]:
108
181
  lowered = question.lower()
109
182
  tokens = [lowered]
110
183
  tokens.extend(part for part in lowered.replace("_", " ").replace("-", " ").split() if part)
111
- for word in ("品牌", "项目", "岗位", "门店", "招聘", "在招", "数量", "列表"):
184
+ for word in (
185
+ "品牌",
186
+ "项目",
187
+ "岗位",
188
+ "门店",
189
+ "招聘",
190
+ "在招",
191
+ "数量",
192
+ "列表",
193
+ "报名",
194
+ "报名情况",
195
+ "报名工单",
196
+ "工单",
197
+ "候选人",
198
+ "候选人状态",
199
+ "面试成功",
200
+ "进行中",
201
+ "姓名",
202
+ ):
112
203
  if word in question:
113
204
  tokens.append(word)
114
205
  return list(dict.fromkeys(tokens))
115
206
 
207
+
208
+ def _referenced_tables(sql: str) -> set[str]:
209
+ return set(match.group(1) for match in re.finditer(r"\b(?:from|join)\s+([a-zA-Z_][a-zA-Z0-9_]*)\b", sql, re.IGNORECASE))
210
+
211
+
212
+ def _needs_location_recruiting_tables(question: str) -> bool:
213
+ return any(word in question for word in ("岗位", "门店", "招聘", "在招"))
214
+
215
+
216
+ def _needs_work_order_detail_tables(question: str) -> bool:
217
+ return bool(
218
+ any(word in question for word in ("报名", "工单", "候选人"))
219
+ and any(word in question for word in ("姓名", "岗位", "项目", "候选人状态"))
220
+ )