|
1 | | -import httpx |
2 | 1 | import json |
3 | | -from typing import Optional, Dict, Any |
4 | | -import asyncio |
| 2 | +from typing import Any, Dict, Optional |
| 3 | + |
| 4 | +import httpx |
| 5 | + |
5 | 6 |
|
6 | 7 | class OllamaClient: |
7 | 8 | """Client for interacting with Ollama local LLM server""" |
8 | | - |
| 9 | + |
9 | 10 | def __init__(self, base_url: str = "http://localhost:11434"): |
10 | 11 | self.base_url = base_url |
11 | 12 | self.model_name = "Instructify" |
12 | | - |
| 13 | + |
13 | 14 | async def is_available(self) -> bool: |
14 | 15 | """Check if Ollama server is available""" |
15 | 16 | try: |
16 | 17 | async with httpx.AsyncClient() as client: |
17 | 18 | response = await client.get(f"{self.base_url}/api/tags") |
18 | 19 | return response.status_code == 200 |
19 | | - except: |
| 20 | + except Exception: |
20 | 21 | return False |
21 | | - |
| 22 | + |
22 | 23 | async def generate_text(self, prompt: str, context: str = "") -> Optional[str]: |
23 | 24 | """Generate text using Gemma 270M model""" |
24 | 25 | try: |
25 | 26 | full_prompt = f"{context}\n\n{prompt}" if context else prompt |
26 | | - |
| 27 | + |
27 | 28 | async with httpx.AsyncClient(timeout=30.0) as client: |
28 | 29 | response = await client.post( |
29 | 30 | f"{self.base_url}/api/generate", |
30 | 31 | json={ |
31 | 32 | "model": self.model_name, |
32 | 33 | "prompt": full_prompt, |
33 | | - "stream": False |
34 | | - } |
| 34 | + "stream": False, |
| 35 | + }, |
35 | 36 | ) |
36 | | - |
| 37 | + |
37 | 38 | if response.status_code == 200: |
38 | 39 | result = response.json() |
39 | | - return result.get("response", "").strip() |
| 40 | + response_text = result.get("response", "") |
| 41 | + return str(response_text).strip() if response_text else None |
40 | 42 | else: |
41 | 43 | print(f"Ollama error: {response.status_code}") |
42 | 44 | return None |
43 | | - |
| 45 | + |
44 | 46 | except Exception as e: |
45 | 47 | print(f"Ollama client error: {e}") |
46 | 48 | return None |
47 | | - |
| 49 | + |
48 | 50 | async def generate_notes(self, transcription: str) -> Optional[str]: |
49 | 51 | """Generate structured notes from class transcription""" |
50 | | - prompt = """Please create structured class notes from the following transcription. |
51 | | - Format the response as markdown with the following sections: |
52 | | - |
| 52 | + prompt = """Please create structured class notes from the transcription. |
| 53 | + Format the response as markdown with these sections: |
| 54 | +
|
53 | 55 | ## Summary |
54 | 56 | [Brief overview of the class] |
55 | | - |
| 57 | +
|
56 | 58 | ## Key Topics |
57 | 59 | [Main topics covered with bullet points] |
58 | | - |
| 60 | +
|
59 | 61 | ## Important Definitions |
60 | 62 | [Key terms and their definitions] |
61 | | - |
| 63 | +
|
62 | 64 | ## Action Items |
63 | 65 | [Any assignments or tasks mentioned] |
64 | | - |
| 66 | +
|
65 | 67 | ## Questions for Review |
66 | 68 | [3-5 review questions based on the content] |
67 | | - |
| 69 | +
|
68 | 70 | Transcription: |
69 | 71 | """ |
70 | | - |
| 72 | + |
71 | 73 | return await self.generate_text(prompt, transcription) |
72 | | - |
| 74 | + |
73 | 75 | async def classify_doubt(self, message: str, context: str = "") -> Dict[str, Any]: |
74 | 76 | """Classify if a message is a genuine doubt that should go to teacher""" |
75 | | - prompt = f"""Analyze this student message and determine if it's a genuine academic doubt that should be forwarded to the teacher. |
76 | | - |
| 77 | + prompt = f"""Analyze this student message and determine if it's a genuine |
| 78 | + academic doubt that should be forwarded to the teacher. |
| 79 | +
|
77 | 80 | Context: {context} |
78 | | - |
| 81 | +
|
79 | 82 | Student message: "{message}" |
80 | | - |
| 83 | +
|
81 | 84 | Respond in JSON format with: |
82 | 85 | {{ |
83 | 86 | "is_genuine_doubt": true/false, |
84 | 87 | "confidence": 0.0-1.0, |
85 | 88 | "reason": "brief explanation", |
86 | 89 | "category": "academic_question|personal_ai_query|spam|off_topic" |
87 | 90 | }} |
88 | | - |
| 91 | +
|
89 | 92 | Only classify as "genuine_doubt" if it's: |
90 | 93 | - A specific academic question about the subject |
91 | 94 | - Request for clarification on course material |
92 | 95 | - Question about assignments or course logistics |
93 | | - |
| 96 | +
|
94 | 97 | Do NOT classify as genuine_doubt if it's: |
95 | 98 | - General knowledge questions |
96 | 99 | - Personal queries to AI |
97 | 100 | - Off-topic discussions |
98 | 101 | - Spam or inappropriate content |
99 | 102 | """ |
100 | | - |
| 103 | + |
101 | 104 | try: |
102 | 105 | response = await self.generate_text(prompt) |
103 | 106 | if response: |
104 | 107 | # Try to extract JSON from response |
105 | 108 | import re |
106 | | - json_match = re.search(r'\{.*\}', response, re.DOTALL) |
| 109 | + |
| 110 | + json_match = re.search(r"\{.*\}", response, re.DOTALL) |
107 | 111 | if json_match: |
108 | | - return json.loads(json_match.group()) |
109 | | - |
| 112 | + parsed_json = json.loads(json_match.group()) |
| 113 | + if isinstance(parsed_json, dict): |
| 114 | + return parsed_json |
| 115 | + return {} |
| 116 | + |
110 | 117 | # Fallback response |
111 | 118 | return { |
112 | 119 | "is_genuine_doubt": False, |
113 | 120 | "confidence": 0.5, |
114 | 121 | "reason": "Could not analyze message", |
115 | | - "category": "unknown" |
| 122 | + "category": "unknown", |
116 | 123 | } |
117 | | - |
| 124 | + |
118 | 125 | except Exception as e: |
119 | 126 | print(f"Error classifying doubt: {e}") |
120 | 127 | return { |
121 | 128 | "is_genuine_doubt": False, |
122 | 129 | "confidence": 0.0, |
123 | 130 | "reason": "Analysis failed", |
124 | | - "category": "error" |
| 131 | + "category": "error", |
125 | 132 | } |
126 | | - |
127 | | - async def answer_student_query(self, query: str, lecture_context: str = "") -> Optional[str]: |
| 133 | + |
| 134 | + async def answer_student_query( |
| 135 | + self, query: str, lecture_context: str = "" |
| 136 | + ) -> Optional[str]: |
128 | 137 | """Answer student query using lecture context""" |
129 | | - prompt = f"""You are an AI teaching assistant. Answer the student's question based on the lecture context provided. |
130 | | - |
| 138 | + prompt = f"""You are an AI teaching assistant. Answer the student's |
| 139 | + question based on the lecture context provided. |
| 140 | +
|
131 | 141 | Lecture Context: |
132 | 142 | {lecture_context} |
133 | | - |
| 143 | +
|
134 | 144 | Student Question: {query} |
135 | | - |
136 | | - Provide a helpful, educational response. If the question is outside the lecture scope, |
137 | | - provide general guidance and suggest asking the teacher for more specific help. |
| 145 | +
|
| 146 | + Provide a helpful, educational response. If the question is outside |
| 147 | + the lecture scope, provide general guidance and suggest asking the |
| 148 | + teacher for more specific help. |
138 | 149 | """ |
139 | | - |
| 150 | + |
140 | 151 | return await self.generate_text(prompt) |
0 commit comments