44import os
55import traceback
66from functools import wraps
7- from typing import Optional , Union
7+ from typing import Callable , Optional , Union
88
99import aiohttp
1010import litellm
2727)
2828from agents .matmaster_agent .prompt import get_params_check_info_prompt
2929from agents .matmaster_agent .utils .auth import ak_to_ticket , ak_to_username
30+ from agents .matmaster_agent .utils .finance import get_user_photon_balance
3031from agents .matmaster_agent .utils .helper_func import (
3132 check_None_wrapper ,
3233 function_calls_to_str ,
@@ -164,6 +165,100 @@ async def default_before_tool_callback(tool, args, tool_context):
164165 return
165166
166167
168+ def default_cost_func () -> int :
169+ return 300
170+
171+
172+ def check_user_phonon_balance (
173+ func : BeforeToolCallback , cost_func : Callable [[], int ] = default_cost_func
174+ ) -> BeforeToolCallback :
175+ @wraps (func )
176+ async def wrapper (
177+ tool : BaseTool , args : dict , tool_context : ToolContext
178+ ) -> Optional [dict ]:
179+ # 两步操作:
180+ # 1. 调用被装饰的 before_tool_callback;
181+ # 2. 如果调用的 before_tool_callback 有返回值,以这个为准
182+ if (before_tool_result := await func (tool , args , tool_context )) is not None :
183+ return before_tool_result
184+
185+ # 如果 tool 不是 CalculationMCPTool,不应该调用这个 callback
186+ if not isinstance (tool , CalculationMCPTool ):
187+ raise TypeError (
188+ "Not CalculationMCPTool type, current tool can't create job!"
189+ )
190+
191+ user_id = _get_user_id (tool_context )
192+ cost = cost_func ()
193+ balance = await get_user_photon_balance (user_id )
194+
195+ logger .info (
196+ f"[check_user_phonon_balance] user_id={ user_id } , cost={ cost } , balance={ balance } "
197+ )
198+ if balance < cost :
199+ raise RuntimeError ('Phonon is not enough, Please recharge.' )
200+
201+ return wrapper
202+
203+
204+ def check_job_create (func : BeforeToolCallback ) -> BeforeToolCallback :
205+ @wraps (func )
206+ async def wrapper (
207+ tool : BaseTool , args : dict , tool_context : ToolContext
208+ ) -> Optional [dict ]:
209+ # 两步操作:
210+ # 1. 调用被装饰的 before_tool_callback;
211+ # 2. 如果调用的 before_tool_callback 有返回值,以这个为准
212+ if (before_tool_result := await func (tool , args , tool_context )) is not None :
213+ return before_tool_result
214+
215+ # 如果 tool 不是 CalculationMCPTool,不应该调用这个 callback
216+ if not isinstance (tool , CalculationMCPTool ):
217+ raise TypeError (
218+ "Not CalculationMCPTool type, current tool can't create job!"
219+ )
220+
221+ if tool .executor is not None :
222+ job_create_url = f"{ OPENAPI_HOST } /openapi/v1/job/create"
223+ user_project_list_url = f"{ OPENAPI_HOST } /openapi/v1/open/user/project/list"
224+ payload = {
225+ 'projectId' : MATERIALS_PROJECT_ID ,
226+ 'name' : 'check_job_create' ,
227+ }
228+ params = {'accessKey' : MATERIALS_ACCESS_KEY }
229+
230+ logger .info (
231+ f"[check_job_create] project_id = { MATERIALS_PROJECT_ID } , "
232+ f"ak = { MATERIALS_ACCESS_KEY } "
233+ )
234+
235+ async with aiohttp .ClientSession () as session :
236+ async with session .get (
237+ user_project_list_url , params = params
238+ ) as response :
239+ res = json .loads (await response .text ())
240+ logger .info (f"[check_job_create] res = { res } " )
241+ project_name = [
242+ item ['project_name' ]
243+ for item in res ['data' ]['items' ]
244+ if item ['project_id' ] == MATERIALS_PROJECT_ID
245+ ][0 ]
246+
247+ async with aiohttp .ClientSession () as session :
248+ async with session .post (
249+ job_create_url , json = payload , params = params
250+ ) as response :
251+ res = json .loads (await response .text ())
252+ if res ['code' ] != 0 :
253+ if res ['code' ] == 2000 :
254+ res ['error' ][
255+ 'msg'
256+ ] = f"您所用项目为 `{ project_name } `,该项目余额不足,请充值或更换项目后重试。"
257+ return res
258+
259+ return wrapper
260+
261+
167262@check_None_wrapper
168263def _get_ak (ctx : Union [InvocationContext , ToolContext , CallbackContext ]):
169264 session_state = get_session_state (ctx )
@@ -180,6 +275,14 @@ def _get_projectId(ctx: Union[InvocationContext, ToolContext]):
180275 )
181276
182277
278+ @check_None_wrapper
279+ def _get_user_id (ctx : Union [InvocationContext , ToolContext ]):
280+ session_state = get_session_state (ctx )
281+ return session_state [FRONTEND_STATE_KEY ].get ('adk_user_id' ) or os .getenv (
282+ 'MATERIALS_USER_ID'
283+ )
284+
285+
183286def _inject_ak (ctx : Union [InvocationContext , ToolContext ], executor , storage ):
184287 access_key = _get_ak (ctx )
185288 if executor is not None :
@@ -295,64 +398,6 @@ async def wrapper(
295398 return wrapper
296399
297400
298- def check_job_create (func : BeforeToolCallback ) -> BeforeToolCallback :
299- @wraps (func )
300- async def wrapper (
301- tool : BaseTool , args : dict , tool_context : ToolContext
302- ) -> Optional [dict ]:
303- # 两步操作:
304- # 1. 调用被装饰的 before_tool_callback;
305- # 2. 如果调用的 before_tool_callback 有返回值,以这个为准
306- if (before_tool_result := await func (tool , args , tool_context )) is not None :
307- return before_tool_result
308-
309- # 如果 tool 不是 CalculationMCPTool,不应该调用这个 callback
310- if not isinstance (tool , CalculationMCPTool ):
311- raise TypeError (
312- "Not CalculationMCPTool type, current tool can't create job!"
313- )
314-
315- if tool .executor is not None :
316- job_create_url = f"{ OPENAPI_HOST } /openapi/v1/job/create"
317- user_project_list_url = f"{ OPENAPI_HOST } /openapi/v1/open/user/project/list"
318- payload = {
319- 'projectId' : MATERIALS_PROJECT_ID ,
320- 'name' : 'check_job_create' ,
321- }
322- params = {'accessKey' : MATERIALS_ACCESS_KEY }
323-
324- logger .info (
325- f"[check_job_create] project_id = { MATERIALS_PROJECT_ID } , "
326- f"ak = { MATERIALS_ACCESS_KEY } "
327- )
328-
329- async with aiohttp .ClientSession () as session :
330- async with session .get (
331- user_project_list_url , params = params
332- ) as response :
333- res = json .loads (await response .text ())
334- logger .info (f"[check_job_create] res = { res } " )
335- project_name = [
336- item ['project_name' ]
337- for item in res ['data' ]['items' ]
338- if item ['project_id' ] == MATERIALS_PROJECT_ID
339- ][0 ]
340-
341- async with aiohttp .ClientSession () as session :
342- async with session .post (
343- job_create_url , json = payload , params = params
344- ) as response :
345- res = json .loads (await response .text ())
346- if res ['code' ] != 0 :
347- if res ['code' ] == 2000 :
348- res ['error' ][
349- 'msg'
350- ] = f"您所用项目为 `{ project_name } `,该项目余额不足,请充值或更换项目后重试。"
351- return res
352-
353- return wrapper
354-
355-
356401# 总应该在最后
357402def catch_before_tool_callback_error (func : BeforeToolCallback ) -> BeforeToolCallback :
358403 @wraps (func )
0 commit comments