Repository navigation
Expand file tree
/
Copy pathmulticoder.py
More file actions
278 lines (222 loc) · 9.11 KB
/
Copy pathmulticoder.py
File metadata and controls
278 lines (222 loc) · 9.11 KB
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
"""MultiCoder CLI module.
This module provides the command-line interface for interacting
with the MultiCoder system.
"""
import argparse
import asyncio
import json
import logging
import os
import sys
import uuid
from typing import Any, Dict, Optional
# Ajouter le répertoire parent au chemin de recherche de Python
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
import redis.asyncio as redis
class MultiCoderCLI:
"""Command-line interface for MultiCoder.
Provides a user-friendly CLI for submitting code generation requests
and displaying results.
Attributes:
redis_url: Redis connection string.
request_channel: Channel to publish requests.
response_channel: Channel to subscribe for responses.
logger: Configured logger instance.
"""
def __init__(
self,
redis_url: str = "redis://localhost:6379/0",
log_level: int = logging.INFO
) -> None:
"""Initialize the CLI client.
Args:
redis_url: Redis connection string.
log_level: Logging level.
"""
self.redis_url = redis_url
self.request_channel = "multicoder:requests"
self.response_channel = "multicoder:responses"
# Setup logging
self.logger = logging.getLogger("multicoder.cli")
self.logger.setLevel(log_level)
if not self.logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
# Redis connection will be initialized in the run method
self.redis_client = None
self.pubsub = None
async def send_request(self, prompt: str) -> str:
"""Send a code generation request and wait for response.
Args:
prompt: Natural language prompt describing the code to generate.
Returns:
Request ID for tracking.
"""
request_id = str(uuid.uuid4())
# Create request message
message = {
"sender": "cli",
"action": "process",
"payload": {
"request_id": request_id,
"prompt": prompt
}
}
# Publish request
message_json = json.dumps(message)
await self.redis_client.publish(self.request_channel, message_json)
self.logger.info(f"Sent request {request_id}: {prompt}")
return request_id
async def wait_for_response(self, request_id: str, timeout: int = 60) -> Optional[Dict[str, Any]]:
"""Wait for a response to a specific request.
Args:
request_id: The ID of the request to wait for.
timeout: Maximum time to wait in seconds.
Returns:
Response data or None if timed out.
"""
self.logger.info(f"Waiting for response to request {request_id}")
# Subscribe to responses
await self.pubsub.subscribe(self.response_channel)
# Set timeout
end_time = asyncio.get_event_loop().time() + timeout
try:
while asyncio.get_event_loop().time() < end_time:
message = await self.pubsub.get_message(timeout=1)
if message and message["type"] == "message":
data = json.loads(message["data"].decode("utf-8"))
response_id = data.get("payload", {}).get("request_id")
if response_id == request_id:
self.logger.info(f"Received response for request {request_id}")
return data.get("payload", {})
# Small delay to prevent CPU spinning
await asyncio.sleep(0.1)
self.logger.warning(f"Timed out waiting for response to {request_id}")
return None
finally:
# Unsubscribe from responses
await self.pubsub.unsubscribe(self.response_channel)
async def process_prompt(self, prompt: str, timeout: int = 60) -> None:
"""Process a prompt and display the results.
Args:
prompt: The prompt to process.
timeout: Maximum time to wait for response.
"""
try:
# Send request
request_id = await self.send_request(prompt)
# Wait for and display progress
print(f"Processing request: {prompt}")
print("Waiting for response...", end="", flush=True)
# Wait for response
response = await self.wait_for_response(request_id, timeout)
# Display results
print("\r" + " " * 50 + "\r", end="") # Clear waiting message
if response:
result = response.get("result", {})
status = response.get("status", "unknown")
if status == "completed":
print("\n✅ Code generation successful!\n")
code = result.get("code", "")
print("-------- Generated Code --------")
print(code)
print("--------------------------------")
else:
print("\n❌ Code generation failed!\n")
issues = result.get("issues", [])
if issues:
print("Issues:")
for issue in issues:
print(f" - {issue}")
code = result.get("code", "")
if code:
print("\nPartial code:")
print("-------- Generated Code --------")
print(code)
print("--------------------------------")
else:
print("\n⚠️ Request timed out. The system might be busy or experiencing issues.")
except Exception as e:
self.logger.error(f"Error processing prompt: {e}", exc_info=True)
print(f"\n❌ Error: {e}")
async def interactive_mode(self) -> None:
"""Run the CLI in interactive mode."""
print("\n🤖 MultiCoder Interactive Mode")
print("Type 'exit' or 'quit' to exit\n")
while True:
try:
prompt = input("🔍 Enter your code request: ")
if prompt.lower() in ["exit", "quit"]:
break
if prompt:
await self.process_prompt(prompt)
print("\n")
except KeyboardInterrupt:
print("\nExiting...")
break
except Exception as e:
self.logger.error(f"Error in interactive mode: {e}", exc_info=True)
print(f"❌ Error: {e}")
async def run(self, prompt: Optional[str] = None) -> None:
"""Run the CLI client.
Args:
prompt: Optional prompt to process in non-interactive mode.
"""
try:
# Initialize Redis connection
self.redis_client = redis.Redis.from_url(self.redis_url)
self.pubsub = self.redis_client.pubsub()
if prompt:
# Process a single prompt
await self.process_prompt(prompt)
else:
# Interactive mode
await self.interactive_mode()
finally:
# Clean up
if self.pubsub:
await self.pubsub.aclose()
if self.redis_client:
await self.redis_client.aclose()
def parse_args() -> argparse.Namespace:
"""Parse command line arguments.
Returns:
Parsed arguments.
"""
parser = argparse.ArgumentParser(description="MultiCoder: AI-powered code generation")
parser.add_argument(
"--prompt", "-p",
type=str,
help="Prompt for code generation (non-interactive mode)"
)
parser.add_argument(
"--redis-url",
type=str,
default="redis://localhost:6379/0",
help="Redis connection URL (default: redis://localhost:6379/0)"
)
parser.add_argument(
"--log-level",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO",
help="Set logging level (default: INFO)"
)
return parser.parse_args()
async def main() -> None:
"""Main entry point for the CLI."""
args = parse_args()
# Configure logging
log_level = getattr(logging, args.log_level)
logging.basicConfig(
level=log_level,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# Run CLI
cli = MultiCoderCLI(redis_url=args.redis_url, log_level=log_level)
await cli.run(args.prompt)
if __name__ == "__main__":
asyncio.run(main())