airayven7 commited on
Commit
a1d3dec
Β·
verified Β·
1 Parent(s): 14a9b91

Sync from GitHub 4e3ca94

Browse files
Files changed (2) hide show
  1. models/minicpm_agent.py +34 -15
  2. pipelines/agent_ask.py +15 -7
models/minicpm_agent.py CHANGED
@@ -50,24 +50,32 @@ SYSTEM_PROMPT = (
50
  "a page viewer. You do NOT answer questions or explain β€” the manual does the "
51
  "talking. Your only job is to FIND the right page and POINT at things on it.\n\n"
52
  "Each step, choose exactly ONE tool and reply with ONLY its JSON object β€” no "
53
- "prose, no markdown, nothing else:\n"
54
- '- Search the whole manual for a part/topic/procedure:\n'
 
 
55
  ' {"tool": "search", "query": "<focused search phrase>"}\n'
56
- "- Jump to a section (use its number from the TABLE OF CONTENTS):\n"
57
  ' {"tool": "go_to_section", "section": <number>}\n'
58
  "- Circle something on the CURRENT page (its full text is given to you):\n"
59
  ' {"tool": "circle", "target": "<short name of the thing to circle>"}\n'
60
  "- Finish β€” nothing more to do, or it isn't in the manual:\n"
61
  ' {"tool": "done", "message": "<one short line for the mechanic>"}\n\n'
62
- "Rules:\n"
63
- "- Prefer circle when the thing the mechanic wants is plainly on the CURRENT "
64
- "page. Prefer go_to_section when they name or describe a section. Otherwise "
65
- "search, then circle on the page it shows.\n"
66
- "- After a search shows a page, that page becomes the CURRENT page; circle on "
67
- "it or search again.\n"
 
68
  "- Use the conversation history only to resolve what they mean (e.g. "
69
- '"circle the other one"); never restate earlier answers.\n'
70
- "- Reply with ONLY the JSON object."
 
 
 
 
 
71
  )
72
 
73
 
@@ -96,12 +104,16 @@ def state_message(
96
  if page_text
97
  else f"CURRENT PAGE: {where} (no text available)"
98
  )
 
 
 
99
  return {
100
  "role": "user",
101
  "content": (
102
- f"The mechanic said: {request!r}\n\n"
103
  f"TABLE OF CONTENTS:\n{toc_lines}\n\n"
104
- f"{page_block}"
 
105
  ),
106
  }
107
 
@@ -177,7 +189,7 @@ def _parse_tool(raw: str) -> dict | None:
177
  tool = obj.get("tool")
178
  if tool == "search":
179
  query = str(obj.get("query") or "").strip()
180
- return {"tool": "search", "query": query} if query else None
181
  if tool == "go_to_section":
182
  try:
183
  return {"tool": "go_to_section", "section": int(obj.get("section"))}
@@ -185,12 +197,19 @@ def _parse_tool(raw: str) -> dict | None:
185
  return None
186
  if tool == "circle":
187
  target = str(obj.get("target") or "").strip()
188
- return {"tool": "circle", "target": target} if target else None
189
  if tool == "done":
190
  return {"tool": "done", "message": str(obj.get("message") or "").strip()}
191
  return None
192
 
193
 
 
 
 
 
 
 
 
194
  def decide(messages: list[dict]) -> tuple[dict | None, str]:
195
  """One agentic step. messages is the running conversation the pipeline
196
  maintains (system + past turns + this turn's state and any tool results).
 
50
  "a page viewer. You do NOT answer questions or explain β€” the manual does the "
51
  "talking. Your only job is to FIND the right page and POINT at things on it.\n\n"
52
  "Each step, choose exactly ONE tool and reply with ONLY its JSON object β€” no "
53
+ "prose, no markdown, nothing else. Replace every <...> placeholder with the "
54
+ "real value β€” NEVER output the angle brackets.\n\n"
55
+ "Tools:\n"
56
+ '- Search the manual for a part/topic/procedure:\n'
57
  ' {"tool": "search", "query": "<focused search phrase>"}\n'
58
+ "- Jump to a section β€” use its number from the TABLE OF CONTENTS:\n"
59
  ' {"tool": "go_to_section", "section": <number>}\n'
60
  "- Circle something on the CURRENT page (its full text is given to you):\n"
61
  ' {"tool": "circle", "target": "<short name of the thing to circle>"}\n'
62
  "- Finish β€” nothing more to do, or it isn't in the manual:\n"
63
  ' {"tool": "done", "message": "<one short line for the mechanic>"}\n\n'
64
+ "How to choose:\n"
65
+ '- They say "go to" / "take me to" / name a section β†’ go_to_section.\n'
66
+ "- The thing they want is on the CURRENT page β†’ circle it. The target MUST "
67
+ "be what the mechanic asked for β€” match it to the page's exact wording if it "
68
+ "appears there; NEVER circle a different component.\n"
69
+ "- Otherwise β†’ search. After a search shows a page, that page becomes the "
70
+ "CURRENT page, so circle on it or search again.\n"
71
  "- Use the conversation history only to resolve what they mean (e.g. "
72
+ '"circle the other one"); never restate earlier answers.\n\n'
73
+ "Examples (copy the FORMAT, not the values):\n"
74
+ 'Mechanic: "go to the cooling system" β†’ {"tool": "go_to_section", "section": 5}\n'
75
+ 'Mechanic: "where do I replace the fuel filter" (not on this page) β†’ '
76
+ '{"tool": "search", "query": "fuel filter replacement"}\n'
77
+ 'Mechanic: "circle the bleeder screw" (it is on this page) β†’ '
78
+ '{"tool": "circle", "target": "bleeder screw"}'
79
  )
80
 
81
 
 
104
  if page_text
105
  else f"CURRENT PAGE: {where} (no text available)"
106
  )
107
+ # The request goes LAST (after the long page text) so it stays freshest β€”
108
+ # otherwise the page block dominates and the agent acts on the page instead
109
+ # of what was asked.
110
  return {
111
  "role": "user",
112
  "content": (
113
+ f"{page_block}\n\n"
114
  f"TABLE OF CONTENTS:\n{toc_lines}\n\n"
115
+ f"The mechanic said: {request!r}\n"
116
+ "Choose ONE tool and reply with ONLY its JSON object."
117
  ),
118
  }
119
 
 
189
  tool = obj.get("tool")
190
  if tool == "search":
191
  query = str(obj.get("query") or "").strip()
192
+ return {"tool": "search", "query": query} if _real(query) else None
193
  if tool == "go_to_section":
194
  try:
195
  return {"tool": "go_to_section", "section": int(obj.get("section"))}
 
197
  return None
198
  if tool == "circle":
199
  target = str(obj.get("target") or "").strip()
200
+ return {"tool": "circle", "target": target} if _real(target) else None
201
  if tool == "done":
202
  return {"tool": "done", "message": str(obj.get("message") or "").strip()}
203
  return None
204
 
205
 
206
+ def _real(value: str) -> bool:
207
+ """A usable arg, not an echoed placeholder. Small models sometimes copy the
208
+ schema example verbatim ("<short name of the thing to circle>") β€” angle
209
+ brackets are the tell; reject so the loop re-asks for a real value."""
210
+ return bool(value) and "<" not in value and ">" not in value
211
+
212
+
213
  def decide(messages: list[dict]) -> tuple[dict | None, str]:
214
  """One agentic step. messages is the running conversation the pipeline
215
  maintains (system + past turns + this turn's state and any tool results).
pipelines/agent_ask.py CHANGED
@@ -112,12 +112,16 @@ def agent_events(
112
  tool, raw = minicpm_agent.decide(messages)
113
  log.info("step %d: tool=%s | raw=%r", step, tool, raw[:200])
114
  if tool is None:
115
- yield {
116
- "type": "done",
117
- "kind": "reply",
118
- "message": "Sorry, I didn't catch that β€” try rephrasing.",
119
- }
120
- return
 
 
 
 
121
  messages.append(minicpm_agent.assistant_action_message(tool))
122
 
123
  if tool["tool"] == "go_to_section":
@@ -169,7 +173,11 @@ def agent_events(
169
  minicpm_agent.tool_result_message(
170
  f"Search showed p.{best_page}. It is now the CURRENT page.\n"
171
  f"CURRENT PAGE (p.{best_page}) β€” full text:\n"
172
- f"{page_text(best_page) or '(no text available)'}"
 
 
 
 
173
  )
174
  )
175
  continue
 
112
  tool, raw = minicpm_agent.decide(messages)
113
  log.info("step %d: tool=%s | raw=%r", step, tool, raw[:200])
114
  if tool is None:
115
+ # Unusable reply (bad JSON, or an echoed placeholder target). Correct
116
+ # it and let the agent try again rather than abandon the turn.
117
+ messages.append(
118
+ minicpm_agent.tool_result_message(
119
+ "That was not a valid tool call. Reply with ONLY one JSON "
120
+ 'object like {"tool": "circle", "target": "drain plug"} β€” '
121
+ "fill in the real value."
122
+ )
123
+ )
124
+ continue
125
  messages.append(minicpm_agent.assistant_action_message(tool))
126
 
127
  if tool["tool"] == "go_to_section":
 
173
  minicpm_agent.tool_result_message(
174
  f"Search showed p.{best_page}. It is now the CURRENT page.\n"
175
  f"CURRENT PAGE (p.{best_page}) β€” full text:\n"
176
+ f"{page_text(best_page) or '(no text available)'}\n\n"
177
+ f"The mechanic asked for: {request!r}. If THAT is on this page, "
178
+ "circle it (use its exact name as printed on the page). "
179
+ "Otherwise search again or go to a section. Do not circle a "
180
+ "different component."
181
  )
182
  )
183
  continue