127.0.0.1 - - [20/Feb/2025:11:05:31 +0000] "GET /...

Créé le : 20 février 2025

Répondu en utilisant O3 Mini High par Chat01

Question

127.0.0.1 - - [20/Feb/2025:11:05:31 +0000] "GET /models HTTP/1.1" 200 192 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
127.0.0.1 - - [20/Feb/2025:11:05:33 +0000] "OPTIONS /chat HTTP/1.1" 200 0 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
INFO:httpx:HTTP Request: POST https://openrouter.ai/api/v1/chat/completions/chat/completions "HTTP/1.1 404 Not Found"
ERROR:app:AI server error: Error code: 404 - {'error': {'message': 'Not Found', 'code': 404}}
Traceback (most recent call last):
File "/opt/render/project/src/app.py", line 410, in stream_response
for chunk in agent_executor.stream({
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 1773, in stream

Entering new None chain...
127.0.0.1 - - [20/Feb/2025:11:05:34 +0000] "POST /chat HTTP/1.1" 200 123 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
for step in iterator:
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent_iterator.py", line 199, in iter
for chunk in self.agent_executor._iter_next_step(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 1358, in _iter_next_step
output = self._action_agent.plan(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 465, in plan
for chunk in self.runnable.stream(inputs, config={"callbacks": callbacks}):
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3409, in stream
yield from self.transform(iter([input]), config, **kwargs)
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3396, in transform
yield from self._transform_stream_with_config(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 2196, in _transform_stream_with_config
chunk: Output = context.run(next, iterator) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3359, in _transform
yield from final_pipeline
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 1414, in transform
for ichunk in input:
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 5567, in transform
yield from self.bound.transform(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 1432, in transform
yield from self.stream(final, config, **kwargs)
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 415, in stream
for chunk in self._stream(messages, stop=stop, **kwargs):
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_openai/chat_models/base.py", line 715, in _stream
response = self.client.create(**payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_utils/_utils.py", line 279, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/resources/chat/completions/completions.py", line 879, in create
return self._post(
^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 1290, in post
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 967, in request
return self._request(
^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 1071, in _request
raise self._make_status_error_from_response(err.response) from None
openai.NotFoundError: Error code: 404 - {'error': {'message': 'Not Found', 'code': 404}}
127.0.0.1 - - [20/Feb/2025:11:05:37 +0000] "GET /models HTTP/1.1" 200 192 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
127.0.0.1 - - [20/Feb/2025:11:05:41 +0000] "OPTIONS /chat HTTP/1.1" 200 0 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
INFO:httpx:HTTP Request: POST https://openrouter.ai/api/v1/chat/completions/chat/completions "HTTP/1.1 404 Not Found"
ERROR:app:AI server error: Error code: 404 - {'error': {'message': 'Not Found', 'code': 404}}
Traceback (most recent call last):
File "/opt/render/project/src/app.py", line 410, in stream_response
for chunk in agent_executor.stream({
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 1773, in stream
for step in iterator:
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent_iterator.py", line 199, in iter
for chunk in self.agent_executor._iter_next_step(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 1358, in _iter_next_step
output = self._action_agent.plan(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain/agents/agent.py", line 465, in plan
for chunk in self.runnable.stream(inputs, config={"callbacks": callbacks}):
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3409, in stream
yield from self.transform(iter([input]), config, **kwargs)
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3396, in transform
yield from self._transform_stream_with_config(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 2196, in _transform_stream_with_config
chunk: Output = context.run(next, iterator) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 3359, in _transform
yield from final_pipeline
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 1414, in transform
for ichunk in input:
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 5567, in transform
yield from self.bound.transform(
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/runnables/base.py", line 1432, in transform
yield from self.stream(final, config, **kwargs)
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_core/language_models/chat_models.py", line 415, in stream
for chunk in self._stream(messages, stop=stop, **kwargs):
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/langchain_openai/chat_models/base.py", line 715, in _stream
response = self.client.create(**payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_utils/_utils.py", line 279, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/resources/chat/completions/completions.py", line 879, in create
return self._post(
^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 1290, in post
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 967, in request
return self._request(
^^^^^^^^^^^^^^
File "/opt/render/project/src/.venv/lib/python3.11/site-packages/openai/_base_client.py", line 1071, in _request
raise self._make_status_error_from_response(err.response) from None
openai.NotFoundError: Error code: 404 - {'error': {'message': 'Not Found', 'code': 404}}
Entering new None chain...
127.0.0.1 - - [20/Feb/2025:11:05:43 +0000] "POST /chat HTTP/1.1" 200 123 "https://pl1nkochat-1.onrender.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"

from gevent import monkey
monkey.patch_all()

import os
import re
import json
import logging
from typing import Any, Dict, List, Optional, Tuple
import time
from tqdm import tqdm

from flask import Flask, request, Response, jsonify, stream_with_context
from flask_cors import CORS

--- LangChain Imports ---

from langchain.agents import AgentExecutor, create_react_agent
from langchain_core.prompts import PromptTemplate
from langchain_core.tools import Tool
from langchain_openai import ChatOpenAI
from langchain_experimental.tools import PythonREPLTool # Correct import

--- End LangChain Imports ---

from e2b import Sandbox # Keep for creating the sandbox

from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type,
)

import requests
from duckduckgo_search import DDGS
from bs4 import BeautifulSoup
from urllib.parse import urlparse
from werkzeug.utils import secure_filename
import PyPDF2
from PIL import Image
import pytesseract

--- Configuration ---

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(name)

app = Flask(name)
CORS(app) # Enables CORS for all routes

Environment variable-based configuration (BEST PRACTICE)

UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER", "uploads") # Default to 'uploads' if not set
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
#Make sure you set these environment variables!
GROQ_API_KEY = os.environ.get("GROQ_API_KEY") #you can comment those two if you are not using them
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
E2B_API_KEY = os.environ.get("E2B_API_KEY") # E2B API Key is required
PORT = int(os.environ.get("PORT", 5000)) # Default to 5000 if PORT is not set
FLASK_ENV = os.environ.get("FLASK_ENV", "production") # Default to production

#No longer needed
#PISTON_API_URL = "https://emkc.org/api/v2/piston/execute"

WEB_SEARCH_CONFIG = {
"openrouter_api_key": OPENROUTER_API_KEY, # Your OpenRouter API key
"max_results": 9,
"cache_file": "search_cache.json",
"model": "google/gemini-2.0-pro-exp-02-05:free", # Or any other suitable model on OpenRouter
"timeout": 20, #timeout for the web search requests
"min_quality_score": 0.65, # You can adjust this threshold
}

VALID_MODELS = [
"google/gemini-2.0-pro-exp-02-05:free",
"deepseek/deepseek-r1:free",
"qwen/qwen2.5-vl-72b-instruct:free",
"meta-llama/llama-3.3-70b-instruct:free",
"anthropic/claude-3-opus-20240229"
]

We no longer need to manually extract code blocks

CODE_PATTERN = re.compile(r"python\s*([\s\S]*?)")

CODE_REQUIRED_MARKER = "[CODE_REQUIRED]"

CANVAS_START_MARKER = "[CANVAS_START]" # Keep these for canvas modification
CANVAS_END_MARKER = "[CANVAS_END]"

--- Sandbox Initialization --- (Keep as is, but we don't assign to a global 'sandbox' anymore)

@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=4, max=20),
retry=retry_if_exception_type(Exception),
)
def initialize_sandbox() -> Sandbox: #this function is correct
"""Initialize the E2B sandbox with a 300-second timeout."""
logger.info("Initializing E2B Sandbox with 300-second timeout...")
return Sandbox(api_key=E2B_API_KEY, timeout=300)

#try: # We'll create sandboxes per request now, not globally.

sandbox = initialize_sandbox()

logger.info("E2B Sandbox initialized successfully.")

#except Exception as e:

logger.error(f"Failed to initialize E2B Sandbox after retries: {e}")

raise e

We don't initialize the sandbox here anymore. We do it inside the agent.

--- Web Search and Content Processing --- (Keep as is)

class DataCollector:
"""Handles web search functionality using DuckDuckGo."""

text
@staticmethod def web_search(query: str) -> List[Dict]: """Perform a web search and return a list of result dictionaries.""" with DDGS() as ddgs: results = [r for r in ddgs.text(query, region="us-en", max_results=WEB_SEARCH_CONFIG["max_results"]) if isinstance(r, dict)] #filter to keep only dictionnaries return [ { "url": r["href"], "title": r["title"], "domain": urlparse(r["href"]).netloc.replace("www.", ""), } for r in results #no need of this filter : if urlparse(r["href"]).scheme in ["http", "https"] ]

class ContentProcessor:
"""Processes web content by fetching and cleaning HTML pages."""

text
def __init__(self): self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} def fetch_content(self, url: str) -> str: """Fetch and extract main content from a URL.""" try: response = requests.get( url, headers=self.headers, timeout=WEB_SEARCH_CONFIG["timeout"] ) response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) soup = BeautifulSoup(response.text, "html.parser") for element in soup( ["script", "style", "nav", "footer", "header", "aside", "form", "button", "meta", "link"] ): element.decompose() main_content = soup.find(["main", "article"]) or soup.body return " ".join(main_content.stripped_strings)[:5000] except Exception as e: logger.error(f"Error fetching {url}: {e}") return ""

class AICore: #keep this class, you'll still need a way to query AI for the web search
"""Core AI functionality for querying OpenRouter API."""

text
@staticmethod def query_ai( system_prompt: str, user_content: str, temp: float = 0.1, model: str = WEB_SEARCH_CONFIG["model"] ) -> str: """Query the OpenRouter AI with a system prompt and user content.""" headers = { "Authorization": f"Bearer {WEB_SEARCH_CONFIG['openrouter_api_key']}", "Content-Type": "application/json", "HTTP-Referer": "https://your-app-url", # Replace with your app URL "X-Title": "Flask Search AI", } data = { "model": model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}, ], "temperature": temp, "max_tokens": 2000, } try: response = requests.post( "https://openrouter.ai/api/v1/chat/completions", headers=headers, json=data, timeout=WEB_SEARCH_CONFIG["timeout"], ) response.raise_for_status() return response.json()["choices"][0]["message"]["content"] except Exception as e: logger.error(f"OpenRouter API error: {e}") return f"Error API OpenRouter: {str(e)}"

--- OpenRouter API Wrappers --- (Keep as is. We need the streaming one.)

def open_router_chat_completion_stream(
messages: List[Dict[str, Any]], model: str, temperature: float = 0.0
):
"""Stream chat completions from OpenRouter API."""
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": 2000,
"stream": True,
}
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers=headers,
json=payload,
stream=True, #keep the stream
timeout=WEB_SEARCH_CONFIG["timeout"],
)
response.raise_for_status()
for line in response.iter_lines():
if line:
decoded_line = line.decode("utf-8")
if decoded_line.startswith("data: "):
data_str = decoded_line[len("data: ") :]
try:
data = json.loads(data_str)
yield data #we yield the data to create a generator
except json.JSONDecodeError:
continue

def open_router_chat_completion( #we don't need the non-streaming one.
messages: List[Dict[str, Any]], model: str, temperature: float = 0.0
) -> Dict:
"""Perform a non-streaming chat completion with OpenRouter API."""
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": 2000,
"stream": False,
}
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers=headers,
json=payload,
timeout=WEB_SEARCH_CONFIG["timeout"],
)
response.raise_for_status()
return response.json()

--- Utility Functions --- (Remove run_code_in_sandbox and extract_numeric_result)

def format_sse_event(data: Dict[str, Any]) -> str:
"""Format data as an SSE event string."""
return f"data: {json.dumps(data)}\n\n"

--- Flask Routes ---

@app.route("/models", methods=["GET"])
def get_models() -> Response:
"""Return the list of valid AI models."""
return jsonify({"models": VALID_MODELS})

@app.route("/chat", methods=["POST"])
def chat_endpoint() -> Response:
"""Handle chat requests with streaming, code execution, and optional file context."""
@stream_with_context
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=4, max=60),
retry=retry_if_exception_type(Exception),
)
def stream_response():
data = request.get_json()
if not data:
logger.warning("Invalid chat request: Missing request body.")
yield format_sse_event(
{"type": "error", "content": "Invalid chat request: Missing body."}
)
return

text
messages = data.get("messages", []) model = data.get("model", "google/gemini-2.0-pro-exp-02-05:free").strip() execute_code = data.get("execute_code", True) # Keep this, but it will be used differently web_search_enabled = data.get("web_search", False) file_context = data.get("file_context", "") if not messages or not isinstance(messages, list) or model not in VALID_MODELS: logger.warning("Invalid chat request: messages=%s, model=%s", messages, model) yield format_sse_event( {"type": "error", "content": "Invalid chat request: Bad messages or model."} ) return # Web search integration (Keep this section as is) web_search_results = [] if web_search_enabled: user_query = messages[-1]["content"] yield format_sse_event({"type": "web_search_status", "content": "start"}) yield format_sse_event({"type": "web_search", "query": user_query}) search_processor = ContentProcessor() search_results = DataCollector.web_search(user_query) filtered_results = [] for result in search_results: content = search_processor.fetch_content(result["url"]) if content: filter_prompt = ( "You are an expert in document relevance. " f"Is this content relevant to the query: '{user_query}'? " "Answer 'yes' or 'no' only." ) is_relevant = AICore.query_ai( filter_prompt, content[:2000], temp=0.0, model="google/gemini-2.0-pro-exp-02-05:free" ) if is_relevant.lower().startswith("yes"): filtered_results.append( {"url": result["url"], "domain": result["domain"], "content": content} ) if len(filtered_results) >= WEB_SEARCH_CONFIG["max_results"]: break web_search_results = filtered_results source_urls = [result["url"] for result in web_search_results] yield format_sse_event({"type": "web_sources", "content": source_urls}) yield format_sse_event({"type": "web_search_status", "content": "end"}) # --- LangChain Agent Setup --- # 1. Define Tools tools = [] if web_search_enabled: # Only add if enabled. search_tool = Tool( name="WebSearch", func=lambda query: [{"url": r["url"], "title": r["title"]} for r in DataCollector.web_search(query)], # Use DataCollector directly description="Use this tool to search the web. Input should be a search query string." ) tools.append(search_tool) # Add the E2B code interpreter as a tool code_interpreter_tool = Tool( name="PythonCodeInterpreter", func=lambda code: sandbox.execute_sync(code, timeout=60).stdout, # Execute code directly, get stdout description="Use this tool to execute Python code. Input should be valid Python code as a string. Returns the standard output of the code execution. Do not use to get the file content, use the search_documents tool instead." ) tools.append(code_interpreter_tool) # 2. Define Prompt (Modified for ReAct and Tools) # Use Langchain's prompt template. This is much cleaner. prompt_template = """You are a helpful assistant. Answer the user's questions as best you can, using the tools available to you. If the question involves calculations or data analysis, use the PythonCodeInterpreter. If you need to find information, use the WebSearch tool. {tools} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! {chat_history} Question: {input} {agent_scratchpad}""" # This is important for the ReAct agent! prompt = PromptTemplate.from_template(prompt_template) #add file context if it exists if file_context: prompt = prompt.partial(additional_info=f"\n\n**File Context:**\n{file_context}") else: prompt = prompt.partial(additional_info="") # 3. Create LLM instance (Using Langchain's ChatOpenAI) llm = ChatOpenAI( # Use the Langchain class model=model, base_url="https://openrouter.ai/api/v1/chat/completions", # Correct base URL api_key=OPENROUTER_API_KEY, # Use api_key streaming=True, # Enable streaming temperature=0.0, ) # 4. Create ReAct Agent (This is the core change!) react_agent = create_react_agent(llm, tools, prompt) # 5. Create Agent Executor agent_executor = AgentExecutor( agent=react_agent, tools=tools, verbose=True, # Set to False in production. This is for debugging. handle_parsing_errors=True, # Handles errors return_intermediate_steps=True # Make sure to set this for visualization ) # --- Streaming Response Generation --- try: # Invoke the agent. The input is the last user message. # We also pass the chat history. for chunk in agent_executor.stream({ "input": messages[-1]["content"], "chat_history": [ {"role": m["role"], "content": m["content"]} for m in messages[:-1] # All messages except the last one ], # Pass file context, if any }): # Stream different parts of the response if "intermediate_steps" in chunk: for step in chunk["intermediate_steps"]: # Access each step action = step[0].tool # Get the tool used. Will be "WebSearch" or "PythonCodeInterpreter" action_input = step[0].tool_input # Get the tool input (either the search query, or code) log = step[0].log observation = step[1] # Get the observation (result of the tool) if action == "PythonCodeInterpreter": #we stream code and result separately yield format_sse_event({"type": "code", "content": action_input}) # Send the code to the frontend yield format_sse_event({"type": "code_result", "content": observation}) # Send the code output #for web search tool, results are sent as they are #print each of the variable above to console print("action: " + action) print("action_input: " + str(action_input)) print("observation: " + str(observation)) print("log: " + str(log)) if "output" in chunk: #this is the final result yield format_sse_event({"type": "text", "content": chunk["output"]}) yield format_sse_event({"type": "final", "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}) #important to calculate tokens yield format_sse_event({"type": "done", "is_code_execution": True}) # The agent *might* execute code except Exception as e: error_msg = f"AI server error: {e}" logger.exception(error_msg) yield format_sse_event({"type": "error", "content": error_msg}) return Response(stream_response(), mimetype="text/event-stream")

@app.route("/canvas_modify", methods=["POST"])
def canvas_modify_endpoint() -> Response:
"""Modify canvas content based on user instructions."""
data = request.get_json()
if not data:
logger.warning("Invalid canvas_modify request: No data.")
return jsonify({"error": "Invalid request: Missing body."}), 400
user_message = data.get("message")
canvas_content = data.get("canvas_content")
model = data.get("model", "google/gemini-2.0-pro-exp-02-05:free").strip()
if not user_message or canvas_content is None:
logger.warning("Incomplete request: message=%s, canvas=%s", user_message, canvas_content)
return jsonify({"error": "Invalid request: Missing message or canvas."}), 400

text
canvas_chat_messages = [ { "role": "system", "content": ( "You are in a canvas chat. The user will ask you to modify the following canvas content. " "Provide ONLY the FULLY UPDATED canvas content, framed by `[CANVAS_START]` and `[CANVAS_END]`. " "Do not include text outside these markers except a brief confirmation after. " "Maintain the context of previous modifications." ), }, { "role": "assistant", "content": f"[CANVAS_START]\n{canvas_content}\n[CANVAS_END] Current canvas. How should I modify it?", }, {"role": "user", "content": user_message}, ] try: response = open_router_chat_completion(canvas_chat_messages, model, 0.0) ai_response_text = response.get("choices", [{}])[0].get("message", {}).get("content", "") if CANVAS_START_MARKER in ai_response_text and CANVAS_END_MARKER in ai_response_text: start_idx = ai_response_text.find(CANVAS_START_MARKER) + len(CANVAS_START_MARKER) end_idx = ai_response_text.find(CANVAS_END_MARKER) updated_canvas = ai_response_text[start_idx:end_idx].strip() logger.info("Canvas modified successfully.") return jsonify( {"updated_canvas_content": updated_canvas, "response_message": "Canvas updated."} ) logger.error("Invalid AI response: Missing CANVAS markers.") return ( jsonify( { "error": "Invalid AI response for canvas modification", "full_response": ai_response_text, } ), 500, ) except Exception as e: error_msg = f"Server error (canvas_modify): {e}" logger.exception(error_msg) return jsonify({"error": error_msg}), 500

@app.route("/run", methods=["POST"]) #keep this endpoint
def run_code_endpoint() -> Response:
"""Execute code via the Piston API (for canvas code)."""
data = request.get_json()
if not data:
logger.warning("Invalid run request: No data.")
return jsonify({"error": "Invalid request: Missing body."}), 400
language = data.get("language")
code = data.get("code")
stdin = data.get("stdin", "")
if not language or not code:
logger.warning("Incomplete request: language=%s, code=%s", language, code)
return jsonify({"error": "Invalid request: Language and code required."}), 400

text
payload = {"language": language, "version": "*", "files": [{"content": code}], "stdin": stdin} try: response = requests.post(PISTON_API_URL, json=payload, timeout=30) response.raise_for_status() logger.info("Code executed via Piston successfully.") return jsonify(response.json()) except requests.RequestException as e: error_msg = f"Piston API error: {e}" logger.exception(error_msg) piston_text = response.text if "response" in locals() else "" return ( jsonify( { "error": "Code execution failed via Piston API.", "piston_response": piston_text, "details": error_msg, } ), 500, ) except Exception as e: error_msg = f"Unexpected error in code execution: {e}" logger.exception(error_msg) return jsonify({"error": "Unexpected error.", "details": error_msg}), 500

@app.route("/run", methods=["OPTIONS"])
def run_code_options() -> Response:
"""Handle CORS preflight requests for /run endpoint."""
return jsonify({"message": "OPTIONS requests allowed"}), 200

@app.route("/upload", methods=["POST"])
def upload_file() -> Response:
"""Handle file uploads and extract content for AI context."""
if "file" not in request.files:
logger.warning("Upload request missing 'file' part.")
return jsonify({"error": "No file part"}), 400
files = request.files.getlist("file")
if not files or all(file.filename == "" for file in files):
logger.warning("No files selected for upload.")
return jsonify({"error": "No selected file"}), 400

text
uploaded_files = [] upload_folder = app.config["UPLOAD_FOLDER"] if not os.path.exists(upload_folder): os.makedirs(upload_folder) allowed_extensions = {".txt", ".py", ".md", ".js", ".json", ".pdf", ".png", ".jpg", ".jpeg"} for file in files: if file.filename == "": continue filename = secure_filename(file.filename) file_ext = os.path.splitext(filename)[1].lower() if file_ext not in allowed_extensions: logger.warning("Unsupported file type: %s", filename) return jsonify({"error": f"Unsupported file type: {file_ext}"}), 400 file_path = os.path.join(upload_folder, filename) try: file.save(file_path) file_content = "" if file_ext in {".txt", ".py", ".md", ".js", ".json"}: with open(file_path, "r", encoding="utf-8") as f: file_content = f.read() elif file_ext == ".pdf": try: reader = PyPDF2.PdfReader(file_path) file_content = "\n".join(page.extract_text() for page in reader.pages) except Exception as e: logger.error("PDF extraction failed for %s: %s", filename, e) file_content = f"PDF extraction error: {e}" elif file_ext in {".png", ".jpg", ".jpeg"}: try: img = Image.open(file_path) file_content = pytesseract.image_to_string(img) except Exception as e: logger.error("OCR failed for %s: %s", filename, e) file_content = f"OCR error: {e}" logger.info(f"File {filename} uploaded successfully to {file_path}") uploaded_files.append( {"filename": filename, "file_path": file_path, "content": file_content} ) except Exception as e: logger.exception(f"Upload failed for {filename}: {e}") return jsonify({"error": f"Upload failed: {e}"}), 500 return jsonify({"message": "Files uploaded successfully", "files": uploaded_files}), 200

--- Application Entry Point ---

if name == "main":
if FLASK_ENV == "development":
app.run(debug=True, host="0.0.0.0", port=PORT)

Réflexion

Considering Flask error

It seems the user pasted a mix of server code and logs from a Flask app. The log entry shows a 404 error for the endpoint "https://openrouter.ai/api/v1/chat/completions". This suggests the Flask app is trying to interact with this endpoint, but it's not found. The user might need help debugging this issue. I could focus on the connection to openrouter and verify the endpoint's availability, or look for code issues related to calling that specific route. Let's start there!

Analyzing URL error

The error seems to occur because the URL is forming incorrectly. The base URL in the code is "https://openrouter.ai/api/v1/chat/completions", but the logs show a double /chat/completions, which is causing the 404 error. This likely happens because the LangChain library is appending an extra route. It’s possible that the base URL should be "https://openrouter.ai/api/v1", with the full URL being "https://openrouter.ai/api/v1/chat/completions" when calling for chat completions. I’ll need to double-check the correct base URL for openrouter.

Reasoned for 11 seconds

Réflexion

Debugging 404 error

The user seems to have an error when calling OpenRouter's chat completions endpoint, but hasn't explicitly asked a question. Looking at the logs, the error is a 404 Not Found, which seems to come from the base_url in the ChatOpenAI instance: "https://openrouter.ai/api/v1/chat/completions". The LangChain agent appends "/chat/completions" again, making the final URL "https://openrouter.ai/api/v1/chat/completions/chat/completions" — leading to the 404 error. The fix would be adjusting the base_url to avoid this duplicate path.

Reasoned for 8 seconds

Réponse

The issue appears to be with the way the base URL is configured for the OpenRouter API. In your code you set the base URL to

"https://openrouter.ai/api/v1/chat/completions"

However, LangChain’s ChatOpenAI class (or its underlying client) automatically appends the chat completions path (i.e. “/chat/completions”) to the base URL. This causes the final URL to become

https://openrouter.ai/api/v1/chat/completions/chat/completions

which returns a 404 Not Found error.

To fix this, update your base URL to just the root endpoint:

python
base_url="https://openrouter.ai/api/v1"

With this change, LangChain will correctly append “/chat/completions” so that the final request goes to

https://openrouter.ai/api/v1/chat/completions

This adjustment follows the examples in the OpenRouter Quickstart documentation, which sets the base URL to “https://openrouter.ai/api/v1” before appending the appropriate path for chat completions [openrouter.ai][openrouter.ai].

Make sure your OPENROUTER_API_KEY is also correctly set, and that no additional path segments are being duplicated. This should resolve the 404 error you’re seeing.

Partager cette Q&R