Skip to content

Commit e2f06ce

Browse files
authored
CG-10805 & CG-10806: Migrate Everything to Langgraph + Multi-LLM Config (openai + anthropic) (#600)
- migrated everything over to langgarph - now uses LLM class for everything including the model in `semantic_edit.py` - no more AgentExecutor - The syntax for codeagent stays the same ```python from codegen import CodeAgent from codegen import Codebase codebase = Codebase("./") agent = CodeAgent(codebase=codebase, thread_id=1) output = agent.run("Go into one of the files") ``` - the agent streams each step `agent.stream` now instead of calling invoke so that intermediate steps can be printed - If you want to directly invoke the agent, then use thread_id instead + set debug to true for the `create_codebase_agent` function so that logs get printed. However, streaming is preferred and outputs nicer logs as well. `agent.stream` does NOT mean streaming each token! Streaming indicates getting each agent action one by one. `agent.invoke` won't output until the the agent finishes the execution - langgraph uses threads to keep track of the conversation history ```python output = agent.invoke({"messages": [("user", "Go into one of the files")]}, config={"configurable": {"thread_id": 1}}) ``` If you want to call invoke for whatever reason, then you can set debug flag to true for printed debug statements. However, streaming shows nicer outputs. Streaming also does NOT mean streaming each token. agent.stream means to stream each step, and thus we can print each step however we want. ```python # add the debug flag to true if you want to Debug intermediate steps when calling invoke (this does not apply to agent.stream()) create_react_agent(model=llm, tools=tools, prompt=system_message, checkpointer=memory, debug=debug)
1 parent af35ac8 commit e2f06ce

File tree

20 files changed

+654
-322
lines changed

20 files changed

+654
-322
lines changed

codegen-examples/examples/deep_code_research/run.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def cli():
7979
@cli.command()
8080
@click.argument("repo_name", required=False)
8181
@click.option("--query", "-q", default=None, help="Initial research query to start with.")
82-
def research(repo_name: Optional[str] = None, query: Optional[str] = None):
82+
def research(repo_name: Optional[str] = None, query: Optional[str] = None, thread_id: Optional[int] = 100):
8383
"""[bold green]Start a code research session[/bold green]
8484
8585
[blue]Arguments:[/blue]
@@ -107,7 +107,7 @@ def research(repo_name: Optional[str] = None, query: Optional[str] = None):
107107

108108
# Initialize agent with research tools
109109
with console.status("[bold blue]Initializing research agent...[/bold blue]") as status:
110-
agent = create_agent_with_tools(codebase=codebase, tools=tools, chat_history=[SystemMessage(content=RESEARCH_AGENT_PROMPT)], verbose=True)
110+
agent = create_agent_with_tools(codebase=codebase, tools=tools, system_message=SystemMessage(content=RESEARCH_AGENT_PROMPT))
111111
status.update("[bold green]✓ Research agent ready![/bold green]")
112112

113113
# Get initial query if not provided
@@ -136,11 +136,11 @@ def research(repo_name: Optional[str] = None, query: Optional[str] = None):
136136
try:
137137
result = agent.invoke(
138138
{"input": query},
139-
config={"configurable": {"session_id": "research"}},
139+
config={"configurable": {"thread_id": thread_id}},
140140
)
141141
# Display the result
142142
console.print("\n[bold blue]📊 Research Findings:[/bold blue]")
143-
console.print(Markdown(result["output"]))
143+
console.print(Markdown(result["messages"][-1].content))
144144
except Exception as e:
145145
console.print(f"\n[bold red]Error during research:[/bold red] {e}")
146146

codegen-examples/examples/langchain_agent/README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ from codegen.extensions.langchain import create_codebase_agent
2929
codebase = Codebase.from_repo("fastapi/fastapi")
3030

3131
# Create the agent
32-
agent = create_codebase_agent(codebase=codebase, model_name="gpt-4", verbose=True)
32+
agent = create_codebase_agent(
33+
codebase=codebase,
34+
model_provider="anthropic", # or "openai"
35+
model_name="claude-3-5-sonnet-latest", # or "gpt-4" for OpenAI
36+
debug=True,
37+
)
3338

3439
# Ask the agent to analyze code
35-
result = agent.invoke({"input": "What are the dependencies of the FastAPI class?", "config": {"configurable": {"session_id": "demo"}}})
36-
print(result["output"])
40+
result = agent.invoke({"input": "What are the dependencies of the FastAPI class?", "config": {"configurable": {"thread_id": "1"}}})
41+
print(result["messages"][-1].content)
3742
```
3843

3944
## Installation
@@ -68,13 +73,13 @@ The agent can perform various code analysis and manipulation tasks:
6873

6974
```python
7075
# Analyze dependencies
71-
agent.invoke({"input": "What are the dependencies of the reveal_symbol function?", "config": {"configurable": {"session_id": "demo"}}})
76+
agent.invoke({"input": "What are the dependencies of the reveal_symbol function?", "config": {"configurable": {"thread_id": "1"}}})
7277

7378
# Find usage patterns
74-
agent.invoke({"input": "Show me examples of dependency injection in the codebase", "config": {"configurable": {"session_id": "demo"}}})
79+
agent.invoke({"input": "Show me examples of dependency injection in the codebase", "config": {"configurable": {"thread_id": "1"}}})
7580

7681
# Move code
77-
agent.invoke({"input": "Move the validate_email function to validation_utils.py", "config": {"configurable": {"session_id": "demo"}}})
82+
agent.invoke({"input": "Move the validate_email function to validation_utils.py", "config": {"configurable": {"thread_id": "1"}}})
7883
```
7984

8085
## Learn More

codegen-examples/examples/langchain_agent/run.py

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,45 @@
1414
SemanticEditTool,
1515
ViewFileTool,
1616
)
17-
from langchain import hub
18-
from langchain.agents import AgentExecutor
19-
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
20-
from langchain_core.chat_history import ChatMessageHistory
21-
from langchain_core.runnables.history import RunnableWithMessageHistory
22-
from langchain_openai import ChatOpenAI
17+
18+
from codegen.extensions.langchain.llm import LLM
19+
from codegen.extensions.langchain.prompts import REASONER_SYSTEM_MESSAGE
20+
21+
from langgraph.checkpoint.memory import MemorySaver
22+
from langgraph.graph.graph import CompiledGraph
23+
from langgraph.prebuilt import create_react_agent
24+
from langchain_core.messages import SystemMessage
2325

2426

2527
def create_codebase_agent(
26-
codebase: Codebase,
27-
model_name: str = "gpt-4o",
28-
temperature: float = 0,
29-
verbose: bool = True,
30-
) -> RunnableWithMessageHistory:
28+
codebase: "Codebase",
29+
model_provider: str = "anthropic",
30+
model_name: str = "claude-3-5-sonnet-latest",
31+
system_message: SystemMessage = SystemMessage(REASONER_SYSTEM_MESSAGE),
32+
memory: bool = True,
33+
debug: bool = True,
34+
**kwargs,
35+
) -> CompiledGraph:
3136
"""Create an agent with all codebase tools.
3237
3338
Args:
3439
codebase: The codebase to operate on
35-
model_name: Name of the model to use (default: gpt-4)
36-
temperature: Model temperature (default: 0)
40+
model_provider: The model provider to use ("anthropic" or "openai")
41+
model_name: Name of the model to use
3742
verbose: Whether to print agent's thought process (default: True)
43+
chat_history: Optional list of messages to initialize chat history with
44+
**kwargs: Additional LLM configuration options. Supported options:
45+
- temperature: Temperature parameter (0-1)
46+
- top_p: Top-p sampling parameter (0-1)
47+
- top_k: Top-k sampling parameter (>= 1)
48+
- max_tokens: Maximum number of tokens to generate
3849
3950
Returns:
4051
Initialized agent with message history
4152
"""
42-
# Initialize language model
43-
llm = ChatOpenAI(
44-
model_name=model_name,
45-
temperature=temperature,
46-
)
53+
llm = LLM(model_provider=model_provider, model_name=model_name, **kwargs)
4754

55+
# Get all codebase tools
4856
# Get all codebase tools
4957
tools = [
5058
ViewFileTool(codebase),
@@ -60,33 +68,9 @@ def create_codebase_agent(
6068
CommitTool(codebase),
6169
]
6270

63-
# Get the prompt to use
64-
prompt = hub.pull("hwchase17/openai-functions-agent")
65-
66-
# Create the agent
67-
agent = OpenAIFunctionsAgent(
68-
llm=llm,
69-
tools=tools,
70-
prompt=prompt,
71-
)
72-
73-
# Create the agent executor
74-
agent_executor = AgentExecutor(
75-
agent=agent,
76-
tools=tools,
77-
verbose=verbose,
78-
)
71+
memory = MemorySaver() if memory else None
7972

80-
# Create message history handler
81-
message_history = ChatMessageHistory()
82-
83-
# Wrap with message history
84-
return RunnableWithMessageHistory(
85-
agent_executor,
86-
lambda session_id: message_history,
87-
input_messages_key="input",
88-
history_messages_key="chat_history",
89-
)
73+
return create_react_agent(model=llm, tools=tools, prompt=system_message, checkpointer=memory, debug=debug)
9074

9175

9276
if __name__ == "__main__":
@@ -101,6 +85,6 @@ def create_codebase_agent(
10185
print("\nAsking agent to analyze symbol relationships...")
10286
result = agent.invoke(
10387
{"input": "What are the dependencies of the reveal_symbol function?"},
104-
config={"configurable": {"session_id": "demo"}},
88+
config={"configurable": {"thread_id": 1}},
10589
)
10690
print("Messages:", result["messages"])

docs/tutorials/build-code-agent.mdx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,19 @@ agent = CodeAgent(codebase)
4646
# Run the agent with a prompt
4747
agent.run("Tell me about this repo")
4848
```
49-
<Note>Your `ANTHROPIC_API_KEY` must be set in your env.</Note>
49+
50+
51+
<Note>Your `ANTHROPIC_API_KEY` and/or `OPENAI_API_KEY` must be set in your env.</Note>
52+
53+
The default implementation uses `anthropic/claude-3-5-sonnet-latest` for the model but this can be changed through the `model_provider` and `model_name` arguments.
54+
55+
```python
56+
agent = CodeAgent(
57+
codebase=codebase,
58+
model_provider="openai",
59+
model_name="gpt-4o",
60+
)
61+
```
5062

5163
# Available Tools
5264

docs/tutorials/deep-code-research.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ def research(repo_name: Optional[str] = None, query: Optional[str] = None):
135135

136136
result = agent.invoke(
137137
{"input": query},
138-
config={"configurable": {"session_id": "research"}}
138+
config={"configurable": {"thread_id": 1}}
139139
)
140-
console.print(Markdown(result["output"]))
140+
console.print(Markdown(result["messages"][-1].content))
141141

142142
query = None # Clear for next iteration
143143
```

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ dependencies = [
6363
"langchain[openai]",
6464
"langchain_core",
6565
"langchain_openai",
66+
"langgraph",
6667
"numpy>=2.2.2",
6768
"mcp[cli]",
6869
"neo4j",

src/codegen/agents/code_agent.py

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,65 @@
1-
from typing import Optional
1+
from typing import TYPE_CHECKING, Optional
22
from uuid import uuid4
33

44
from langchain.tools import BaseTool
5+
from langchain_core.messages import AIMessage
56

67
from codegen.extensions.langchain.agent import create_codebase_agent
7-
from codegen.sdk.core.codebase import Codebase
8+
9+
if TYPE_CHECKING:
10+
from codegen import Codebase
811

912

1013
class CodeAgent:
1114
"""Agent for interacting with a codebase."""
1215

13-
def __init__(self, codebase: Codebase, tools: Optional[list[BaseTool]] = None):
16+
def __init__(self, codebase: "Codebase", model_provider: str = "anthropic", model_name: str = "claude-3-5-sonnet-latest", memory: bool = True, tools: Optional[list[BaseTool]] = None, **kwargs):
17+
"""Initialize a CodeAgent.
18+
19+
Args:
20+
codebase: The codebase to operate on
21+
model_provider: The model provider to use ("anthropic" or "openai")
22+
model_name: Name of the model to use
23+
memory: Whether to let LLM keep track of the conversation history
24+
tools: Additional tools to use
25+
**kwargs: Additional LLM configuration options. Supported options:
26+
- temperature: Temperature parameter (0-1)
27+
- top_p: Top-p sampling parameter (0-1)
28+
- top_k: Top-k sampling parameter (>= 1)
29+
- max_tokens: Maximum number of tokens to generate
30+
"""
1431
self.codebase = codebase
15-
self.agent = create_codebase_agent(self.codebase, additional_tools=tools)
16-
17-
def run(self, prompt: str, session_id: Optional[str] = None) -> str:
18-
if session_id is None:
19-
session_id = str(uuid4())
20-
return self.agent.invoke(
21-
{"input": prompt},
22-
config={"configurable": {"session_id": session_id}},
23-
)
32+
self.agent = create_codebase_agent(self.codebase, model_provider=model_provider, model_name=model_name, memory=memory, additional_tools=tools, **kwargs)
33+
34+
def run(self, prompt: str, thread_id: Optional[str] = None) -> str:
35+
"""Run the agent with a prompt.
36+
37+
Args:
38+
prompt: The prompt to run
39+
thread_id: Optional thread ID for message history
40+
41+
Returns:
42+
The agent's response
43+
"""
44+
if thread_id is None:
45+
thread_id = str(uuid4())
46+
47+
# this message has a reducer which appends the current message to the existing history
48+
# see more https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers
49+
input = {"messages": [("user", prompt)]}
50+
51+
# we stream the steps instead of invoke because it allows us to access intermediate nodes
52+
stream = self.agent.stream(input, config={"configurable": {"thread_id": thread_id}}, stream_mode="values")
53+
54+
for s in stream:
55+
message = s["messages"][-1]
56+
if isinstance(message, tuple):
57+
print(message)
58+
else:
59+
if isinstance(message, AIMessage) and isinstance(message.content, list) and "text" in message.content[0]:
60+
AIMessage(message.content[0]["text"]).pretty_print()
61+
else:
62+
message.pretty_print()
63+
64+
# last stream object contains all messages. message[-1] is the last message
65+
return s["messages"][-1].content

src/codegen/cli/commands/agent/main.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import warnings
33

44
import rich_click as click
5-
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
5+
from langchain_core.messages import SystemMessage
66
from rich.console import Console
77
from rich.markdown import Markdown
88
from rich.prompt import Prompt
@@ -73,13 +73,11 @@ def say(message: str):
7373
]
7474

7575
# Initialize chat history with system message
76-
chat_history = [
77-
SystemMessage(
78-
content="""You are a helpful AI assistant with access to the local codebase.
76+
system_message = SystemMessage(
77+
content="""You are a helpful AI assistant with access to the local codebase.
7978
You can help with code exploration, editing, and general programming tasks.
8079
Always explain what you're planning to do before taking actions."""
81-
)
82-
]
80+
)
8381

8482
# Get initial query if not provided via command line
8583
if not query:
@@ -92,7 +90,7 @@ def say(message: str):
9290
query = Prompt.ask("[bold]>[/bold]") # Simple arrow prompt
9391

9492
# Create the agent
95-
agent = create_agent_with_tools(codebase, tools, chat_history=chat_history)
93+
agent = create_agent_with_tools(codebase=codebase, tools=tools, system_message=system_message)
9694

9795
# Main chat loop
9896
while True:
@@ -105,21 +103,19 @@ def say(message: str):
105103
if user_input.lower() in ["exit", "quit"]:
106104
break
107105

108-
# Add user message to chat history
109-
chat_history.append(HumanMessage(content=user_input))
110-
111106
# Invoke the agent
112107
with console.status("[bold green]Agent is thinking...") as status:
113108
try:
114-
session_id = str(uuid.uuid4())
109+
thread_id = str(uuid.uuid4())
115110
result = agent.invoke(
116111
{"input": user_input},
117-
config={"configurable": {"session_id": session_id}},
112+
config={"configurable": {"thread_id": thread_id}},
118113
)
114+
115+
result = result["messages"][-1].content
119116
# Update chat history with AI's response
120-
if result.get("output"):
121-
say(result["output"])
122-
chat_history.append(AIMessage(content=result["output"]))
117+
if result:
118+
say(result)
123119
except Exception as e:
124120
console.print(f"[bold red]Error during agent execution:[/bold red] {e}")
125121
break

0 commit comments

Comments
 (0)