File size: 6,398 Bytes
fcbf0a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f47e966
9da8a12
 
 
f47e966
fcbf0a3
 
 
 
 
 
 
 
 
 
f47e966
 
fcbf0a3
 
 
 
 
733bdfd
fcbf0a3
 
9da8a12
fcbf0a3
4100b1a
fcbf0a3
9da8a12
fcbf0a3
 
ef408d7
9da8a12
ef408d7
9da8a12
ef408d7
fcbf0a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9da8a12
ef408d7
9da8a12
 
fcbf0a3
 
9da8a12
ef408d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcbf0a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6ba032
ef408d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcbf0a3
 
 
 
 
 
69c11c4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import uuid
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from langchain_core.messages import BaseMessage, HumanMessage, trim_messages
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from pydantic import BaseModel
from typing import Optional
import json
from sse_starlette.sse import EventSourceResponse
import io
import sys
from contextlib import redirect_stdout, redirect_stderr
from langchain_core.runnables import RunnableConfig
import requests
import uvicorn
import re
from fastapi.staticfiles import StaticFiles
from langchain_core.runnables import RunnableConfig
from langchain_core.prompts import ChatPromptTemplate
from datetime import datetime

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.mount("/chatui", StaticFiles(directory="static/chatui", html=True), name="index")

class CodeExecutionResult:
    def __init__(self, output: str, error: str = None):
        self.output = output
        self.error = error

API_URL = "https://pvanand-code-execution-files-v5.hf.space"

@tool
def execute_python(code: str, config: RunnableConfig):
    """Execute Python code in an IPython interactiveshell and return the output.
       The returned artifacts (if present) are automatically rendered in the UI and visible to the user. 
    Args:
        code: Valid Python code with correct indentation and syntax including necessary imports.

    Available Libraries:
    # Use plotly as the default charting library

    matplotlib
    pandas
    plotly
    groq
    yfinance
    numpy
    seaborn
    numpy
    scikit-learn
    statsmodels
    geopandas
    folium
    fpdf
    kaleido
    scipy
    geopy
    mapbox
    """

    #print(config)

    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/json'
    }
    data = {
        "session_token": "test12345", #config.configurable.get("thread_id", "test"),
        "code": code
    }
    response = requests.post(
        f'{API_URL}/v0/execute',
        headers=headers,
        data=json.dumps(data)
    )

    if response.status_code != 200:
        return f"Error: Request failed with status code {response.status_code}. Response: {response.text}"
    else:
        response_json = response.json()
        return f"data: {json.dumps(response_json)} \ndata:"

# Configure the memory and model"
memory = MemorySaver()
model = ChatOpenAI(model="gpt-4o-mini", streaming=True)
prompt = ChatPromptTemplate.from_messages([
    ("system", f"You are a Data Visualization assistant.You have access to a jupyter client with access to internet for python code execution. Your taks is to assist users with your data analysis and visualization expertise. Today's date is {datetime.now().strftime('%Y-%m-%d')}. The current folder contains the following files: {{collection_files}}"),
    ("placeholder", "{messages}"),
])

def state_modifier(state) -> list[BaseMessage]:
    collection_files = "None"
    # Format the prompt with the current state
    formatted_prompt = prompt.invoke({
        "collection_files": collection_files,
        "messages": state["messages"]
    })
    
    # Trim the messages
    return trim_messages(
        formatted_prompt,
        token_counter=len,
        max_tokens=16000,
        strategy="last",
        start_on="human",
        include_system=True,
        allow_partial=False,
    )

# Create the agent with the Python execution tool
agent = create_react_agent(
    model,
    tools=[execute_python],
    checkpointer=memory,
    state_modifier=state_modifier,
)

class ChatInput(BaseModel):
    message: str
    thread_id: Optional[str] = None

@app.post("/chat")
async def chat(input_data: ChatInput):
    thread_id = input_data.thread_id or str(uuid.uuid4())
    
    config = {
        "configurable": {
            "thread_id": thread_id
        }
    }
    
    input_message = HumanMessage(content=input_data.message)
    
    async def generate():
        async for event in agent.astream_events(
            {"messages": [input_message]}, 
            config,
            version="v2"
        ):
            kind = event["event"]
            
            if kind == "on_chat_model_stream":
                content = event["data"]["chunk"].content
                if content:
                    yield f"{json.dumps({'type': 'token', 'content': content})}\n"

            elif kind == "on_tool_start":
                tool_input = event['data'].get('input', '')
                yield f"{json.dumps({'type': 'tool_start', 'tool': event['name'], 'input': tool_input})}\n"
            
            elif kind == "on_tool_end":
                tool_output = event['data'].get('output', '').content
                #print(type(tool_output))
                #print(dir(tool_output))
                #print the keys
                pattern = r'data: (.*?)\ndata:'
                match = re.search(pattern, tool_output)
                print(tool_output)

                if match:
                    tool_output_json = match.group(1).strip()
                    try:
                        tool_output = json.loads(tool_output_json)
                        if "artifacts" in tool_output:
                            for artifact in tool_output["artifacts"]:
                                artifact_content = requests.get(f"{API_URL}/artifact/{artifact['artifact_id']}").content
                                print(artifact_content)
                                tool_output["artifacts"][artifact["artifact_id"]] = artifact_content
                    except Exception as e:
                        print(e)
                        print("Error parsing tool output as json: ", tool_output)
                else:
                    print("No match found in tool output")  
                yield f"{json.dumps({'type': 'tool_end', 'tool': event['name'], 'output': tool_output})}\n"
    return EventSourceResponse(
        generate(),
        media_type="text/event-stream"
    )

@app.get("/health")
async def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=7860)