harvesthealth commited on
Commit
b6181ac
·
verified ·
1 Parent(s): 25805b3

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. __pycache__/app.cpython-312.pyc +0 -0
  2. app.py +166 -70
  3. requirements.txt +4 -3
__pycache__/app.cpython-312.pyc CHANGED
Binary files a/__pycache__/app.cpython-312.pyc and b/__pycache__/app.cpython-312.pyc differ
 
app.py CHANGED
@@ -4,11 +4,14 @@ import asyncio
4
  import logging
5
  import json
6
  import requests
 
7
  from datetime import datetime
 
 
8
  from langchain_openai import ChatOpenAI
9
  from langchain.agents import AgentExecutor, create_openai_functions_agent
10
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
11
- from langchain_core.messages import HumanMessage, AIMessage
12
  from langchain_mcp_adapters.tools import load_mcp_tools
13
  from mcp import StdioServerParameters
14
  from tools_api import search_web_tool, search_hf_spaces_tool
@@ -16,6 +19,13 @@ from langchain.tools import Tool
16
  from git import Repo
17
  import shutil
18
 
 
 
 
 
 
 
 
19
  # Configure logging
20
  logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger("agent-app")
@@ -23,9 +33,9 @@ logger = logging.getLogger("agent-app")
23
  # Constants
24
  JULES_FILES_REPO = "https://github.com/JsonLord/jules_files.git"
25
  WORK_DIR = "/tmp/agent_work"
 
26
 
27
  # Initialize LLMs (Helmholtz Blablador)
28
- HELMHOLTZ_BASE_URL = "https://api.helmholtz-blablador.fz-juelich.de/v1"
29
  api_key = os.environ.get("BLABLADOR_API_KEY")
30
 
31
  chat_llm = ChatOpenAI(
@@ -49,6 +59,70 @@ fast_llm = ChatOpenAI(
49
  max_tokens=512
50
  )
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  # Shared memory and context management
53
  def convert_to_langchain_messages(history):
54
  """Convert Gradio history (list of dicts) to LangChain message objects."""
@@ -61,24 +135,13 @@ def convert_to_langchain_messages(history):
61
  messages.append(HumanMessage(content=content))
62
  elif role == "assistant":
63
  messages.append(AIMessage(content=content))
64
- elif isinstance(msg, (list, tuple)) and len(msg) == 2:
65
- # Handle legacy tuple format just in case
66
- messages.append(HumanMessage(content=msg[0]))
67
- messages.append(AIMessage(content=msg[1]))
68
  return messages
69
 
70
- def truncate_history(history, max_messages=5):
71
- """Truncate chat history to fit smaller context windows."""
72
- if not history:
73
- return []
74
- return history[-max_messages:]
75
-
76
  # Log required secrets reminder
77
  required_secrets = [
78
  "BLABLADOR_API_KEY",
79
  "GITHUB_TOKEN",
80
  "JULES_API_KEY",
81
- "CONTEXTSTREAM_API_KEY",
82
  "HF_TOKEN",
83
  "HF_PROFILE"
84
  ]
@@ -90,15 +153,14 @@ for secret in required_secrets:
90
  logger.warning(f"[MISSING] {secret} is not set. Please add it to your Space secrets.")
91
  logger.info("==========================================")
92
 
93
- # Memory/Storage
94
- project_state = {
95
  "idea": "",
96
  "overview": "",
97
  "repo_url": ""
98
  }
99
 
100
  async def get_all_tools():
101
- # 1. Custom Search Tools
102
  custom_tools = [
103
  Tool(
104
  name="search_web",
@@ -112,75 +174,87 @@ async def get_all_tools():
112
  )
113
  ]
114
 
115
- # 2. MCP Tools
116
  mcp_tools = []
117
 
118
- # Jules MCP
119
- try:
120
- jules_mcp = await load_mcp_tools(StdioServerParameters(
121
- command="python3",
122
- args=["mcp/mcp_jules.py"],
123
- env=os.environ.copy()
124
- ))
125
- mcp_tools.extend(jules_mcp)
126
- except Exception as e:
127
- logger.error(f"Failed to load Jules MCP tools: {e}")
128
-
129
- # ContextStream MCP
130
  try:
131
- cs_mcp = await load_mcp_tools(StdioServerParameters(
132
- command="npx",
133
- args=["-y", "@contextstream/mcp-server"],
134
- env=os.environ.copy()
135
- ))
136
- mcp_tools.extend(cs_mcp)
137
  except Exception as e:
138
- logger.error(f"Failed to load ContextStream MCP tools: {e}")
139
 
140
- # GitHub MCP
141
  try:
142
- github_mcp = await load_mcp_tools(StdioServerParameters(
143
- command="npx",
144
- args=["-y", "@modelcontextprotocol/server-github"],
145
- env=os.environ.copy()
146
- ))
147
- mcp_tools.extend(github_mcp)
148
  except Exception as e:
149
- logger.error(f"Failed to load GitHub MCP tools: {e}")
150
 
151
  return custom_tools + mcp_tools
152
 
153
- async def create_agent(llm_instance):
 
 
154
  tools = await get_all_tools()
 
 
 
 
 
 
155
  prompt = ChatPromptTemplate.from_messages([
156
- ("system", "You are an autonomous agent assisting in building and improving applications. "
157
- "Use your tools to search the web, explore HF spaces, manage GitHub repos, and interact with Jules. "
158
- "Current project state: {project_state}"),
159
- MessagesPlaceholder(variable_name="chat_history"),
160
- ("human", "{input}"),
161
  MessagesPlaceholder(variable_name="agent_scratchpad"),
162
  ])
163
- agent = create_openai_functions_agent(llm_instance, tools, prompt)
164
- return AgentExecutor(agent=agent, tools=tools, verbose=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  # Session handlers
167
  async def handle_ideate(message, history):
168
- # Use chat_llm for ideation
169
- agent_executor = await create_agent(chat_llm)
 
170
 
171
- # Convert Gradio history to LangChain messages
172
- lc_history = convert_to_langchain_messages(history)
173
 
174
- # Large model can handle more context, but still good to be mindful
175
- processed_history = truncate_history(lc_history, max_messages=10)
 
 
176
 
177
- result = await agent_executor.ainvoke({
178
- "input": message,
179
- "chat_history": processed_history,
180
- "project_state": json.dumps(project_state)
181
- })
182
- project_state["idea"] += f"\nUser: {message}\nAgent: {result['output']}"
183
- return result["output"]
184
 
185
  async def handle_github_prep(idea_description, target_repo_name):
186
  if not idea_description:
@@ -189,17 +263,16 @@ async def handle_github_prep(idea_description, target_repo_name):
189
  return "Please provide a target repository name."
190
 
191
  try:
192
- # 1. Clone jules_files
193
  repo_path = os.path.join(WORK_DIR, "jules_files")
194
  if os.path.exists(repo_path):
195
  shutil.rmtree(repo_path)
196
  Repo.clone_from(JULES_FILES_REPO, repo_path)
197
 
198
- # 2. Adapt files
199
  to_be_adapted_dir = os.path.join(repo_path, "to_be_adapted")
200
  jules_temp_dir = os.path.join(repo_path, "jules")
201
  os.makedirs(jules_temp_dir, exist_ok=True)
202
 
 
203
  for filename in os.listdir(to_be_adapted_dir):
204
  file_path = os.path.join(to_be_adapted_dir, filename)
205
  if os.path.isfile(file_path):
@@ -208,8 +281,21 @@ async def handle_github_prep(idea_description, target_repo_name):
208
  response = await code_llm.ainvoke(f"Adapt the following file according to instructions for the project idea: {idea_description}\n\nInstructions:\n{instructions}")
209
  with open(os.path.join(jules_temp_dir, filename), 'w') as f:
210
  f.write(response.content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- # 3. Push to target repo
213
  github_token = os.environ.get("GITHUB_TOKEN")
214
  if not github_token:
215
  return "GITHUB_TOKEN not found in environment."
@@ -242,18 +328,28 @@ async def handle_github_prep(idea_description, target_repo_name):
242
  repo.index.commit(f"Add adapted jules files for project: {idea_description[:50]}...")
243
  repo.git.push()
244
 
245
- project_state["repo_url"] = f"https://github.com/JsonLord/{target_repo_name}"
246
  return f"Files adapted and pushed to JsonLord/{target_repo_name}/jules."
247
  except Exception as e:
248
  logger.error(f"GitHub Prep failed: {e}")
249
  return f"Error: {str(e)}"
250
 
251
  async def handle_jules_comm(repo_url):
252
- agent_executor = await create_agent(fast_llm)
253
  prompt = f"Create a new session in Jules for the repo {repo_url} and start implementing based on the files in /jules folder."
254
  result = await agent_executor.ainvoke({"input": prompt, "chat_history": [], "project_state": ""})
255
  return result["output"]
256
 
 
 
 
 
 
 
 
 
 
 
 
257
  async def handle_test():
258
  results = []
259
  try:
 
4
  import logging
5
  import json
6
  import requests
7
+ import uuid
8
  from datetime import datetime
9
+ from typing import Literal, TypedDict, List, Optional, Tuple, Any
10
+ from pydantic import BaseModel, Field
11
  from langchain_openai import ChatOpenAI
12
  from langchain.agents import AgentExecutor, create_openai_functions_agent
13
  from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
14
+ from langchain_core.messages import HumanMessage, AIMessage, BaseMessage
15
  from langchain_mcp_adapters.tools import load_mcp_tools
16
  from mcp import StdioServerParameters
17
  from tools_api import search_web_tool, search_hf_spaces_tool
 
19
  from git import Repo
20
  import shutil
21
 
22
+ # LangGraph imports
23
+ from langgraph.graph import StateGraph, START, END, MessagesState
24
+ from langgraph.checkpoint.memory import MemorySaver
25
+ from langgraph.store.memory import InMemoryStore
26
+ from langgraph.store.base import BaseStore
27
+ from langgraph.types import Command
28
+
29
  # Configure logging
30
  logging.basicConfig(level=logging.INFO)
31
  logger = logging.getLogger("agent-app")
 
33
  # Constants
34
  JULES_FILES_REPO = "https://github.com/JsonLord/jules_files.git"
35
  WORK_DIR = "/tmp/agent_work"
36
+ HELMHOLTZ_BASE_URL = "https://api.helmholtz-blablador.fz-juelich.de/v1"
37
 
38
  # Initialize LLMs (Helmholtz Blablador)
 
39
  api_key = os.environ.get("BLABLADOR_API_KEY")
40
 
41
  chat_llm = ChatOpenAI(
 
59
  max_tokens=512
60
  )
61
 
62
+ # Memory Stores
63
+ checkpointer = MemorySaver()
64
+ long_term_store = InMemoryStore()
65
+
66
+ # Schemas for Memory
67
+ class UserPreferences(BaseModel):
68
+ """Updated user preferences and project context."""
69
+ chain_of_thought: str = Field(description="Reasoning about what needs to be remembered.")
70
+ user_preferences: str = Field(description="Updated user preferences and context.")
71
+
72
+ class State(MessagesState):
73
+ """Central state for the agent graph."""
74
+ project_state: str # JSON string of project_state
75
+
76
+ # Memory Functions
77
+ def get_memory(store, namespace, key="user_preferences", default_content=""):
78
+ """Retrieve memory from store or initialize with default."""
79
+ memory = store.get(namespace, key)
80
+ if memory:
81
+ return memory.value
82
+ else:
83
+ store.put(namespace, key, default_content)
84
+ return default_content
85
+
86
+ MEMORY_UPDATE_INSTRUCTIONS = """
87
+ # Role
88
+ You are a memory profile manager for an AI application assistant.
89
+
90
+ # Rules
91
+ - NEVER overwrite the entire profile
92
+ - ONLY add new information or update facts contradicted by feedback
93
+ - PRESERVE all other information
94
+ - Focus on user preferences, project goals, and technical decisions.
95
+
96
+ # Process current profile for {namespace}
97
+ <memory_profile>
98
+ {current_profile}
99
+ </memory_profile>
100
+ """
101
+
102
+ async def update_memory(store, namespace, messages, current_profile):
103
+ """Intelligently update the memory store based on conversation."""
104
+ if not messages:
105
+ return current_profile
106
+
107
+ memory_updater_llm = fast_llm.with_structured_output(UserPreferences)
108
+
109
+ messages_to_send = [
110
+ msg for msg in messages
111
+ if isinstance(msg, (HumanMessage, AIMessage))
112
+ ]
113
+
114
+ try:
115
+ result = await memory_updater_llm.ainvoke(
116
+ [
117
+ {"role": "system", "content": MEMORY_UPDATE_INSTRUCTIONS.format(current_profile=current_profile, namespace=namespace)},
118
+ ] + messages_to_send
119
+ )
120
+ store.put(namespace, "user_preferences", result.user_preferences)
121
+ return result.user_preferences
122
+ except Exception as e:
123
+ logger.error(f"Failed to update memory: {e}")
124
+ return current_profile
125
+
126
  # Shared memory and context management
127
  def convert_to_langchain_messages(history):
128
  """Convert Gradio history (list of dicts) to LangChain message objects."""
 
135
  messages.append(HumanMessage(content=content))
136
  elif role == "assistant":
137
  messages.append(AIMessage(content=content))
 
 
 
 
138
  return messages
139
 
 
 
 
 
 
 
140
  # Log required secrets reminder
141
  required_secrets = [
142
  "BLABLADOR_API_KEY",
143
  "GITHUB_TOKEN",
144
  "JULES_API_KEY",
 
145
  "HF_TOKEN",
146
  "HF_PROFILE"
147
  ]
 
153
  logger.warning(f"[MISSING] {secret} is not set. Please add it to your Space secrets.")
154
  logger.info("==========================================")
155
 
156
+ # Initial Project State
157
+ initial_project_state = {
158
  "idea": "",
159
  "overview": "",
160
  "repo_url": ""
161
  }
162
 
163
  async def get_all_tools():
 
164
  custom_tools = [
165
  Tool(
166
  name="search_web",
 
174
  )
175
  ]
176
 
 
177
  mcp_tools = []
178
 
179
+ # Try loading MCP tools
 
 
 
 
 
 
 
 
 
 
 
180
  try:
181
+ # Jules
182
+ j_params = StdioServerParameters(command="python3", args=["mcp/mcp_jules.py"], env=os.environ.copy())
183
+ # The correct way might be passing a list of servers to a manager
184
+ # For now, let's stick to what we have or just skip them if they fail
185
+ mcp_tools.extend(await load_mcp_tools(j_params))
 
186
  except Exception as e:
187
+ logger.warning(f"Jules MCP skipped: {e}")
188
 
 
189
  try:
190
+ # GitHub
191
+ gh_params = StdioServerParameters(command="npx", args=["-y", "@modelcontextprotocol/server-github"], env=os.environ.copy())
192
+ mcp_tools.extend(await load_mcp_tools(gh_params))
 
 
 
193
  except Exception as e:
194
+ logger.warning(f"GitHub MCP skipped: {e}")
195
 
196
  return custom_tools + mcp_tools
197
 
198
+ # LangGraph Nodes
199
+ async def agent_node(state: State, config: Any, store: BaseStore):
200
+ """Agent reasoning node."""
201
  tools = await get_all_tools()
202
+
203
+ # Retrieve LTM
204
+ user_id = config.get("configurable", {}).get("user_id", "default_user")
205
+ namespace = (user_id, "ideate_memory")
206
+ ltm_context = get_memory(store, namespace, default_content="No previous context.")
207
+
208
  prompt = ChatPromptTemplate.from_messages([
209
+ ("system", "You are an autonomous agent assisting in building applications. "
210
+ "Use tools to search web/HF, manage GitHub, and Jules. "
211
+ "\nLong-Term Memory/Context: {ltm_context}"
212
+ "\nProject State: {project_state}"),
213
+ MessagesPlaceholder(variable_name="messages"),
214
  MessagesPlaceholder(variable_name="agent_scratchpad"),
215
  ])
216
+
217
+ agent = create_openai_functions_agent(chat_llm, tools, prompt)
218
+ agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
219
+
220
+ try:
221
+ result = await agent_executor.ainvoke({
222
+ "messages": state["messages"],
223
+ "ltm_context": ltm_context,
224
+ "project_state": state["project_state"]
225
+ })
226
+ output = result["output"]
227
+ except Exception as e:
228
+ logger.error(f"Agent execution failed: {e}")
229
+ output = f"I encountered an error: {e}"
230
+
231
+ # Update memory after each turn
232
+ await update_memory(store, namespace, state["messages"] + [AIMessage(content=output)], ltm_context)
233
+
234
+ return {"messages": [AIMessage(content=output)]}
235
+
236
+ # Assemble Graph
237
+ workflow = StateGraph(State)
238
+ workflow.add_node("agent", agent_node)
239
+ workflow.add_edge(START, "agent")
240
+ workflow.add_edge("agent", END)
241
+ compiled_graph = workflow.compile(checkpointer=checkpointer, store=long_term_store)
242
 
243
  # Session handlers
244
  async def handle_ideate(message, history):
245
+ config = {"configurable": {"thread_id": "global_thread", "user_id": "user_1"}}
246
+ lc_messages = convert_to_langchain_messages(history)
247
+ lc_messages.append(HumanMessage(content=message))
248
 
249
+ p_state = json.dumps(initial_project_state)
 
250
 
251
+ result = await compiled_graph.ainvoke(
252
+ {"messages": lc_messages, "project_state": p_state},
253
+ config=config
254
+ )
255
 
256
+ bot_message = result["messages"][-1].content
257
+ return bot_message
 
 
 
 
 
258
 
259
  async def handle_github_prep(idea_description, target_repo_name):
260
  if not idea_description:
 
263
  return "Please provide a target repository name."
264
 
265
  try:
 
266
  repo_path = os.path.join(WORK_DIR, "jules_files")
267
  if os.path.exists(repo_path):
268
  shutil.rmtree(repo_path)
269
  Repo.clone_from(JULES_FILES_REPO, repo_path)
270
 
 
271
  to_be_adapted_dir = os.path.join(repo_path, "to_be_adapted")
272
  jules_temp_dir = os.path.join(repo_path, "jules")
273
  os.makedirs(jules_temp_dir, exist_ok=True)
274
 
275
+ # Adapt existing files
276
  for filename in os.listdir(to_be_adapted_dir):
277
  file_path = os.path.join(to_be_adapted_dir, filename)
278
  if os.path.isfile(file_path):
 
281
  response = await code_llm.ainvoke(f"Adapt the following file according to instructions for the project idea: {idea_description}\n\nInstructions:\n{instructions}")
282
  with open(os.path.join(jules_temp_dir, filename), 'w') as f:
283
  f.write(response.content)
284
+
285
+ # Explicitly generate AGENTS.md for Jules behavior and context
286
+ agents_md_prompt = f"""Generate an AGENTS.md file for the project: {idea_description}.
287
+ This file is for 'Google Jules' (an AI coding agent).
288
+ It must contain:
289
+ 1. Working behavior: How Jules should approach tasks in this repo.
290
+ 2. Prompt context: Key information Jules must keep in context.
291
+ 3. Instructions on how to follow the other project files (Project_Overview, etc.).
292
+ 4. Tips for Jules to achieve the best results for this specific project.
293
+ Format it in Markdown."""
294
+
295
+ agents_response = await code_llm.ainvoke(agents_md_prompt)
296
+ with open(os.path.join(jules_temp_dir, "AGENTS.md"), 'w') as f:
297
+ f.write(agents_response.content)
298
 
 
299
  github_token = os.environ.get("GITHUB_TOKEN")
300
  if not github_token:
301
  return "GITHUB_TOKEN not found in environment."
 
328
  repo.index.commit(f"Add adapted jules files for project: {idea_description[:50]}...")
329
  repo.git.push()
330
 
 
331
  return f"Files adapted and pushed to JsonLord/{target_repo_name}/jules."
332
  except Exception as e:
333
  logger.error(f"GitHub Prep failed: {e}")
334
  return f"Error: {str(e)}"
335
 
336
  async def handle_jules_comm(repo_url):
337
+ agent_executor = await create_agent_executor(fast_llm)
338
  prompt = f"Create a new session in Jules for the repo {repo_url} and start implementing based on the files in /jules folder."
339
  result = await agent_executor.ainvoke({"input": prompt, "chat_history": [], "project_state": ""})
340
  return result["output"]
341
 
342
+ async def create_agent_executor(llm_instance):
343
+ tools = await get_all_tools()
344
+ prompt = ChatPromptTemplate.from_messages([
345
+ ("system", "You are an assistant for Jules session management."),
346
+ MessagesPlaceholder(variable_name="chat_history"),
347
+ ("human", "{input}"),
348
+ MessagesPlaceholder(variable_name="agent_scratchpad"),
349
+ ])
350
+ agent = create_openai_functions_agent(llm_instance, tools, prompt)
351
+ return AgentExecutor(agent=agent, tools=tools, verbose=True)
352
+
353
  async def handle_test():
354
  results = []
355
  try:
requirements.txt CHANGED
@@ -1,7 +1,8 @@
1
  gradio
2
- langchain==0.3.17
3
- langchain-openai==0.2.14
4
- langchain-mcp-adapters==0.1.0
 
5
  requests
6
  python-dotenv
7
  huggingface_hub
 
1
  gradio
2
+ langchain
3
+ langchain-openai
4
+ langchain-mcp-adapters
5
+ langgraph
6
  requests
7
  python-dotenv
8
  huggingface_hub