-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/pyq solver #4
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
Changes from 9 commits
f5b545c
d6f1b4e
47aff4c
158adf2
5e42a11
f98cd21
59fd21b
9caf21e
718dcc8
3e75e83
6333577
f5849ed
534c2e0
3f01d52
c1d206e
b105df8
dd75fe4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """add chat sessions and processing progress | ||
|
|
||
| Revision ID: d3dcf6b6fe3f | ||
| Revises: 9f2c76932a93 | ||
| Create Date: 2026-05-17 16:07:40.708352 | ||
|
|
||
| """ | ||
| from typing import Sequence, Union | ||
|
|
||
| from alembic import op | ||
| import sqlalchemy as sa | ||
|
|
||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision: str = 'd3dcf6b6fe3f' | ||
| down_revision: Union[str, Sequence[str], None] = '9f2c76932a93' | ||
| branch_labels: Union[str, Sequence[str], None] = None | ||
| depends_on: Union[str, Sequence[str], None] = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| """Upgrade schema.""" | ||
| # ### commands auto generated by Alembic - please adjust! ### | ||
| op.create_table('chat_sessions', | ||
| sa.Column('id', sa.UUID(), nullable=False), | ||
| sa.Column('user_id', sa.UUID(), nullable=False), | ||
| sa.Column('title', sa.String(), nullable=True), | ||
| sa.Column('created_at', sa.DateTime(), nullable=True), | ||
| sa.Column('updated_at', sa.DateTime(), nullable=True), | ||
| sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), | ||
| sa.PrimaryKeyConstraint('id') | ||
| ) | ||
| op.add_column('questions', sa.Column('session_id', sa.UUID(), nullable=True)) | ||
| op.create_foreign_key(None, 'questions', 'chat_sessions', ['session_id'], ['id'], ondelete='CASCADE') | ||
| op.add_column('resources', sa.Column('processing_progress', sa.Integer(), nullable=True)) | ||
| # ### end Alembic commands ### | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| """Downgrade schema.""" | ||
| # ### commands auto generated by Alembic - please adjust! ### | ||
| op.drop_column('resources', 'processing_progress') | ||
| op.drop_constraint(None, 'questions', type_='foreignkey') | ||
| op.drop_column('questions', 'session_id') | ||
| op.drop_table('chat_sessions') | ||
| # ### end Alembic commands ### | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||
| import httpx | ||||||||||||||||||||||||||||||||||||||||||||||
| from typing import AsyncGenerator | ||||||||||||||||||||||||||||||||||||||||||||||
| from ..config import settings | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| class OpenRouterClient: | ||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self): | ||||||||||||||||||||||||||||||||||||||||||||||
| self.api_key = settings.OPENROUTER_API_KEY | ||||||||||||||||||||||||||||||||||||||||||||||
| self.base_url = "https://openrouter.ai/api/v1/chat/completions" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| async def stream_chat(self, messages: list, model: str = "openrouter/owl-alpha") -> AsyncGenerator[str, None]: | ||||||||||||||||||||||||||||||||||||||||||||||
| headers = { | ||||||||||||||||||||||||||||||||||||||||||||||
| "Authorization": f"Bearer {self.api_key}", | ||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||
| "HTTP-Referer": settings.FRONTEND_URL, | ||||||||||||||||||||||||||||||||||||||||||||||
| "X-Title": "PYQ Solver", | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| payload = { | ||||||||||||||||||||||||||||||||||||||||||||||
| "model": model, | ||||||||||||||||||||||||||||||||||||||||||||||
| "messages": messages, | ||||||||||||||||||||||||||||||||||||||||||||||
| "stream": True | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| async with httpx.AsyncClient(timeout=120.0) as client: | ||||||||||||||||||||||||||||||||||||||||||||||
| async with client.stream("POST", self.base_url, headers=headers, json=payload) as response: | ||||||||||||||||||||||||||||||||||||||||||||||
| if response.status_code != 200: | ||||||||||||||||||||||||||||||||||||||||||||||
| error_text = await response.aread() | ||||||||||||||||||||||||||||||||||||||||||||||
| yield f"Error: {response.status_code} - {error_text.decode()}" | ||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| async for line in response.aiter_lines(): | ||||||||||||||||||||||||||||||||||||||||||||||
| if not line or line == "": | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if line.startswith("data: "): | ||||||||||||||||||||||||||||||||||||||||||||||
| data_str = line[6:] | ||||||||||||||||||||||||||||||||||||||||||||||
| if data_str == "[DONE]": | ||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||
| data = json.loads(data_str) | ||||||||||||||||||||||||||||||||||||||||||||||
| chunk = data['choices'][0]['delta'].get('content', "") | ||||||||||||||||||||||||||||||||||||||||||||||
| if chunk: | ||||||||||||||||||||||||||||||||||||||||||||||
| yield chunk | ||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: git ls-files | grep -E "(client\.py|solver\.py)" | head -20Repository: shubhamxdd/pyq-appl Length of output: 119 🏁 Script executed: wc -l backend/app/llm/client.pyRepository: shubhamxdd/pyq-appl Length of output: 92 🏁 Script executed: cat -n backend/app/llm/client.py | sed -n '30,60p'Repository: shubhamxdd/pyq-appl Length of output: 928 🏁 Script executed: cat -n backend/app/llm/client.py | head -80Repository: shubhamxdd/pyq-appl Length of output: 2237 🏁 Script executed: rg "class.*Client" backend/app/llm/client.py -A 5Repository: shubhamxdd/pyq-appl Length of output: 350 🏁 Script executed: rg "stream_chat" backend/ -B 3 -A 15Repository: shubhamxdd/pyq-appl Length of output: 2865 🏁 Script executed: rg "stream_chat" backend/ -B 2 -A 10Repository: shubhamxdd/pyq-appl Length of output: 1942 🏁 Script executed: fd "test" backend/ -type f | head -20Repository: shubhamxdd/pyq-appl Length of output: 234 🏁 Script executed: find backend -name "*test*" -type f 2>/dev/nullRepository: shubhamxdd/pyq-appl Length of output: 45 🏁 Script executed: cat -n backend/app/routers/solver.py | sed -n '60,120p'Repository: shubhamxdd/pyq-appl Length of output: 2465 🏁 Script executed: rg "def event_generator" backend/app/routers/solver.py -A 40Repository: shubhamxdd/pyq-appl Length of output: 1304 Narrow exception handling to distinguish protocol errors from schema mismatches. The bare - try:
- data = json.loads(data_str)
- chunk = data['choices'][0]['delta'].get('content', "")
- if chunk:
- yield chunk
- except Exception:
- continue
+ try:
+ data = json.loads(data_str)
+ except json.JSONDecodeError:
+ continue
+
+ if "error" in data:
+ raise RuntimeError(f"OpenRouter stream error: {data['error']}")
+
+ try:
+ chunk = data["choices"][0]["delta"].get("content", "")
+ except (KeyError, IndexError, TypeError) as exc:
+ raise RuntimeError(f"Unexpected stream chunk: {data}") from exc
+
+ if chunk:
+ yield chunk📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.12)[error] 45-46: (S112) [warning] 45-45: Do not catch blind exception: (BLE001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| open_router_client = OpenRouterClient() | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| SOLVER_SYSTEM = """ | ||
| You are an expert academic tutor helping a student understand a question from their study materials. | ||
|
|
||
| You are given relevant excerpts from the student's own documents as context. | ||
| Answer the question using ONLY the provided context. | ||
|
|
||
| Rules: | ||
| - Be clear, structured, and student-friendly. | ||
| - Use markdown for formatting: headings (###), bold, and bullet points. | ||
| - If the context does not contain enough information to answer the question, say so honestly. Do not make up information. | ||
| - Provide a concise answer first, followed by a more detailed explanation if helpful. | ||
| - Cite the source filename when referencing specific points. | ||
| - DO NOT add information that is not present in the provided context. | ||
| """ | ||
|
|
||
| SOLVER_USER_TEMPLATE = """ | ||
| Context from student materials: | ||
| {context} | ||
|
|
||
| Question: | ||
| {question} | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from sqlalchemy import Column, String, DateTime, UUID, ForeignKey | ||
| from sqlalchemy.orm import relationship | ||
| from datetime import datetime | ||
| import uuid | ||
| from .base import Base | ||
|
|
||
| class ChatSession(Base): | ||
| __tablename__ = "chat_sessions" | ||
|
|
||
| id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) | ||
| user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False) | ||
| title = Column(String, default="New Chat") | ||
| created_at = Column(DateTime, default=datetime.utcnow) | ||
| updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | ||
|
|
||
| user = relationship("User", back_populates="chat_sessions") | ||
| questions = relationship("Question", back_populates="session", cascade="all, delete-orphan") | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
Uh oh!
There was an error while loading. Please reload this page.