Skip to content

Commit 597fa3f

Browse files
authored
Merge pull request #85 from pattern-tech/refactor/ai-v2
Add support for POKT tools and environment variables for APIs
2 parents 709f5f9 + 3856716 commit 597fa3f

File tree

5 files changed

+5369
-1
lines changed

5 files changed

+5369
-1
lines changed

.github/workflows/dev.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ jobs:
9393
-e FANTOM_RPC=${{ vars.FANTOM_RPC }} \
9494
-e BASE_RPC=${{ vars.BASE_RPC }} \
9595
-e SOLSCAN_API_KEY=${{ secrets.SOLSCAN_API_KEY }} \
96+
-e GEMINI_API_KEY=${{ secrets.GEMINI_API_KEY }} \
97+
-e POKT_API_KEY=${{ secrets.POKT_API_KEY }} \
98+
-e POKT_GRAPH_SCHEMA_PATH=${{ vars.POKT_GRAPH_SCHEMA_PATH }} \
99+
-e POKT_GRAPH_ENDPOINT=${{ vars.POKT_GRAPH_ENDPOINT }} \
96100
patterntechnology/pattern-core-api:latest
97101
98102
# Wait a few seconds to give the container time to start
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import re
2+
import os
3+
import json
4+
import requests
5+
6+
from google import genai
7+
from datetime import datetime
8+
from google.genai import types
9+
from langchain.tools import tool
10+
11+
from src.share.logging import Logging
12+
from src.agentflow.utils.shared_tools import load_pokt_schema
13+
14+
# Get logger instance
15+
logger = Logging().get_logger()
16+
17+
pokt_graphql = load_pokt_schema()
18+
19+
20+
def extract_graphql_query(response_text):
21+
"""
22+
Extract the GraphQL query from LLM response text.
23+
24+
Args:
25+
response_text (str): The response text from the LLM.
26+
27+
Returns:
28+
str: The extracted GraphQL query.
29+
"""
30+
# Look for text between ```graphql and ``` or between ``` and ```
31+
pattern = r'```(?:graphql)?\s*([\s\S]*?)\s*```'
32+
matches = re.findall(pattern, response_text)
33+
34+
if matches:
35+
return matches[0].strip()
36+
else:
37+
# If no code block, try to find a query block
38+
pattern = r'query\s*\{[\s\S]*?\}'
39+
matches = re.findall(pattern, response_text)
40+
if matches:
41+
return matches[0].strip()
42+
else:
43+
# Return the whole text if no specific query format found
44+
return response_text.strip()
45+
46+
47+
def execute_graphql_query(query, variables=None):
48+
"""
49+
Execute a GraphQL query against the POKT API.
50+
51+
Args:
52+
query (str): The GraphQL query to execute.
53+
variables (dict, optional): Variables for the GraphQL query.
54+
55+
Returns:
56+
dict: The response from the GraphQL API.
57+
"""
58+
# GraphQL endpoint URL
59+
url = os.environ.get("POKT_GRAPH_ENDPOINT")
60+
if not url:
61+
logger.error("Environment variable 'POKT_GRAPH_ENDPOINT' is not set.")
62+
raise ValueError(
63+
"Environment variable 'POKT_GRAPH_ENDPOINT' is not set.")
64+
65+
# Prepare the request payload
66+
payload = {
67+
"query": query,
68+
}
69+
70+
if variables:
71+
payload["variables"] = variables
72+
73+
# Set headers
74+
headers = {
75+
"Content-Type": "application/json"
76+
}
77+
78+
api_key = os.environ.get("POKT_API_KEY")
79+
if not api_key:
80+
logger.error("Environment variable 'POKT_API_KEY' is not set.")
81+
raise ValueError("Environment variable 'POKT_API_KEY' is not set.")
82+
83+
params = {
84+
"token": api_key
85+
}
86+
87+
# Make the request
88+
try:
89+
logger.info(f"Executing GraphQL query: {query[:100]}...")
90+
response = requests.post(
91+
url, json=payload, headers=headers, params=params)
92+
response.raise_for_status() # Raise an exception for HTTP errors
93+
return response.json()
94+
except requests.exceptions.RequestException as e:
95+
logger.error(f"Error making GraphQL request: {e}")
96+
return {"error": str(e), "query": query}
97+
98+
99+
def fix_graphql_query(pokt_graphql, user_task, graphql_query, error_response):
100+
"""
101+
Attempt to fix a failed GraphQL query using AI.
102+
103+
Args:
104+
pokt_graphql (str): The GraphQL schema.
105+
user_task (str): The original user task.
106+
graphql_query (str): The failed GraphQL query.
107+
error_response (str): The error response from the GraphQL API.
108+
109+
Returns:
110+
str: A fixed GraphQL query.
111+
"""
112+
# Initialize client
113+
api_key = os.environ.get("GEMINI_API_KEY")
114+
if not api_key:
115+
logger.error("Environment variable 'GEMINI_API_KEY' is not set.")
116+
raise ValueError("Environment variable 'GEMINI_API_KEY' is not set.")
117+
118+
client = genai.Client(api_key=api_key)
119+
120+
# Define the system prompt to help fix the query
121+
error_handling_system_prompt = f"""You are an expert programmer.
122+
An graphql query was generated and executed but there is an error in the query. You need to fix the query and write a new query.
123+
124+
graphql schema is:
125+
```graphql
126+
{pokt_graphql}
127+
```
128+
129+
user task is:
130+
```text
131+
{user_task}
132+
```
133+
134+
graphql query is:
135+
```graphql
136+
{graphql_query}
137+
```
138+
139+
error is:
140+
```json
141+
{error_response}
142+
```
143+
"""
144+
145+
# Define system prompt configuration
146+
config = types.GenerateContentConfig(
147+
system_instruction=error_handling_system_prompt
148+
)
149+
150+
try:
151+
logger.info("Attempting to fix GraphQL query with AI")
152+
# Ask the model to fix the query
153+
response = client.models.generate_content(
154+
model="gemini-2.0-flash",
155+
contents="Please fix the GraphQL query to address the error.",
156+
config=config,
157+
)
158+
159+
# Extract the fixed GraphQL query
160+
fixed_query = extract_graphql_query(response.text)
161+
logger.info("Generated fixed GraphQL query")
162+
return fixed_query
163+
except Exception as e:
164+
logger.error(f"Error trying to fix GraphQL query: {e}")
165+
# If we can't fix it, return the original query
166+
return graphql_query
167+
168+
169+
@tool
170+
def answer_pokt_query_task(user_task: str) -> str:
171+
"""
172+
Answer a task using the POKT GraphQL API. The user task will be converted to a POKT GraphQL
173+
query and execute the query. Will attempt up to 3 times to fix query errors.
174+
175+
Args:
176+
user_task (str): exact user task to be answered.
177+
178+
Returns:
179+
str: The result of the task.
180+
"""
181+
logger.info(f"Processing POKT query task: {user_task}")
182+
183+
try:
184+
185+
logger.info("Successfully loaded POKT GraphQL schema")
186+
187+
# Create system prompt with current date
188+
system_prompt = f"""You are an expert programmer. Your given a graphql schema and you need to generate graphql query to answer user task.
189+
190+
Pokt Date format in schema is YYYY-MM-DDTHH:mm:ss.SSSZ
191+
192+
Today date is {datetime.now()}
193+
The graphql schema is:
194+
```graphql
195+
{pokt_graphql}
196+
```
197+
"""
198+
199+
# Initialize client
200+
api_key = os.environ.get("GEMINI_API_KEY")
201+
if not api_key:
202+
logger.error("Environment variable 'GEMINI_API_KEY' is not set.")
203+
return {"error": "Environment variable 'GEMINI_API_KEY' is not set."}
204+
205+
client = genai.Client(api_key=api_key)
206+
207+
# Define a system prompt to steer the model's behavior
208+
config = types.GenerateContentConfig(
209+
system_instruction=system_prompt
210+
)
211+
212+
# Generate GraphQL query
213+
logger.info("Generating initial GraphQL query with Gemini")
214+
response = client.models.generate_content(
215+
model="gemini-2.0-flash",
216+
contents=user_task,
217+
config=config,
218+
)
219+
220+
# Extract the GraphQL query from the LLM response
221+
graphql_query = extract_graphql_query(response.text)
222+
logger.info(f"Generated GraphQL query: {graphql_query}")
223+
224+
# Execute the GraphQL query with retry logic (max 3 attempts)
225+
max_attempts = 3
226+
attempt = 1
227+
228+
while attempt <= max_attempts:
229+
logger.info(f"Query execution attempt {attempt}/{max_attempts}")
230+
result = execute_graphql_query(graphql_query)
231+
232+
# Check if there's an error in the response
233+
if "errors" in result:
234+
logger.warning(
235+
f"Attempt {attempt}/{max_attempts}: GraphQL query failed: {json.dumps(result.get('errors', {}))}")
236+
237+
# If we've reached max attempts, return the error
238+
if attempt == max_attempts:
239+
logger.error(
240+
"Failed to execute query after maximum attempts")
241+
return {
242+
"error": "Failed to execute query after maximum attempts",
243+
"last_error": result["errors"],
244+
"last_query": graphql_query
245+
}
246+
247+
# Try to fix the query
248+
error_response = json.dumps(result.get("errors", {}))
249+
graphql_query = fix_graphql_query(
250+
pokt_graphql, user_task, graphql_query, error_response)
251+
attempt += 1
252+
else:
253+
# Query succeeded, return the result
254+
logger.info("GraphQL query executed successfully")
255+
return result
256+
257+
# Should never reach here due to the return in the loop
258+
return result
259+
except Exception as e:
260+
logger.error(f"Error in answer_pokt_query_task: {str(e)}")
261+
return {"error": str(e), "task": user_task}

src/agentflow/tool/hub.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class ToolRegistery:
1515
"chain_scan",
1616
"evm_moralis",
1717
"solana_moralis",
18-
"solana"
18+
"solana",
19+
"pokt"
1920
]
2021

2122
_tool_selector = None

src/agentflow/utils/shared_tools.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
import os
23
import functools
34
import threading
45

@@ -311,3 +312,15 @@ def wrapper(*args, **kwargs):
311312
print("----------------------------------------")
312313
return result
313314
return wrapper
315+
316+
317+
def load_pokt_schema():
318+
"""Load the POKT GraphQL schema from the file specified in environment variables."""
319+
file_path = os.environ.get("POKT_GRAPH_SCHEMA_PATH")
320+
if file_path is None:
321+
raise ValueError(
322+
"Environment variable 'POKT_GRAPH_SCHEMA_PATH' is not set.")
323+
324+
# Read the contents of the file
325+
with open(file_path, "r") as file:
326+
return file.read()

0 commit comments

Comments
 (0)