Coverage for src/integry/resources/functions/types.py: 52%

92 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-03-26 01:26 +0500

1from pydantic import BaseModel, Field 

2from typing import ( 

3 Any, 

4 Awaitable, 

5 Callable, 

6 Dict, 

7 List, 

8 Union, 

9 Literal, 

10 Optional, 

11 Annotated, 

12 TYPE_CHECKING, 

13 cast, 

14) 

15 

16from integry.utils.common import generate_docstring_from_schema_for_smolagent 

17from integry.utils.pydantic import get_pydantic_model_from_json_schema 

18 

19if TYPE_CHECKING: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true

20 from integry.resources.functions.api import Functions as FunctionsResource 

21 

22 

23class StringSchema(BaseModel): 

24 type: Literal["string"] 

25 

26 

27class NumberSchema(BaseModel): 

28 type: Literal["number"] 

29 

30 

31class BooleanSchema(BaseModel): 

32 type: Literal["boolean"] 

33 

34 

35class NullSchema(BaseModel): 

36 type: Literal["null"] 

37 

38 

39class ObjectSchema(BaseModel): 

40 type: Literal["object"] 

41 properties: Dict[str, "JSONSchemaType"] = Field(default_factory=dict) 

42 required: List[str] = [] 

43 additionalProperties: Union["JSONSchemaType", bool] = True 

44 

45 

46class ArraySchema(BaseModel): 

47 type: Literal["array"] 

48 items: Union["JSONSchemaType", List["JSONSchemaType"], None] = None 

49 

50 

51JSONSchemaType = Annotated[ 

52 StringSchema 

53 | NumberSchema 

54 | BooleanSchema 

55 | NullSchema 

56 | ObjectSchema 

57 | ArraySchema, 

58 Field(discriminator="type"), 

59] 

60 

61 

62class FunctionCallOutput(BaseModel): 

63 network_code: int 

64 output: Any 

65 

66 

67class PaginatedFunctionCallOutput(FunctionCallOutput): 

68 cursor: str = Field(alias="_cursor") 

69 

70 

71class Function(BaseModel): 

72 name: str 

73 description: str 

74 parameters: JSONSchemaType 

75 arguments: dict[str, Any] = Field(default_factory=dict) 

76 

77 _json_schema: dict[str, Any] 

78 _resource: "FunctionsResource" 

79 

80 def __init__(self, **data: Any): 

81 super().__init__(**data) 

82 

83 self._resource = data.pop("_resource") 

84 

85 self._json_schema = data 

86 

87 def get_json_schema(self) -> dict[str, Any]: 

88 """ 

89 Returns the JSON schema of the function which can be passed directly to an LLM. 

90 

91 Returns: 

92 The JSON schema. 

93 """ 

94 return self._json_schema 

95 

96 def get_langchain_tool[T]( 

97 self, 

98 from_function: Callable[..., T], 

99 user_id: str, 

100 variables: Optional[dict[str, Any]] = None, 

101 ) -> T: 

102 """ 

103 Returns a LangChain tool for the function. 

104 

105 Args: 

106 from_function: This should be LangChain's `StructuredTool.from_function` method. 

107 user_id: The user ID of the user on whose behalf the function will be called. 

108 variables: The variables to use for mapping the arguments, if applicable. 

109 

110 Returns: 

111 The LangChain tool. 

112 """ 

113 argument_schema = get_pydantic_model_from_json_schema( 

114 json_schema=self.get_json_schema()["parameters"], 

115 ) 

116 

117 tool = from_function( 

118 coroutine=self._get_callable(user_id, variables), 

119 func=self._get_sync_callable(user_id, variables), 

120 name=self.name, 

121 description=self.description, 

122 args_schema=argument_schema, 

123 ) 

124 return tool 

125 

126 def get_haystack_tool[T]( 

127 self, 

128 haystack_tool: Callable[..., T], 

129 user_id: str, 

130 variables: Optional[dict[str, Any]] = None, 

131 ) -> T: 

132 """ 

133 Returns a Haystack tool for the function. 

134 

135 Args: 

136 haystack_tool: This should be Haystack Tool constructor (`from haystack.tools import Tool`). 

137 user_id: The user ID for authentication. 

138 

139 Returns: 

140 The Haystack tool. 

141 """ 

142 

143 schema = self.get_json_schema() 

144 

145 return haystack_tool( 

146 name=schema["name"], 

147 description=schema["description"], 

148 function=self._get_sync_callable(user_id=user_id, variables=variables), 

149 parameters=schema["parameters"], 

150 ) 

151 

152 def get_litellm_tool[T]( 

153 self, 

154 ) -> Dict[str, Any]: 

155 """ 

156 Returns a Litellm tool for the function. 

157 

158 Generates a Litellm tool based on the function's JSON schema. 

159 

160 Returns: 

161 The Litellm tool. 

162 """ 

163 

164 schema = self.get_json_schema() 

165 

166 return { 

167 "type": "function", 

168 "function": { 

169 "name": schema["name"], 

170 "description": schema["description"], 

171 "parameters": schema["parameters"], 

172 }, 

173 } 

174 

175 def get_mistralai_tool[T]( 

176 self, 

177 ) -> Dict[str, Any]: 

178 """ 

179 Returns a Mistral AI tool for the function. 

180 

181 Generates a Mistral AI tool based on the function's JSON schema. 

182 

183 Returns: 

184 The Mistral AI tool. 

185 """ 

186 

187 schema = self.get_json_schema() 

188 

189 return { 

190 "type": "function", 

191 "function": { 

192 "name": schema["name"], 

193 "description": schema["description"], 

194 "parameters": schema["parameters"], 

195 }, 

196 } 

197 

198 def get_llamaindex_tool[T]( 

199 self, 

200 tool_from_defaults: Callable[..., T], 

201 tools_metadata: Callable[..., Any], 

202 user_id: str, 

203 variables: Optional[dict[str, Any]] = None, 

204 ) -> T: 

205 """ 

206 Returns a LlamaIndex tool for the function. 

207 

208 Args: 

209 tool_from_defaults: This should be LlamaIndex's `FunctionTool.from_defaults` method. 

210 tools_metadata: This should be LlamaIndex's `ToolMetadata` class. 

211 user_id: The user ID for authentication. 

212 

213 Returns: 

214 The LlamaIndex tool. 

215 """ 

216 

217 function_schema = get_pydantic_model_from_json_schema( 

218 json_schema=self.get_json_schema()["parameters"], 

219 ) 

220 

221 metadata = tools_metadata( 

222 name=self.name, 

223 description=self.description, 

224 fn_schema=function_schema, 

225 ) 

226 

227 return tool_from_defaults( 

228 async_fn=self._get_callable(user_id=user_id, variables=variables), 

229 tool_metadata=metadata, 

230 ) 

231 

232 def register_with_autogen_agents( 

233 self, 

234 register_function: Callable[..., None], 

235 caller: Any, 

236 executor: Any, 

237 user_id: str, 

238 variables: Optional[dict[str, Any]] = None, 

239 ): 

240 """ 

241 Registers the function as a tool with AutoGen caller and executor agents. 

242 

243 Args: 

244 register_function: This should be AutoGen's `register_function` function (`from autogen import register_function`). 

245 caller: The caller agent. 

246 executor: The executor agent. 

247 user_id: The ID of the user on whose behalf the function will be called. 

248 variables: The variables to use for mapping the arguments, if applicable. 

249 

250 """ 

251 argument_schema = get_pydantic_model_from_json_schema( 

252 json_schema=self.get_json_schema()["parameters"], 

253 ) 

254 

255 async def autogen_function(input: Annotated[argument_schema, f"Input to the {self.name}."]) -> FunctionCallOutput: # type: ignore 

256 args = cast(BaseModel, input).model_dump(by_alias=True, exclude_unset=True) 

257 return await self._resource.call( 

258 self.name, 

259 args, 

260 user_id, 

261 variables, 

262 ) 

263 

264 register_function( 

265 autogen_function, 

266 caller=caller, 

267 executor=executor, 

268 name=self.name, 

269 description=self.description, 

270 ) 

271 

272 def get_smolagent_tool[T]( 

273 self, 

274 newTool: Callable[..., T], 

275 user_id: str, 

276 variables: Optional[dict[str, Any]] = None, 

277 ) -> T: 

278 """ 

279 Returns a SmolAgent tool for the function. 

280 

281 Args: 

282 newTool: This should be SmolAgent tool. 

283 user_id: The user ID for authentication 

284 variables: The variables to use for mapping the arguments, if applicable. 

285 

286 Returns: 

287 The SmolAgent tool. 

288 """ 

289 

290 def add_docstring(func: Callable[..., Any]) -> Callable[..., Any]: 

291 schema = self.get_json_schema() 

292 exec_docstring = generate_docstring_from_schema_for_smolagent(schema) 

293 func.__doc__ = exec_docstring 

294 return func 

295 

296 @newTool 

297 @add_docstring 

298 def execute_function(**kwargs: dict[str, Any]) -> dict[str, Any]: 

299 callable_function = self._get_sync_callable( 

300 user_id=user_id, variables=variables 

301 ) 

302 result = callable_function(**kwargs) 

303 return cast(dict[str, Any], result) 

304 

305 return execute_function 

306 

307 async def __call__( 

308 self, 

309 user_id: str, 

310 arguments: dict[str, Any], 

311 variables: Optional[dict[str, Any]] = None, 

312 connected_account_id: Optional[int] = None, 

313 ) -> FunctionCallOutput: 

314 return await self._resource.call(self.name, arguments, user_id, variables, connected_account_id) 

315 

316 def _get_callable( 

317 self, user_id: str, variables: Optional[dict[str, Any]] = None 

318 ) -> Callable[..., Awaitable[FunctionCallOutput]]: 

319 

320 async def callable(**arguments: dict[str, Any]) -> FunctionCallOutput: 

321 return await self._resource.call(self.name, arguments, user_id, variables) 

322 

323 return callable 

324 

325 def _get_sync_callable( 

326 self, user_id: str, variables: Optional[dict[str, Any]] = None 

327 ): 

328 def sync_callable(**arguments: dict[str, Any]) -> FunctionCallOutput: 

329 return self._resource.call_sync(self.name, arguments, user_id, variables) 

330 

331 return sync_callable 

332 

333 

334class FunctionsPage(BaseModel): 

335 functions: list[Function] 

336 cursor: str 

337 

338 

339IncludeOptions = list[Literal["meta"]] 

340 

341FunctionType = Literal["ACTION", "QUERY"]