kimhyunwoo commited on
Commit
741208e
·
verified ·
1 Parent(s): 40d7cda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +243 -170
app.py CHANGED
@@ -4,194 +4,267 @@ import subprocess
4
  import requests
5
  import json
6
  import re
 
7
  import time
8
 
9
- # --- 1. 환경 설정 및 강화된 API 호출 함수 ---
 
10
  MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY")
11
  CODESTRAL_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"
 
12
 
13
- def call_mistral_api(system_prompt: str, user_prompt: str):
14
- """향상된 Mistral API 호출 함수"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  if not MISTRAL_API_KEY:
16
- raise gr.Error("MISTRAL_API_KEY is not set. Please add it to your Space Secrets.")
17
-
18
  headers = {"Authorization": f"Bearer {MISTRAL_API_KEY}", "Content-Type": "application/json"}
19
- messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}]
20
- data = {"model": "codestral-latest", "messages": messages}
21
-
22
  try:
23
- response = requests.post(CODESTRAL_ENDPOINT, headers=headers, data=json.dumps(data), timeout=60)
24
  response.raise_for_status()
25
  return response.json()["choices"][0]["message"]["content"]
26
  except requests.exceptions.RequestException as e:
27
- raise gr.Error(f"API Call Error: {e}")
28
-
29
- # --- 2. 백엔드 핵심 기능 (안정성 강화) ---
30
- def parse_code_from_response(response_text: str) -> str | None:
31
- """C 코드 블록을 파싱하는 안정적인 함수"""
32
- match = re.search(r'```(?:c)?\n(.*?)\n```', response_text, re.DOTALL)
33
- if match: return match.group(1).strip()
34
- # 비상시 순수 코드 응답 처리
35
- if response_text.strip().startswith("#include") and response_text.strip().endswith("}"):
36
- return response_text.strip()
37
- return None
38
-
39
- def generate_c_code(description: str) -> str:
40
- system_prompt = "You are an expert C code generator..." # (이전과 동일)
41
- user_prompt = f"Generate C code for: '{description}'"
42
- response = call_mistral_api(system_prompt, user_prompt)
43
- return parse_code_from_response(response) or f"// Failed to parse code from response:\n{response}"
44
-
45
- def compile_and_run_c_code(code: str) -> str:
46
- """컴파일 및 실행 함수"""
47
- if not code.strip(): return "--- SYSTEM ERROR ---\nCode is empty."
48
- with open("main.c", "w", encoding='utf-8') as f: f.write(code)
49
- compile_proc = subprocess.run(["gcc", "main.c", "-o", "main.out", "-lm", "-w"], capture_output=True, text=True, timeout=15)
50
- if compile_proc.returncode != 0: return f"--- COMPILATION FAILED ---\n{compile_proc.stderr}"
51
- run_proc = subprocess.run(["./main.out"], capture_output=True, text=True, timeout=15)
52
- if run_proc.returncode != 0: return f"--- RUNTIME ERROR ---\n{run_proc.stderr}"
53
- output = run_proc.stdout
54
- return f"--- EXECUTION SUCCEEDED ---\n{output}" if output.strip() else "--- EXECUTION SUCCEEDED ---\n(No output)"
55
-
56
- def analyze_and_refactor_code(code: str, instruction: str) -> str:
57
- system_prompt = "You are a world-class C code reviewer..." # (이전과 동일)
58
- user_prompt = f"Instruction: '{instruction}'\n\nC Code:\n```c\n{code}\n```"
59
- return call_mistral_api(system_prompt, user_prompt)
60
-
61
- # ⭐️ 새로운 기능: 외부 MCP 툴을 사용하는 클라이언트 함수
62
- def call_external_mcp_tool(tool_url: str, code: str, instruction: str) -> str:
63
- """다른 Gradio Space MCP 툴을 API로 호출하는 함수"""
64
- # Gradio 클라이언트를 사용하여 외부 API 호출 (gradio_client 설치 필요)
65
- from gradio_client import Client
66
  try:
67
- client = Client(tool_url)
68
- # 외부 툴의 API 엔드포인트와 파라미터 이름에 맞춰야 함
69
- # 예시: predict(code_to_analyze=code, user_instruction=instruction)
70
- result = client.predict(code, instruction, api_name="/predict") # api_name은 외부 툴에 따라 다름
71
- return f"--- EXTERNAL TOOL SUCCEEDED ---\n{result}"
72
- except Exception as e:
73
- return f"--- EXTERNAL TOOL FAILED ---\nCould not call tool at {tool_url}. Error: {e}"
74
-
75
- # --- 3. 1등을 위한 지능형 에이전트 로직 (최종 버전) ---
76
- def ultimate_agent_ide(initial_code: str, full_instruction: str):
77
- tasks = [task.strip() for task in re.split(r'\s+and then\s+|\s+and\s+|,\s*then\s*|\s*그리고\s+|\s*후에\s*', full_instruction, flags=re.IGNORECASE) if task.strip()]
78
- current_code = initial_code
79
- log = []
80
-
81
- # Step 1: 계획 수립
82
- log.append("### 📝 Agent's Plan")
83
- plan = "".join([f"\n{i+1}. {task}" for i, task in enumerate(tasks)])
84
- log.append(plan)
85
- yield current_code, "\n".join(log)
86
- time.sleep(1)
87
-
88
- # Step 2: 계획 실행
89
- for i, task in enumerate(tasks):
90
- log.append(f"\n<details><summary><b>▶ Step {i+1}: {task}</b></summary>\n")
91
- yield current_code, "\n".join(log)
92
- time.sleep(0.5)
93
-
94
- lower_task = task.lower()
95
-
96
- # ⭐️ 에이전트의 '생각'과 '행동'
97
- if "generate" in lower_task or "create" in lower_task or "만들어" in lower_task:
98
- log.append("🧠 **Thought:** The user wants new code. Using `generate_c_code` tool.")
99
- yield current_code, "\n".join(log)
100
- new_code = generate_c_code(task)
101
- if new_code and not new_code.startswith("//"):
102
- current_code = new_code
103
- log.append("\n✅ **Action Result:** Code generated and updated in the editor.")
104
  else:
105
- log.append(f"\n❌ **Action Result:** Generation failed. {new_code}")
106
-
107
- elif "compile" in lower_task or "run" in lower_task or "실행" in lower_task:
108
- log.append("🧠 **Thought:** The user wants to compile and run. Using `compile_and_run_c_code` tool.")
109
- yield current_code, "\n".join(log)
110
- result = compile_and_run_c_code(current_code)
111
- log.append(f"\n💻 **Action Result:**\n```\n{result}\n```")
112
-
113
- # ⭐️⭐️ 자가 수정 (SELF-CORRECTION) 로직 ⭐️⭐️
114
- if "COMPILATION FAILED" in result:
115
- log.append("\n\n🧠 **Thought:** Compilation failed. I will try to fix the code myself.")
116
- yield current_code, "\n".join(log)
117
- time.sleep(1)
118
-
119
- error_message = result.split("--- COMPILATION FAILED ---")[1]
120
- fix_instruction = f"The following C code failed to compile with this error:\n\n**Error:**\n```\n{error_message}\n```\n\nPlease fix the code so it compiles successfully. Provide only the complete, corrected C code."
121
-
122
- log.append("\n🛠️ **Self-Correction:** Asking the LLM to fix the error...")
123
- yield current_code, "\n".join(log)
124
-
125
- fixed_code_response = analyze_and_refactor_code(current_code, fix_instruction)
126
- fixed_code = parse_code_from_response(fixed_code_response)
127
-
128
- if fixed_code:
129
- current_code = fixed_code
130
- log.append("\n✅ **Self-Correction Result:** A potential fix has been applied to the code editor. Please try compiling again.")
131
- else:
132
- log.append("\n❌ **Self-Correction Result:** Failed to automatically fix the code.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- # ⭐️⭐️ 외부 MCP 사용 예시 ⭐️⭐️
135
- elif "security" in lower_task or "보안" in lower_task:
136
- log.append("🧠 **Thought:** The user wants a security analysis. I will use an external MCP tool for this.")
137
- yield current_code, "\n".join(log)
138
- # 이 URL은 예시이며, 실제 작동하는 보안 분석 MCP Space가 있다면 그 주소를 넣어야 합니다.
139
- # 해커톤 제출 시, 직접 간단한 보안분석 툴을 하나 더 만들거나, 다른 참가자의 툴을 사용하는 모습을 보여주면 최고입니다.
140
- external_tool_url = "user-provided-security-tool-space-url"
141
- log.append(f"\n🔌 **Action:** Calling external tool at `{external_tool_url}`...")
142
- yield current_code, "\n".join(log)
143
-
144
- # 실제로는 instruction에서 URL을 파싱해야 하지만, 여기서는 하드코딩으로 예시를 보여줍니다.
145
- security_result = call_external_mcp_tool(external_tool_url, current_code, task)
146
- log.append(f"\n🛡️ **Action Result:**\n```\n{security_result}\n```")
147
-
148
- else:
149
- log.append("🧠 **Thought:** The user wants to analyze or refactor. Using `analyze_and_refactor_code` tool.")
150
- yield current_code, "\n".join(log)
151
- analysis_result = analyze_and_refactor_code(current_code, task)
152
- refactored_code = parse_code_from_response(analysis_result)
153
- if refactored_code:
154
- current_code = refactored_code
155
- log.append("\n✅ **Action Result:** Code refactored and updated in the editor.")
156
- log.append(f"\n🔎 **Analysis Result:**\n{analysis_result}")
157
 
158
- log.append("</details>")
159
- yield current_code, "\n".join(log)
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- log.append("\n\n--- All tasks complete. ---")
162
- yield current_code, "\n".join(log)
 
163
 
164
- # --- 4. 통합된 Gradio UI (출력 컴포넌트를 Markdown으로 변경) ---
165
  with gr.Blocks(theme=gr.themes.Monochrome(primary_hue="indigo", secondary_hue="blue"), css="footer {visibility: hidden}") as demo:
166
- gr.Markdown("# 🏆 The Ultimate C-Codestral IDE Agent 🏆")
167
-
168
- with gr.Tabs():
169
- with gr.TabItem("👨‍💻 IDE Agent"):
170
- with gr.Row(equal_height=True):
171
- with gr.Column(scale=2):
172
- code_editor = gr.Code(label="C Code Editor", language="c", lines=28, interactive=True, value='#include <stdio.h>\n\nint main() {\n printf("Hello, World!\\n");\n return 0;\n}')
173
-
174
- with gr.Column(scale=1):
175
- instruction_box = gr.Textbox(label="Instruction", placeholder="e.g., 'Refactor this code, then compile it, then check security'", lines=4)
176
- execute_btn = gr.Button("Execute", variant="primary", size="lg")
177
- # 출력을 Markdown으로 변경하여 풍부한 UI를 제공
178
- output_box = gr.Markdown(label="Console / Output")
179
-
180
- execute_btn.click(
181
- fn=ultimate_agent_ide,
182
- inputs=[code_editor, instruction_box],
183
- outputs=[code_editor, output_box]
184
- )
185
-
186
- with gr.TabItem("🛠️ MCP Tools API"):
187
- # MCP 탭은 이전과 동일하게 유지
188
- gr.Markdown("## Available MCP Tools for other Agents\nThese APIs are the building blocks of our IDE agent.")
189
- with gr.Accordion("Tool: Generate C Code", open=False):
190
- gr.Interface(fn=generate_c_code, inputs="text", outputs=gr.Code(language="c", label="Generated C Code"))
191
- with gr.Accordion("Tool: Compile & Run C Code", open=False):
192
- gr.Interface(fn=compile_and_run_c_code, inputs=gr.Code(language="c"), outputs=gr.Textbox(label="Output"))
193
- with gr.Accordion("Tool: Analyze & Refactor C Code", open=False):
194
- gr.Interface(fn=analyze_and_refactor_code, inputs=[gr.Code(language="c", label="Code to Analyze"), gr.Textbox(label="Instruction")], outputs=gr.Markdown())
195
 
196
  if __name__ == "__main__":
197
  demo.queue().launch()
 
4
  import requests
5
  import json
6
  import re
7
+ import shlex
8
  import time
9
 
10
+ # --- 1. 환경 설정 및 API ---
11
+ # Hugging Face Space의 Secrets에 'MISTRAL_API_KEY'를 반드시 추가해야 합니다.
12
  MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY")
13
  CODESTRAL_ENDPOINT = "https://codestral.mistral.ai/v1/chat/completions"
14
+ MAX_AGENT_TURNS = 15 # 무한 루프 방지를 위한 최대 실행 횟수
15
 
16
+ # --- 2. 시스템 프롬프트: 에이전트의 두뇌 ---
17
+ # 프롬프트는 에이전트의 행동 원칙, 사고 방식, 출력 형식을 정의하는 가장 중요한 부분입니다.
18
+ SYSTEM_PROMPT = """
19
+ You are a 'Recursive Autonomous Swarm Intelligence' (RASI), a hyper-capable AI agent operating a Linux terminal. Your primary directive is to achieve the user's goal by planning, executing commands, observing results, and correcting your own mistakes.
20
+
21
+ **YOUR OPERATING PROCEDURE:**
22
+ 1. **THINK:** Analyze the user's request, the conversation history, and the last command's output. Formulate a step-by-step thought process.
23
+ 2. **PLAN:** Based on your thoughts, create or update a list of discrete terminal commands to execute. The plan should be a list of strings.
24
+ 3. **ACT:** Select the *single* next command from your plan to execute.
25
+ 4. **RESPOND:** You MUST respond ONLY with a JSON object. No other text or explanation. The JSON format is:
26
+ ```json
27
+ {
28
+ "thought": "Your detailed, step-by-step reasoning. Explain why you are choosing the next command based on previous results and the overall goal.",
29
+ "plan": [
30
+ "updated list of commands to execute",
31
+ "the first item is what you will do next",
32
+ "..."
33
+ ],
34
+ "command": "The single, exact command to execute NOW. This must be the first item from your 'plan'. Use 'done' when the entire task is complete.",
35
+ "user_summary": "A brief, human-readable summary of your action for the user interface."
36
+ }
37
+ ```
38
+
39
+ **SELF-CORRECTION DIRECTIVE:**
40
+ - If a command results in an error (`stderr`), your next `thought` MUST be to analyze the error.
41
+ - Create a new plan to fix the error (e.g., if 'file not found', use 'ls' to check; if 'directory exists', use 'cd' instead of 'mkdir').
42
+ - Insert the fix-it steps at the beginning of your plan.
43
+
44
+ **RECURSIVE SELF-MODIFICATION (DANGER - USE WITH EXTREME CAUTION):**
45
+ - You have the ability to read and write your own source code (`app.py`).
46
+ - This is a last resort for when you are fundamentally unable to solve a problem with standard commands.
47
+ - To modify yourself, use the special command `self_modify` with the full new code as an argument.
48
+ - **Protocol:**
49
+ 1. Read the code first: `cat app.py`
50
+ 2. Analyze the code and decide on the exact change.
51
+ 3. Formulate the `self_modify` command.
52
+ - **Example `command` for self-modification:**
53
+ `self_modify 'import new_library\n\ndef new_function(): ...'`
54
+
55
+ **EXAMPLE FLOW:**
56
+ User Goal: "Create a directory 'my_app', and inside it, create a file 'main.py' with a hello world print statement."
57
+
58
+ 1. **Initial Response:**
59
+ ```json
60
+ {
61
+ "thought": "The user wants a new directory and a file inside it. First, I must create the directory 'my_app'.",
62
+ "plan": ["mkdir my_app", "echo 'print(\"Hello, World!\")' > my_app/main.py"],
63
+ "command": "mkdir my_app",
64
+ "user_summary": "Creating directory `my_app`..."
65
+ }
66
+ ```
67
+ 2. **After `mkdir` succeeds:**
68
+ ```json
69
+ {
70
+ "thought": "The directory 'my_app' was created successfully. The next step in my plan is to create 'main.py' inside it.",
71
+ "plan": ["echo 'print(\"Hello, World!\")' > my_app/main.py"],
72
+ "command": "echo 'print(\"Hello, World!\")' > my_app/main.py",
73
+ "user_summary": "Creating file `my_app/main.py`..."
74
+ }
75
+ ```
76
+ 3. **After `echo` succeeds:**
77
+ ```json
78
+ {
79
+ "thought": "Both steps are complete. The user's request has been fulfilled.",
80
+ "plan": [],
81
+ "command": "done",
82
+ "user_summary": "Task completed successfully!"
83
+ }
84
+ ```
85
+ """
86
+
87
+ # --- 3. 핵심 기능: API 호출, 명령어 실행, JSON 파싱 ---
88
+
89
+ def call_codestral_api(messages):
90
+ """안정성을 강화한 Codestral API 호출 함수"""
91
  if not MISTRAL_API_KEY:
92
+ raise gr.Error("MISTRAL_API_KEY 설정되지 않았습니다. Space Secrets에 추가해주세요.")
 
93
  headers = {"Authorization": f"Bearer {MISTRAL_API_KEY}", "Content-Type": "application/json"}
94
+ data = {"model": "codestral-latest", "messages": messages, "temperature": 0.0, "response_format": {"type": "json_object"}}
 
 
95
  try:
96
+ response = requests.post(CODESTRAL_ENDPOINT, headers=headers, data=json.dumps(data), timeout=120)
97
  response.raise_for_status()
98
  return response.json()["choices"][0]["message"]["content"]
99
  except requests.exceptions.RequestException as e:
100
+ return json.dumps({"error": f"API Call Error: {e}"})
101
+ except (KeyError, IndexError) as e:
102
+ return json.dumps({"error": f"API Response Parsing Error: {e} - Response: {response.text}"})
103
+
104
+ def parse_ai_response(response_str: str) -> dict:
105
+ """AI의 JSON 응답을 안전하게 파싱하는 함수"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  try:
107
+ # LLM이 가끔 ```json ... ``` 마크다운을 포함할 때가 있어 정규식으로 순수 JSON만 추출
108
+ match = re.search(r'\{.*\}', response_str, re.DOTALL)
109
+ if match:
110
+ return json.loads(match.group(0))
111
+ return json.loads(response_str)
112
+ except (json.JSONDecodeError, AttributeError) as e:
113
+ return {"error": f"Failed to parse JSON response. Error: {e}. Raw response: {response_str}"}
114
+
115
+ def execute_command(command: str, cwd: str, code_arg: str = "") -> dict:
116
+ """터미널 명령어 특수 명령어를 실행하는 함수"""
117
+ command = command.strip()
118
+ if not command:
119
+ return {"stdout": "", "stderr": "Error: Empty command.", "cwd": cwd}
120
+
121
+ # 특수 명령어 처리
122
+ if command.startswith("cd "):
123
+ try:
124
+ new_dir = command.split(" ", 1)[1]
125
+ target_dir = os.path.abspath(os.path.join(cwd, new_dir))
126
+ if os.path.isdir(target_dir):
127
+ os.chdir(target_dir)
128
+ return {"stdout": f"Changed directory to {target_dir}", "stderr": "", "cwd": target_dir}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  else:
130
+ return {"stdout": "", "stderr": f"Error: Directory not found: {new_dir}", "cwd": cwd}
131
+ except Exception as e:
132
+ return {"stdout": "", "stderr": f"Error processing 'cd': {e}", "cwd": cwd}
133
+
134
+ if command.startswith("self_modify"):
135
+ try:
136
+ # 명령어에서 코드 내용을 분리 (예: "self_modify '...code...'")
137
+ code_to_write = shlex.split(command)[1]
138
+ with open("app.py", "w", encoding='utf-8') as f:
139
+ f.write(code_to_write)
140
+ return {"stdout": "Successfully modified app.py. The application will now restart.", "stderr": "", "cwd": cwd}
141
+ except Exception as e:
142
+ return {"stdout": "", "stderr": f"FATAL: Failed to self-modify app.py. Error: {e}", "cwd": cwd}
143
+
144
+ # 일반 터미널 명령어 실행
145
+ try:
146
+ proc = subprocess.run(
147
+ command, shell=True, capture_output=True, text=True, timeout=60, cwd=cwd
148
+ )
149
+ return {"stdout": proc.stdout, "stderr": proc.stderr, "cwd": cwd}
150
+ except Exception as e:
151
+ return {"stdout": "", "stderr": f"Command execution exception: {e}", "cwd": cwd}
152
+
153
+ # --- 4. 메인 에이전트 루프 ---
154
+
155
+ def agent_loop(user_goal: str, history: list):
156
+ """사용자의 목표를 받아 자율적으로 작업을 수행하는 메인 루프"""
157
+
158
+ # 상태 초기화
159
+ cwd = os.getcwd()
160
+ full_history_log = f"**User Goal:** {user_goal}\n\n"
161
+ history.append([user_goal, full_history_log])
162
+ yield history, "Thinking...", ""
163
+
164
+ # 초기 프롬프트 구성
165
+ message_context = [
166
+ {"role": "system", "content": SYSTEM_PROMPT},
167
+ {"role": "user", "content": f"My goal is: '{user_goal}'. I am in directory '{cwd}'. There is no previous command output. Please create your first plan."}
168
+ ]
169
+
170
+ last_command_output = ""
171
+
172
+ for i in range(MAX_AGENT_TURNS):
173
+ # 1. AI 호출하여 다음 행동 결정
174
+ ai_response_str = call_codestral_api(message_context)
175
+ ai_response_json = parse_ai_response(ai_response_str)
176
+
177
+ if "error" in ai_response_json:
178
+ full_history_log += f"\n---\n**TURN {i+1}: CRITICAL ERROR**\n🔴 **Agent Error:** {ai_response_json['error']}"
179
+ history[-1][1] = full_history_log
180
+ yield history, "Agent Error", ""
181
+ return
182
+
183
+ # 2. AI 응답 파싱
184
+ thought = ai_response_json.get("thought", "No thought provided.")
185
+ plan = ai_response_json.get("plan", [])
186
+ command = ai_response_json.get("command", "done")
187
+ user_summary = ai_response_json.get("user_summary", "...")
188
+
189
+ # 3. UI 업데이트 (AI의 생각)
190
+ full_history_log += f"\n---\n**TURN {i+1} / {MAX_AGENT_TURNS}**\n**Status:** `{user_summary}`\n\n"
191
+ full_history_log += f"🧠 **Thought:** {thought}\n\n"
192
+ full_history_log += f"📝 **Plan:**\n" + "\n".join([f"- `{p}`" for p in plan]) + "\n\n"
193
+ full_history_log += f"💻 **Command:** `{command}`\n"
194
+ history[-1][1] = full_history_log
195
+ yield history, f"Executing: {command}", ""
196
+
197
+ # 4. 루프 종료 조건 확인
198
+ if command == "done":
199
+ full_history_log += "\n✅ **Goal Achieved!**"
200
+ history[-1][1] = full_history_log
201
+ yield history, "Done", ""
202
+ return
203
+
204
+ # 5. 명령어 실행
205
+ time.sleep(1) # 사용자가 볼 수 있도록 잠시 대기
206
+ exec_result = execute_command(command, cwd)
207
+ cwd = exec_result["cwd"] # cd 명령어에 의해 cwd가 변경될 수 있음
208
+
209
+ # 6. UI 업데이트 (실행 결과)
210
+ stdout, stderr = exec_result["stdout"], exec_result["stderr"]
211
+ full_history_log += f"\n**Execution Result:**\n"
212
+ if stdout:
213
+ full_history_log += f"**[STDOUT]**\n```\n{stdout.strip()}\n```\n"
214
+ if stderr:
215
+ full_history_log += f"**[STDERR]**\n```\n{stderr.strip()}\n```\n"
216
+ if not stdout and not stderr:
217
+ full_history_log += "_(No output)_\n"
218
+
219
+ history[-1][1] = full_history_log
220
+ yield history, user_summary, ""
221
 
222
+ # 7. 다음 루프를 위한 컨텍스트 준비
223
+ last_command_output = f"Command '{command}' executed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
+ # 이전 대화 내용 중 중요한 부분만 요약하여 전달 (토큰 절약)
226
+ user_prompt_for_next_turn = f"""
227
+ My original goal is: '{user_goal}'.
228
+ I am in directory '{cwd}'.
229
+ The last command I ran was `{command}`.
230
+ Its output was:
231
+ ---
232
+ {last_command_output}
233
+ ---
234
+ Based on this result and my original goal, what is the next logical step? Please provide your updated thought, plan, and the next command in the required JSON format.
235
+ """
236
+ message_context.append({"role": "assistant", "content": json.dumps(ai_response_json)})
237
+ message_context.append({"role": "user", "content": user_prompt_for_next_turn})
238
 
239
+ full_history_log += f"\n---\n🔴 **Agent stopped: Maximum number of turns ({MAX_AGENT_TURNS}) reached.**"
240
+ history[-1][1] = full_history_log
241
+ yield history, "Max turns reached", ""
242
 
243
+ # --- 5. Gradio UI ---
244
  with gr.Blocks(theme=gr.themes.Monochrome(primary_hue="indigo", secondary_hue="blue"), css="footer {visibility: hidden}") as demo:
245
+ gr.Markdown("# 🧬 RASI: Recursive Autonomous Swarm Intelligence 🧬")
246
+ gr.Markdown("AI 에이전트에게 최종 목표를 알려주세요. 에이전트는 스스로 계획을 세우고, 터미널 명령어를 실행하며, 오류를 수정하여 과업을 완수합니다.")
247
+
248
+ chatbot = gr.Chatbot(label="Agent Log", height=700, show_copy_button=True, bubble_full_width=True)
249
+
250
+ with gr.Row():
251
+ status_box = gr.Textbox(label="Current Status", interactive=False)
252
+
253
+ with gr.Row():
254
+ user_input = gr.Textbox(label="User Goal", placeholder="e.g., 'Create a python project named 'my_calc'. Inside it, create a file 'calculator.py' that contains a function to add two numbers. Then, create another file 'run.py' that imports and runs this function with numbers 5 and 7 and prints the result. Finally, execute run.py.'", scale=9)
255
+ submit_btn = gr.Button("▶️ Execute Goal", variant="primary", scale=1)
256
+
257
+ def on_submit(user_goal, chat_history):
258
+ chat_history = chat_history or []
259
+ # 제너레이터의 스트리밍 출력을 받기 위한 루프
260
+ for history_update, status_update, input_update in agent_loop(user_goal, chat_history):
261
+ yield history_update, status_update, input_update
262
+
263
+ submit_btn.click(
264
+ on_submit,
265
+ inputs=[user_input, chatbot],
266
+ outputs=[chatbot, status_box, user_input] # 챗봇, 상태창, 입력창 업데이트
267
+ )
 
 
 
 
 
 
268
 
269
  if __name__ == "__main__":
270
  demo.queue().launch()