Conversation
Move provider specific logic into prepare_args method mostly. The only exceptions are OpenAI because it uses completely different method to call the API and TestMock which doesn't require most of the things.
Change the LLM call API to pass the list of tools to call and receive the list of tool calls. Adapt loop.metta to this change. Fix unit tests.
The historical information is still returned via HISTORY paragraph of the system prompt.
paul-v-snet
left a comment
There was a problem hiding this comment.
Other non-critical problems:
&lastresultsandLAST_SKILL_USE_RESULTShave been removed fromloop.metta, but are still described in thedocs/, e.g., here: https://github.com/vsbogd/Omega/blob/125889524b707e1ee55d12df3fa7bcf9f67c2a67/docs/reference-internals-loop.md;
Also, most of the issues found during QA in #349 still apply to this PR, so they should be addressed here as well.
|
|
||
| def _log_raw(provider: str, model: str, raw: str) -> None: | ||
| logger.debug(f"[LLM_RAW] provider={provider} model={model} chars={len(raw or '')} raw={raw!r}") | ||
| def _log_raw(kind, provider: str, model: str, raw: Dict) -> None: |
There was a problem hiding this comment.
non-critical: kind without annotation
suggesting to replace it with: kind: str
| raw = llm._llm_empty_response_command() | ||
| return self._clean_text(raw) | ||
| if incomplete_reason == "max_output_tokens": | ||
| response.add_tool_call(_llm_empty_response_tool_call(raw.id)) |
There was a problem hiding this comment.
critical: _llm_empty_response_tool_call defined in providers/lib_llm_ext.py, so it will lead to an error.
suggesting to replace it with: response.add_tool_call(llm._llm_empty_response_tool_call(raw.id))
| } | ||
|
|
||
| def convert_response(self, raw): | ||
| response = LLMResponse() |
There was a problem hiding this comment.
non-critical: double spaces
| (= (llmRequestMessage role $role content $content) | ||
| (py-call (providers.llmRequestMessage $role $content))) | ||
|
|
||
| (= (llmRequestMessage role $role callid $callid content $content) | ||
| (py-call (providers.llmToolCallResponseMessage $role $callid $content))) | ||
|
|
||
| (= (llmRequestMessage role $role calls $calls content $content) | ||
| (py-call (providers.llmToolCallMessage $role $calls $content))) |
There was a problem hiding this comment.
non-critical
I'm afraid this may be confusing. The same expression llmRequestMessage refers to several different things at the same time:
- User's request to the LLM
- LLM's response
- Tool call results
So I believe it would be better to use different names for them, but since this is not critical, it would be enough to just take this into account for the future.
| self.max_tokens = 6000 | ||
| self.reasoning_mode = "medium" |
There was a problem hiding this comment.
non-critical, but important
This may lead to hidden issues in the future since max_tokens and reasoning_mode are hardcoded here. If for any reason these values are not passed explicitly, they will be silently configured using these hardcoded values rather than the values from config.yaml.
So I suggest using config_get_by_key here instead of any hardcoded values.
| def _validate_response(request: LLMRequest, response: LLMResponse) -> LLMResponse: | ||
| for call in response.calls: | ||
| if call.is_error(): | ||
| continue | ||
| if not request.has_tool(call.name): | ||
| call.set_error(f"Unknown tool: {call.name!r}") | ||
| call.set_tool(request.get_tool(call.name)) | ||
| for parameter in call.tool.parameters: | ||
| if not parameter.name in call.arguments: | ||
| call.set_error(f"Call tool parameter is not set: tool: {call.name!r}, parameter: {parameter.name!r}") | ||
| break | ||
| return response |
There was a problem hiding this comment.
potentially critical:
There is no continue after call.set_error(f"Unknown tool: {call.name!r}"), so call.set_tool(request.get_tool(call.name)) at line 248 will return None, and for parameter in call.tool.parameters: at line 249 will raise an AttributeError.
As a side effect, the LLM will never receive the Unknown tool message and will not be able to correct itself in subsequent responses.
| def llmToolCallToSExpr(call: LLMToolCall): | ||
| sexpr = f"({call.name} " | ||
| for parameter in call.tool.parameters: | ||
| if parameter.name in call.arguments: | ||
| arg = call.arguments[parameter.name] | ||
| arg = arg.replace('"','\\"') | ||
| sexpr = sexpr + f"\"{arg}\" " | ||
| sexpr = sexpr + ")" | ||
| if call.is_error(): | ||
| sexpr = f"(Error {sexpr} \"{call.error}\")" | ||
| return f"({call.id} {sexpr})" |
There was a problem hiding this comment.
Same as what @TossSky found during QA in #349, point 8:
- llmToolCallToSExpr escapes " but not \ (providers.py:316), so ls C:\ becomes (shell "ls C:") and the backslash escapes the closing quote. A literal \n in an argument turns into a newline. A non-string argument raises AttributeError at loop.metta:79, outside the catch on line 83.
| "\nResults in " (researchDir) "/" $research-name "/" | ||
| "\nWorkflow unloaded from context."))) | ||
| (appendToHistory ((get_time_as_string) (newline) (strings-concat ("RESEARCH [" $research-name "] COMPLETED")) (newline)) ) | ||
| (addToHistory (strings-concat ("RESEARCH [" $research-name "] COMPLETED"))) |
There was a problem hiding this comment.
potentially critical:
addToHistory expects 3 arguments, but the workflow plugins provide only 1 everywhere.
I guess this will lead to errors or these expressions being silently ignored (I'm not sure how this will be processed by PeTTa).
Anyway, I'd like to ask @TossSky to check this during QA later.
Description
This PR is part of #349 which doesn't include loop and memory changes. It delivers the most massive code change: using tools API to get the list of tool calls from LLM. It obsoletes
helper.balance_parenthesisfunction.How Has This Been Tested?
This PR doesn't introduce any code logic change just migrates existing logic to the new API. Regression testing should be enough. This PR is checked using automatic regression testing and manual smoke check with each provider.
Checklist