fix: tool calls, again lol
This commit is contained in:
1 parent
0dee6a392f
commit
f217e7db3e
3 files changed
+67
-23
No files matched your search
@@ -31,17 +31,17 @@ async def chat_completion_loop(app, run, messages, model_id=None, tools=None):
|
|||||||
opts=run.input,
|
opts=run.input,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pre_len = len(messages)
|
||||||
async for msg in generator:
|
async for msg in generator:
|
||||||
messages.append(msg)
|
messages.append(msg)
|
||||||
|
run.emit_event('inference', msg)
|
||||||
if msg.role == 'tool':
|
if msg.role == 'tool':
|
||||||
tool_calls.append(msg.content)
|
tool_calls.append(msg.content)
|
||||||
if msg.role == 'assistant':
|
if msg.role == 'assistant':
|
||||||
turn_response = True
|
turn_response = True
|
||||||
|
|
||||||
run.emit_event('inference', msg)
|
# Avoid infinite loops from server failures
|
||||||
|
if len(messages) == pre_len: break
|
||||||
# Broken generator with no output
|
|
||||||
else: break
|
|
||||||
|
|
||||||
for tc in tool_calls:
|
for tc in tool_calls:
|
||||||
tool = next((t for t in tools if t.name == tc.tool_name), None)
|
tool = next((t for t in tools if t.name == tc.tool_name), None)
|
||||||
@@ -53,9 +53,8 @@ async def chat_completion_loop(app, run, messages, model_id=None, tools=None):
|
|||||||
else:
|
else:
|
||||||
tc.tool = tool
|
tc.tool = tool
|
||||||
|
|
||||||
# If not calling must output something useful
|
# If not calling, respond to end the turn
|
||||||
else:
|
if not (tool_calls and execute_tools) and turn_response: break
|
||||||
if turn_response: break
|
|
||||||
|
|
||||||
run.close()
|
run.close()
|
||||||
|
|
||||||
|
|||||||
+60
-15
@@ -6,6 +6,7 @@ class's automatic ``ToolSpec`` generation (docstring -> description,
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from html import unescape
|
from html import unescape
|
||||||
|
|
||||||
@@ -28,24 +29,68 @@ def _strip_html(html: str) -> str:
|
|||||||
class WebSearch(Tool):
|
class WebSearch(Tool):
|
||||||
"""Search the web with DuckDuckGo. Returns titles, URLs and snippets."""
|
"""Search the web with DuckDuckGo. Returns titles, URLs and snippets."""
|
||||||
|
|
||||||
|
ENDPOINT = "https://html.duckduckgo.com/html/"
|
||||||
|
USER_AGENT = (
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
|
||||||
|
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Result anchors look like:
|
||||||
|
# <a class="result__a" href="//duckduckgo.com/l/?uddg=...">Title</a>
|
||||||
|
_RESULT_RE = re.compile(
|
||||||
|
r'<a[^>]*class="[^"]*\bresult__a\b[^"]*"[^>]*href="(?P<href>[^"]+)"[^>]*>'
|
||||||
|
r"(?P<title>.*?)</a>",
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
_SNIPPET_RE = re.compile(
|
||||||
|
r'<a[^>]*class="[^"]*\bresult__snippet\b[^"]*"[^>]*>(?P<snippet>.*?)</a>',
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
|
||||||
def run(self, query: str, max_results: int = 5):
|
def run(self, query: str, max_results: int = 5):
|
||||||
# `ddgs` is the current package name; `duckduckgo_search` is the legacy one.
|
body = urllib.parse.urlencode({"q": query}).encode("utf-8")
|
||||||
try:
|
req = urllib.request.Request(
|
||||||
from ddgs import DDGS
|
self.ENDPOINT,
|
||||||
except ImportError: # pragma: no cover - fallback for older installs
|
data=body,
|
||||||
from duckduckgo_search import DDGS
|
method="POST",
|
||||||
|
headers={
|
||||||
|
"User-Agent": self.USER_AGENT,
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"Accept": "text/html",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
|
page = resp.read().decode("utf-8", errors="replace")
|
||||||
|
|
||||||
with DDGS() as ddgs:
|
snippets = [m.group("snippet") for m in self._SNIPPET_RE.finditer(page)]
|
||||||
results = list(ddgs.text(query, max_results=max_results))
|
|
||||||
|
|
||||||
return [
|
results = []
|
||||||
{
|
for i, match in enumerate(self._RESULT_RE.finditer(page)):
|
||||||
"title": r.get("title", ""),
|
if len(results) >= max_results:
|
||||||
"url": r.get("href", ""),
|
break
|
||||||
"snippet": r.get("body", ""),
|
results.append({
|
||||||
}
|
"title": self._clean(match.group("title")),
|
||||||
for r in results
|
"url": self._unwrap(match.group("href")),
|
||||||
]
|
"snippet": self._clean(snippets[i]) if i < len(snippets) else "",
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clean(value: str) -> str:
|
||||||
|
return unescape(re.sub(r"<[^>]+>", "", value)).strip()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _unwrap(href: str) -> str:
|
||||||
|
# DDG wraps result links as //duckduckgo.com/l/?uddg=<url-encoded-target>
|
||||||
|
if "uddg=" in href:
|
||||||
|
target = urllib.parse.parse_qs(urllib.parse.urlparse(href).query).get(
|
||||||
|
"uddg", [""]
|
||||||
|
)[0]
|
||||||
|
if target:
|
||||||
|
return target
|
||||||
|
if href.startswith("//"):
|
||||||
|
return "https:" + href
|
||||||
|
return href
|
||||||
|
|
||||||
|
|
||||||
class WebFetch(Tool):
|
class WebFetch(Tool):
|
||||||
|
|||||||
+1
-1
@@ -149,7 +149,7 @@ class Tool:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tool_name(cls):
|
def tool_name(cls):
|
||||||
return ''.join(['-'+c.lower() if c.isupper() else c for c in cls.__name__]).lstrip('_')
|
return ''.join(['-'+c.lower() if c.isupper() else c for c in cls.__name__]).lstrip('-')
|
||||||
|
|
||||||
|
|
||||||
def _generate_spec(self):
|
def _generate_spec(self):
|
||||||
|
|||||||
Reference in new issue
Block a user