165 lines
6.4 KiB
Python
165 lines
6.4 KiB
Python
import pandas as pd
|
|
from typing import List, Dict
|
|
from autoagent.memory.rag_memory import Memory, Reranker
|
|
import json
|
|
import math
|
|
import os
|
|
from litellm import completion
|
|
from pydantic import BaseModel
|
|
"""
|
|
Category | Tool_Name | Tool_Description | API_Name | API_Description | Method | API_Details | Required_API_Key | Platform
|
|
"""
|
|
class ToolMemory(Memory):
|
|
def __init__(
|
|
self,
|
|
project_path: str,
|
|
db_name: str = '.tool_table',
|
|
platform: str = 'OpenAI',
|
|
api_key: str = None,
|
|
embedding_model: str = "text-embedding-3-small",
|
|
):
|
|
super().__init__(
|
|
project_path=project_path,
|
|
db_name=db_name,
|
|
platform=platform,
|
|
api_key=api_key,
|
|
embedding_model=embedding_model
|
|
)
|
|
self.collection_name = 'tool_memory'
|
|
|
|
def add_dataframe(self, df: pd.DataFrame, collection: str = None, batch_size: int = 100):
|
|
if not collection:
|
|
collection = self.collection_name
|
|
queries = []
|
|
for idx, row in df.iterrows():
|
|
query = {
|
|
'query': ' '.join(row[['Tool_Name', 'Tool_Description', 'API_Name', 'API_Description']].astype(str)),
|
|
'response': row.to_json()
|
|
}
|
|
queries.append(query)
|
|
|
|
# self.add_query(queries, collection=collection)
|
|
print(f'Adding {len(queries)} queries to {collection} with batch size {batch_size}')
|
|
num_batches = math.ceil(len(queries) / batch_size)
|
|
|
|
for i in range(num_batches):
|
|
start_idx = i * batch_size
|
|
end_idx = min((i + 1) * batch_size, len(queries))
|
|
batch_queries = queries[start_idx:end_idx]
|
|
|
|
# Add the current batch of queries
|
|
self.add_query(batch_queries, collection=collection)
|
|
|
|
print(f"Batch {i+1}/{num_batches} added")
|
|
|
|
def query_table(
|
|
self,
|
|
query_text: str,
|
|
collection: str = None,
|
|
n_results: int = 5
|
|
) -> pd.DataFrame:
|
|
"""
|
|
Query the table and return the results
|
|
"""
|
|
if not collection:
|
|
collection = self.collection_name
|
|
results = self.query([query_text], collection=collection, n_results=n_results)
|
|
|
|
metadata_results = results['metadatas'][0]
|
|
|
|
df_results = pd.DataFrame([json.loads(item['response']) for item in metadata_results])
|
|
return df_results
|
|
|
|
def peek_table(self, collection: str = None, n_results: int = 20) -> pd.DataFrame:
|
|
"""
|
|
Peek at the data in the table
|
|
"""
|
|
if not collection:
|
|
collection = self.collection_name
|
|
results = self.peek(collection=collection, n_results=n_results)
|
|
df_results = pd.DataFrame([json.loads(item['response']) for item in results['metadatas']])
|
|
return df_results
|
|
|
|
class ToolReranker(Reranker):
|
|
def rerank(self, query_text: str, query_df: pd.DataFrame) -> str:
|
|
system_prompt = \
|
|
"""
|
|
You are a helpful assistant that reranks the given API table based on the query.
|
|
You should select the top 5 APIs to answer the query in the given format.
|
|
You can only select APIs I give you.
|
|
Directly give the answer without any other words.
|
|
"""
|
|
# Use the DataFrame's to_dict method to convert all rows to a list of dictionaries
|
|
# print('query_df', query_df)
|
|
api_data = query_df.to_dict(orient='records')
|
|
|
|
# Use a list comprehension and f-string to format each API's data
|
|
api_prompts = [f"\n\nAPI {i+1}:\n{api}" for i, api in enumerate(api_data)]
|
|
|
|
# add the query text to the prompt
|
|
prompt = ''.join(api_prompts)
|
|
prompt = f"The query is: {query_text}\n\n{prompt}"
|
|
message = [
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": prompt}
|
|
]
|
|
|
|
class Tools(BaseModel):
|
|
tool_name: str
|
|
api_name: str
|
|
rank: int
|
|
|
|
class RerankResult(BaseModel):
|
|
tools: list[Tools]
|
|
|
|
create_params = {
|
|
"model": self.model,
|
|
"messages": message,
|
|
"stream": False,
|
|
"response_format": RerankResult
|
|
}
|
|
response = completion(**create_params).choices[0].message.content
|
|
print(response)
|
|
rerank_result = json.loads(response)["tools"]
|
|
print(rerank_result)
|
|
if len(rerank_result) == 0:
|
|
return "Fail to retrieve the relevant information from the tool documentation."
|
|
try:
|
|
return self.wrap_rerank_result(rerank_result, query_df)
|
|
except Exception as e:
|
|
raise ValueError(f"Failed to wrap rerank result: {e}")
|
|
|
|
def wrap_rerank_result(self, rerank_result: List[pd.DataFrame], query_df: pd.DataFrame) -> str:
|
|
res = ""
|
|
res_tmp = """
|
|
The rank {rank} referenced tool documentation is:
|
|
API Name: {api_name}
|
|
API Description: {api_description}
|
|
API Details: {api_details}
|
|
Required API Key: {required_api_key}
|
|
Platform: {platform}
|
|
"""
|
|
try:
|
|
for tool_api in rerank_result:
|
|
tool_name = tool_api['tool_name']
|
|
api_name = tool_api['api_name']
|
|
matched_rows = query_df[(query_df['API_Name'] == api_name) & (query_df['Tool_Name'] == tool_name)]
|
|
if not matched_rows.empty:
|
|
res = res + res_tmp.format(rank=tool_api['rank'], api_name=matched_rows['API_Name'].values[0], api_description=matched_rows['API_Description'].values[0], api_details=matched_rows['API_Details'].values[0], required_api_key=matched_rows['Required_API_Key'].values[0], platform=matched_rows['Platform'].values[0])
|
|
return res
|
|
except Exception as e:
|
|
raise ValueError(f"Failed to wrap rerank result: {e}")
|
|
def dummy_rerank(self, query_text: str, query_df: pd.DataFrame) -> str:
|
|
res = ""
|
|
res_tmp = """
|
|
The rank {rank} referenced tool documentation is:
|
|
API Name: {api_name}
|
|
API Description: {api_description}
|
|
API Details: {api_details}
|
|
Required API Key: {required_api_key}
|
|
Platform: {platform}
|
|
"""
|
|
for i in range(len(query_df)):
|
|
res = res + res_tmp.format(rank=i+1, api_name=query_df['API_Name'].values[i], api_description=query_df['API_Description'].values[i], api_details=query_df['API_Details'].values[i], required_api_key=query_df['Required_API_Key'].values[i], platform=query_df['Platform'].values[i])
|
|
return res
|