Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Featuring SQ mech tool #240

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
19 changes: 19 additions & 0 deletions packages/subquery/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------
19 changes: 19 additions & 0 deletions packages/subquery/customs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------
20 changes: 20 additions & 0 deletions packages/subquery/customs/graphql_response_analyser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2023-2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------

"""This module explains the output of the GraphQL server within a given context, including a description of the indexed data being served and the query itself."""
19 changes: 19 additions & 0 deletions packages/subquery/customs/graphql_response_analyser/component.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: graphql_response_analyser
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are some missing required fields here:

aea.exceptions.AEAValidationError: The following errors occurred during validation:
 - : 'fingerprint' is a required property
 - : 'fingerprint_ignore_patterns' is a required property

author: subquery
version: 0.1.0
type: custom
description: This module explains the output of the GraphQL chain data indexer within a given context, including a description of the indexed data being served and the query itself.
license: Apache-2.0
aea_version: '>=1.0.0, <2.0.0'
entry_point: graphql_response_analyser.py
fingerprint:
__init__.py:
graphql_response_analyser.py:
fingerprint_ignore_patterns: []
callable: run
dependencies:
openai:
version: ==1.11.0
tiktoken:
version: ==0.5.1
requests: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2023-2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------

"""Contains the job definitions"""

from typing import Any, Dict, Optional, Tuple
import os
from openai import OpenAI
import json
import requests

client: Optional[OpenAI] = None


def generate_graphql_query(user_request, schema, description, examples):
return (
f"""
You are a GraphQL query generator. Based on the following GraphQL schema and the user's natural language request, generate a valid GraphQL query.

GraphQL Project Description: "{description}"

User Request: "{user_request}"

GraphQL Schema: {json.dumps(schema)}

Example Queries:

"""
+ examples
+ """

GraphQL Query:

"""
)


# Analyze data and generate response using OpenAI
def analyze_data_and_generate_response(data):
return f"""

Once the query you have given was executed, the following data was fetched:

JSON Data: {json.dumps(data)}

Based on the provided context, please generate a bullet-pointed summary in a machine-readable JSON format. The JSON structure should have an array object named 'analysis_result,' with each analytical conclusion represented as a separate string element within the array.
"""


class OpenAIClientManager:
"""Client context manager for OpenAI."""

def __init__(self, api_key: str):
self.api_key = api_key

def __enter__(self) -> OpenAI:
global client
if client is None:
client = OpenAI(api_key=self.api_key)
return client

def __exit__(self, exc_type, exc_value, traceback) -> None:
global client
if client is not None:
client.close()
client = None


DEFAULT_OPENAI_SETTINGS = {
"max_tokens": 500,
"temperature": 0.7,
}
PREFIX = "openai-"
ENGINES = {
"chat": ["gpt-3.5-turbo", "gpt-4"],
"completion": ["gpt-3.5-turbo-instruct"],
}
ALLOWED_TOOLS = [PREFIX + value for values in ENGINES.values() for value in values]


# Fetch the GraphQL schema using introspection query
def fetch_graphql_schema(endpoint):
introspection_query = """
{
__schema {
types {
name
fields {
name
type {
kind
name
}
}
}
}
}
"""
response = requests.post(endpoint, json={"query": introspection_query})
if response.status_code == 200:
return response.json()
else:
raise Exception(
f"Failed to fetch schema: {response.status_code}, {response.text}"
)


def fetch_data_from_indexer(endpoint, query):
response = requests.post(endpoint, json={"query": query})
if response.status_code == 200:
return response.json()
else:
raise Exception(
f"Failed to fetch schema: {response.status_code}, {response.text}"
)


def run(**kwargs) -> Tuple[Optional[str], Optional[Dict[str, Any]], Any, Any]:
"""Run the task"""
with OpenAIClientManager(kwargs["api_keys"]["openai"]):
max_tokens = kwargs.get("max_tokens", DEFAULT_OPENAI_SETTINGS["max_tokens"])
temperature = kwargs.get("temperature", DEFAULT_OPENAI_SETTINGS["temperature"])
endpoint = kwargs.get("endpoint")
description = kwargs.get("description")
request = kwargs.get("request")
examples = kwargs.get("examples")
tool = kwargs["tool"]
schema = fetch_graphql_schema(endpoint)
prompt = generate_graphql_query(request, schema, description, examples)
if tool not in ALLOWED_TOOLS:
return (
f"Tool {tool} is not in the list of supported tools.",
None,
None,
None,
)
engine = tool.replace(PREFIX, "")
messages = [
{"role": "user", "content": prompt},
]
response = client.chat.completions.create(
model=engine,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
n=1,
timeout=120,
stop=None,
)
query_to_be_used = response.choices[0].message.content
print(query_to_be_used)
requested_data = fetch_data_from_indexer(endpoint, query_to_be_used)
messages = [
{"role": "user", "content": prompt},
{"role": "user", "content": query_to_be_used},
{
"role": "user",
"content": analyze_data_and_generate_response(requested_data),
},
]
response = client.chat.completions.create(
model=engine,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
n=1,
timeout=120,
stop=None,
)
return response.choices[0].message.content, prompt, None, None
72 changes: 72 additions & 0 deletions tests/subquery/examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2024 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------

query_examples = """
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copyright header is missing here, which the CI complains about

1.

# Provide me a list of addresses that have made the highest number of transfers to a specific address

query MyQuery {
account(id: "0x0000000000000000000000000000000000000000") {
id
receivedTransfers {
groupedAggregates(groupBy: FROM_ID) {
keys
distinctCount {
id
}
}
}
}
}

2.

# Please provide a list of addresses who transfered the highest amounts within the certain timeframe.

query MyQuery {
account(id: "0x0000000000000000000000000000000000000000") {
id
receivedTransfers(
first: 5
filter: {and: [{timestamp: {greaterThan: "0"}}, {timestamp: {lessThan: "1000"}}]}
) {
groupedAggregates(groupBy: FROM_ID) {
keys
sum {
value
}
}
}
}
}

3.

# Please provide a first transfer ever indexed

query MyQuery {
transfers(first: 1, orderBy: TIMESTAMP_ASC) {
nodes {
id
value
}
}
}
"""
28 changes: 26 additions & 2 deletions tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
from packages.valory.customs.prediction_request import prediction_request
from packages.valory.skills.task_execution.utils.apis import KeyChain
from packages.valory.skills.task_execution.utils.benchmarks import TokenCounterCallback

from packages.subquery.customs.graphql_response_analyser import (
graphql_response_analyser
)
from tests.subquery.examples import query_examples

from tests.constants import (
OPENAI_SECRET_KEY,
STABILITY_API_KEY,
Expand Down Expand Up @@ -191,7 +197,6 @@ class TestDALLEGeneration(BaseToolTest):
]
tool_module = dalle_request


class TestPredictionSentenceEmbeddings(BaseToolTest):
"""Test Prediction Sum URL Content."""

Expand All @@ -202,7 +207,6 @@ class TestPredictionSentenceEmbeddings(BaseToolTest):
]
tool_module = prediction_sentence_embeddings


class TestOfvMarketResolverTool(BaseToolTest):
"""Test OFV Market Resolver Tool."""

Expand All @@ -212,3 +216,23 @@ class TestOfvMarketResolverTool(BaseToolTest):
'Please take over the role of a Data Scientist to evaluate the given question. With the given question "Will Apple release iPhone 17 by March 2025?" and the `yes` option represented by `Yes` and the `no` option represented by `No`, what are the respective probabilities of `p_yes` and `p_no` occurring?'
]
tool_module = ofv_market_resolver

class TestGraphResponseAnalyser:
"""Check successful query output analysis"""

tool_callable: str = "run"
tool_module = graphql_response_analyser

def test_run(self) -> None:
"""Test run method."""
kwargs = dict(
tool="openai-gpt-3.5-turbo",
request="When was the first transfer?",
examples=query_examples,
endpoint="https://api.subquery.network/sq/subquery/cusdnew",
description="This project manages and indexes data pertaining to cUSD (CELO USD) ERC-20 token transfers and approvals recorded within a dedicated smart contract. The stored data includes information on approvals granted and transfers executed. These entities provide insights into the authorization and movement of cUSD tokens within the CELO ecosystem, facilitating analysis and monitoring of token transactions.",
api_keys={"openai": OPENAI_SECRET_KEY},
)
func = getattr(self.tool_module, self.tool_callable)
response = func(**kwargs)
assert "analysis_result" in response[0]
Loading