|
31 | 31 | register_sales_tools, |
32 | 32 | register_web_scraper_tools, |
33 | 33 | ) |
34 | | -from tools.registry import get_registered_tool, register_tool |
| 34 | +from tools.registry import register_tool |
35 | 35 |
|
36 | 36 |
|
37 | 37 | def _normalize_web_url(raw_url: str) -> str: |
@@ -199,199 +199,6 @@ def _clean_text(value: str) -> str: |
199 | 199 | return {"results": results} |
200 | 200 |
|
201 | 201 |
|
202 | | -def _prepare_ask_ai_prompt( |
203 | | - *, |
204 | | - prompt: str | None, |
205 | | - question: str | None, |
206 | | - context: Dict[str, Any] | None, |
207 | | - expected_format: str | Mapping[str, Any] | None, |
208 | | -) -> str: |
209 | | - if prompt: |
210 | | - return prompt |
211 | | - |
212 | | - parts = ["You are a helpful assistant that produces concise JSON answers."] |
213 | | - parts.append(f"Question: {question}") |
214 | | - if context: |
215 | | - context_json = json.dumps(context, ensure_ascii=False, indent=2) |
216 | | - parts.append("Context (for reference):") |
217 | | - parts.append(context_json) |
218 | | - if expected_format: |
219 | | - expected_format_text = ( |
220 | | - expected_format |
221 | | - if isinstance(expected_format, str) |
222 | | - else json.dumps(expected_format, ensure_ascii=False, indent=2) |
223 | | - ) |
224 | | - parts.append("Return JSON following this guidance:") |
225 | | - parts.append(expected_format_text) |
226 | | - parts.append("Respond with valid JSON only.") |
227 | | - return "\n\n".join(parts) |
228 | | - |
229 | | - |
230 | | -def _build_call_tool_schema(action: Mapping[str, Any]) -> Optional[Dict[str, Any]]: |
231 | | - action_id = action.get("action_id") or action.get("tool_name") or "tool" |
232 | | - safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", action.get("tool_name") or action_id) |
233 | | - description = action.get("description") or action.get("name") or action_id |
234 | | - parameters = action.get("arg_schema") or action.get("params_schema") |
235 | | - if not isinstance(parameters, Mapping): |
236 | | - return None |
237 | | - |
238 | | - return { |
239 | | - "type": "function", |
240 | | - "function": { |
241 | | - "name": safe_name, |
242 | | - "description": description, |
243 | | - "parameters": parameters, |
244 | | - }, |
245 | | - } |
246 | | - |
247 | | - |
248 | | -def _execute_business_tool( |
249 | | - *, action: Mapping[str, Any], args: Mapping[str, Any], call_name: str |
250 | | -) -> Dict[str, Any]: |
251 | | - tool_fn_name = action.get("tool_name") or action.get("action_id") or call_name |
252 | | - registered_tool = get_registered_tool(tool_fn_name) |
253 | | - |
254 | | - if not registered_tool: |
255 | | - return { |
256 | | - "status": "unavailable", |
257 | | - "action_id": action.get("action_id"), |
258 | | - "message": f"Business tool '{tool_fn_name}' is not registered; returning the original arguments for reference.", |
259 | | - "echo": args, |
260 | | - } |
261 | | - |
262 | | - try: |
263 | | - output = registered_tool(**args) |
264 | | - return { |
265 | | - "status": "ok", |
266 | | - "action_id": action.get("action_id"), |
267 | | - "output": output, |
268 | | - } |
269 | | - except Exception as exc: # pragma: no cover - runtime errors are surfaced to LLM |
270 | | - return { |
271 | | - "status": "error", |
272 | | - "action_id": action.get("action_id"), |
273 | | - "message": str(exc), |
274 | | - } |
275 | | - |
276 | | - |
277 | | -def ask_ai( |
278 | | - *, |
279 | | - system_prompt: str | None = None, |
280 | | - prompt: str | None = None, |
281 | | - question: str | None = None, |
282 | | - context: Dict[str, Any] | None = None, |
283 | | - expected_format: str | Mapping[str, Any] | None = None, |
284 | | - tool: List[Mapping[str, Any]] | None = None, |
285 | | - model: str | None = None, |
286 | | - max_tokens: int = 256, |
287 | | - max_rounds: int = 3, |
288 | | -) -> Dict[str, Any]: |
289 | | - """LLM helper that can optionally call business tools and analyze results.""" |
290 | | - |
291 | | - if not (prompt or question): |
292 | | - raise ValueError("either prompt or question must be provided for ask_ai") |
293 | | - |
294 | | - user_prompt = _prepare_ask_ai_prompt( |
295 | | - prompt=prompt, question=question, context=context, expected_format=expected_format |
296 | | - ) |
297 | | - system_text = system_prompt or "You are an intelligent assistant skilled at invoking business tools to solve problems." |
298 | | - |
299 | | - client = OpenAI() |
300 | | - chat_model = model or OPENAI_MODEL |
301 | | - messages: List[Dict[str, Any]] = [ |
302 | | - {"role": "system", "content": system_text}, |
303 | | - {"role": "user", "content": user_prompt}, |
304 | | - ] |
305 | | - |
306 | | - tools_spec: List[Dict[str, Any]] = [] |
307 | | - tool_lookup: Dict[str, Mapping[str, Any]] = {} |
308 | | - for item in tool or []: |
309 | | - schema = _build_call_tool_schema(item) |
310 | | - if schema: |
311 | | - tool_name = schema["function"]["name"] |
312 | | - tools_spec.append(schema) |
313 | | - tool_lookup[tool_name] = item |
314 | | - |
315 | | - for round_idx in range(1, max_rounds + 1): |
316 | | - response = client.chat.completions.create( |
317 | | - model=chat_model, |
318 | | - messages=messages, |
319 | | - tools=tools_spec or None, |
320 | | - tool_choice="auto" if tools_spec else None, |
321 | | - max_completion_tokens=max_tokens, |
322 | | - ) |
323 | | - if not response.choices: |
324 | | - raise RuntimeError("ask_ai did not return any choices") |
325 | | - |
326 | | - message = response.choices[0].message |
327 | | - assistant_msg = { |
328 | | - "role": "assistant", |
329 | | - "content": message.content or "", |
330 | | - "tool_calls": message.tool_calls, |
331 | | - } |
332 | | - messages.append(assistant_msg) |
333 | | - log_info(f"[ask_ai] round={round_idx} assistant response received") |
334 | | - if assistant_msg.get("content"): |
335 | | - log_json("[ask_ai] assistant content", assistant_msg.get("content")) |
336 | | - else: |
337 | | - reminder = ( |
338 | | - "Your previous response had no content. " |
339 | | - "Please provide a concise response that satisfies the user's requirements." |
340 | | - ) |
341 | | - messages.append({"role": "user", "content": reminder}) |
342 | | - log_info("[ask_ai] assistant content empty; prompting for a complete response") |
343 | | - |
344 | | - tool_calls = getattr(message, "tool_calls", None) or [] |
345 | | - if tool_calls: |
346 | | - for tc in tool_calls: |
347 | | - func_name = tc.function.name |
348 | | - raw_args = tc.function.arguments |
349 | | - try: |
350 | | - parsed_args = json.loads(raw_args) if raw_args else {} |
351 | | - except json.JSONDecodeError: |
352 | | - parsed_args = {"__raw__": raw_args or ""} |
353 | | - |
354 | | - action_def = tool_lookup.get(func_name) |
355 | | - if not action_def: |
356 | | - tool_result = { |
357 | | - "status": "error", |
358 | | - "message": f"Unknown business tool invocation: {func_name}", |
359 | | - "echo": parsed_args, |
360 | | - } |
361 | | - else: |
362 | | - tool_result = _execute_business_tool( |
363 | | - action=action_def, args=parsed_args, call_name=func_name |
364 | | - ) |
365 | | - |
366 | | - messages.append( |
367 | | - { |
368 | | - "role": "tool", |
369 | | - "tool_call_id": tc.id, |
370 | | - "content": json.dumps(tool_result, ensure_ascii=False), |
371 | | - } |
372 | | - ) |
373 | | - log_info(f"[ask_ai] round={round_idx} tool_call={func_name}") |
374 | | - log_json("[ask_ai] tool_args", parsed_args) |
375 | | - log_json("[ask_ai] tool_result", tool_result) |
376 | | - continue |
377 | | - |
378 | | - final_content = (message.content or "").strip() |
379 | | - if not final_content: |
380 | | - continue |
381 | | - |
382 | | - try: |
383 | | - parsed_content = json.loads(final_content) |
384 | | - results = parsed_content if isinstance(parsed_content, dict) else {"answer": parsed_content} |
385 | | - except json.JSONDecodeError: |
386 | | - results = {"answer": final_content} |
387 | | - |
388 | | - return {"status": "ok", "results": results, "messages": messages} |
389 | | - |
390 | | - return { |
391 | | - "status": "error", |
392 | | - "results": {"message": "ask_ai could not produce an answer within the allowed rounds"}, |
393 | | - "messages": messages, |
394 | | - } |
395 | 202 |
|
396 | 203 |
|
397 | 204 | def classify_text( |
@@ -767,31 +574,6 @@ def register_builtin_tools() -> None: |
767 | 574 | ) |
768 | 575 | ) |
769 | 576 |
|
770 | | - register_tool( |
771 | | - Tool( |
772 | | - name="ask_ai", |
773 | | - description=( |
774 | | - "LLM helper that can run multi-round chat with optional business tool calls " |
775 | | - "and return normalized status/results output." |
776 | | - ), |
777 | | - function=ask_ai, |
778 | | - args_schema={ |
779 | | - "type": "object", |
780 | | - "properties": { |
781 | | - "system_prompt": {"type": "string", "nullable": True}, |
782 | | - "prompt": {"type": "string", "nullable": True}, |
783 | | - "question": {"type": "string", "nullable": True}, |
784 | | - "context": {"type": "object", "nullable": True}, |
785 | | - "expected_format": {"type": "string", "nullable": True}, |
786 | | - "tool": {"type": "array", "items": {"type": "object"}, "nullable": True}, |
787 | | - "model": {"type": "string", "nullable": True}, |
788 | | - "max_tokens": {"type": "integer", "default": 256}, |
789 | | - "max_rounds": {"type": "integer", "default": 3}, |
790 | | - }, |
791 | | - }, |
792 | | - ) |
793 | | - ) |
794 | | - |
795 | 577 | register_tool( |
796 | 578 | Tool( |
797 | 579 | name="classify_text", |
@@ -1050,7 +832,6 @@ def register_builtin_tools() -> None: |
1050 | 832 | "register_builtin_tools", |
1051 | 833 | "search_web", |
1052 | 834 | "search_news", |
1053 | | - "ask_ai", |
1054 | 835 | "classify_text", |
1055 | 836 | "list_files", |
1056 | 837 | "read_file", |
|
0 commit comments