Skip to content

Commit c3f4f9b

Browse files
committed
Initial Robopages integration
1 parent 165e102 commit c3f4f9b

4 files changed

Lines changed: 187 additions & 2 deletions

File tree

rigging/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from rigging import data, error, generator, model, parsing, watchers
1+
from rigging import data, error, generator, integrations, logging, model, parsing, tool, watchers
22
from rigging.chat import Chat, ChatPipeline, MapChatCallback, ThenChatCallback
33
from rigging.completion import Completion, CompletionPipeline, MapCompletionCallback, ThenCompletionCallback
44
from rigging.generator import (
@@ -53,6 +53,9 @@
5353
"model",
5454
"error",
5555
"parsing",
56+
"tool",
57+
"integrations",
58+
"logging",
5659
"await_",
5760
"interact",
5861
"ThenChatCallback",

rigging/integrations/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .robopages import RoboPagesTool, robopages
2+
3+
__all__ = [
4+
"RoboPagesTool",
5+
"robopages",
6+
]

rigging/integrations/robopages.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import json
2+
import re
3+
import typing as t
4+
from functools import cached_property
5+
from urllib.parse import urlparse
6+
7+
import requests
8+
import typing_extensions as te
9+
from loguru import logger
10+
11+
from rigging.message import Message
12+
from rigging.tool import ApiTool, Tool, ToolType
13+
from rigging.tool.api import FunctionDefinition, ToolCall, ToolDefinition
14+
from rigging.tracing import tracer
15+
16+
17+
class RobopagesApiTool(ApiTool):
18+
"""
19+
Overload of the ApiTool class to support making tool calls through a Robopages server.
20+
"""
21+
22+
def __init__(self, url: str, name: str, description: str, parameters: dict[str, t.Any] | None) -> None:
23+
self._url = urlparse(url)._replace(path="").geturl()
24+
self._name = name
25+
self._description = description
26+
self._parameters = parameters
27+
28+
@cached_property
29+
def name(self) -> str:
30+
return self._name
31+
32+
@cached_property
33+
def description(self) -> str:
34+
return self._name
35+
36+
@cached_property
37+
def schema(self) -> dict[str, t.Any]:
38+
return self._parameters or {}
39+
40+
@cached_property
41+
def definition(self) -> ToolDefinition:
42+
return ToolDefinition(
43+
function=FunctionDefinition(name=self.name, description=self.description, parameters=self._parameters)
44+
)
45+
46+
async def execute(self, tool_call: ToolCall) -> Message:
47+
"""Executes a function call on the tool."""
48+
49+
from rigging.message import Message
50+
51+
if tool_call.function.name != self.name:
52+
raise ValueError(f"Function name {tool_call.function.name} does not match {self.name}")
53+
54+
with tracer.span(f"Robopages Tool {self.name}()", name=self.name, tool_call_id=tool_call.id) as span:
55+
args = json.loads(tool_call.function.arguments)
56+
span.set_attribute("arguments", args)
57+
58+
response = requests.post(
59+
f"{self._url}/process",
60+
json=[
61+
{
62+
"type": "function",
63+
"id": tool_call.id,
64+
"function": {
65+
"name": self._name,
66+
"arguments": args,
67+
},
68+
}
69+
],
70+
)
71+
if response.status_code not in [200, 400]:
72+
response.raise_for_status()
73+
74+
if response.status_code == 400:
75+
result = response.content.decode()
76+
else:
77+
result = response.json()[0]["content"]
78+
79+
span.set_attribute("result", result)
80+
81+
return Message(role="tool", tool_call_id=tool_call.id, content=str(result))
82+
83+
__call__ = execute
84+
85+
86+
class OpenAIFunction(te.TypedDict):
87+
name: str
88+
description: str
89+
parameters: dict[str, t.Any]
90+
91+
92+
class OpenAIFormat(te.TypedDict):
93+
type: str
94+
function: OpenAIFunction
95+
96+
97+
class RobopagesFunction(te.TypedDict):
98+
name: str
99+
description: str
100+
parameters: list[dict[str, t.Any]]
101+
page_name: str
102+
page_description: str
103+
104+
105+
@t.overload
106+
def robopages(url: str, *, tool_type: t.Literal["api"] = "api") -> list[RobopagesApiTool]:
107+
...
108+
109+
110+
@t.overload
111+
def robopages(url: str, *, tool_type: t.Literal["native"]) -> list[Tool]:
112+
...
113+
114+
115+
def robopages(
116+
url: str, *, tool_type: ToolType = "api", name_filter: str | None = None
117+
) -> list[RobopagesApiTool] | list[Tool]:
118+
"""
119+
Create a list of tools from a Robopages server.
120+
121+
Args:
122+
url: The URL of the Robopages server.
123+
tool_type: The type of tools to fetch.
124+
name_filter: A regular expression to filter the tools by name.
125+
126+
Returns:
127+
A list of tools from the Robopages server.
128+
129+
Example:
130+
131+
```python
132+
import rigging as rg
133+
134+
tools = rg.integrations.robopages("http://localhost:8080")
135+
136+
chat = (
137+
await rg.get_generator('gpt-4o')
138+
.chat('Please use tools')
139+
.using(*tools)
140+
.run()
141+
)
142+
143+
print(chat.conversation)
144+
```
145+
"""
146+
147+
filter_regex = re.compile(name_filter) if name_filter else None
148+
149+
response = requests.get(url, params={"flavor": "rigging" if tool_type == "native" else "openai"})
150+
response.raise_for_status()
151+
tools_data = response.json()
152+
153+
if tool_type == "api":
154+
openai_tools = t.cast(list[OpenAIFormat], tools_data)
155+
156+
logger.info(f"Fetched {len(openai_tools)} functions from Robopages ({url})")
157+
158+
tools: list[RobopagesApiTool] = []
159+
for tool in openai_tools:
160+
function = tool["function"]
161+
if filter_regex and not filter_regex.search(function["name"]):
162+
logger.debug(f"Skipping function {function['name']}")
163+
continue
164+
tools.append(RobopagesApiTool(url, function["name"], function["description"], function.get("parameters")))
165+
166+
return tools
167+
168+
raise NotImplementedError("Only API tools are supported right now")

rigging/tool/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
This module defines handles tool interaction with rigging generation.
33
"""
44

5+
import typing as t
6+
57
from rigging.tool.api import ApiTool
68
from rigging.tool.native import Tool
79

8-
__all__ = ["Tool", "ApiTool"]
10+
ToolType = t.Literal["api", "native"]
11+
12+
__all__ = [
13+
"Tool",
14+
"ApiTool",
15+
"ToolType",
16+
]

0 commit comments

Comments
 (0)