Skip to content

Commit 31fb2c3

Browse files
authored
Merge pull request #103 from DagaBhai/agent
feat: simple react agent structure
2 parents fc2a085 + 63e948c commit 31fb2c3

4 files changed

Lines changed: 111 additions & 0 deletions

File tree

src/fenn/agents/agent.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from fenn.agents import Flow
2+
from fenn.agents.node import ThinkNode, ActNode, ObserveNode
3+
import yaml
4+
5+
class Agent:
6+
def __init__(self, config, llm):
7+
self.llm = llm
8+
with open(config) as f:
9+
self.config = yaml.safe_load(f)
10+
11+
think = ThinkNode()
12+
act = ActNode()
13+
observe = ObserveNode()
14+
15+
think - "act" >> act
16+
think - "done" >> None
17+
act - "observe" >> observe
18+
observe - "think" >> think
19+
observe - "done" >> None
20+
21+
self.flow = Flow(start=think)
22+
23+
def run(self, user_input):
24+
shared = {
25+
"llm": self.llm,
26+
"messages": [
27+
{"role": "system", "content": self.config["agent"]["system_prompt"]},
28+
{"role": "user", "content": user_input}
29+
],
30+
"iterations": 0,
31+
"max_iterations": self.config["agent"]["max_iterations"],
32+
"last_thought": None,
33+
"last_observation": None
34+
}
35+
36+
self.flow.run(shared)
37+
return shared["messages"][-1]["content"]

src/fenn/agents/llm.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Client:
2+
def __init__(self):
3+
self._fn = None
4+
5+
def register(self, fn):
6+
self._fn = fn
7+
8+
def chat_complete(self, messages, **kwargs):
9+
if self._fn:
10+
return self._fn(messages, **kwargs)
11+
raise ValueError("No LLM function registered")

src/fenn/agents/node.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from fenn.agents import Node
2+
from fenn.agents.tools import TOOLS
3+
4+
class ThinkNode(Node):
5+
def prep(self, shared):
6+
return {"llm": shared["llm"], "messages": shared["messages"]}
7+
8+
def exec(self, prep_res):
9+
llm = prep_res["llm"]
10+
response =llm.chat_complete(prep_res["messages"])
11+
return response
12+
13+
def post(self, shared, prep_res, exec_res):
14+
shared["last_thought"] = exec_res
15+
shared["messages"].append({"role": "assistant", "content": exec_res})
16+
if "Action:" in exec_res:
17+
return "act"
18+
return "done"
19+
20+
class ActNode(Node):
21+
def prep(self, shared):
22+
return shared["last_thought"]
23+
24+
def exec(self, thought):
25+
line = [l for l in thought.split("\n") if l.startswith("Action:")][0]
26+
tool_call = line.replace("Action:", "").strip()
27+
28+
tool_name = tool_call.split("(")[0]
29+
tool_arg = tool_call.split("(")[1].rstrip(")")
30+
31+
result = TOOLS[tool_name](tool_arg)
32+
return result
33+
34+
def post(self, shared, prep_res, exec_res):
35+
shared["last_observation"] = exec_res
36+
return "observe"
37+
38+
class ObserveNode(Node):
39+
def prep(self, shared):
40+
return shared["last_observation"]
41+
42+
def exec(self, observation):
43+
return observation
44+
45+
def post(self, shared, prep_res, exec_res):
46+
shared["messages"].append({
47+
"role": "user",
48+
"content": f"Observation: {exec_res}"
49+
})
50+
shared["iterations"] += 1
51+
if shared["iterations"] >= shared["max_iterations"]:
52+
return "done"
53+
return "think"

src/fenn/agents/tools.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
TOOLS = {
2+
"search": lambda query: f"Search result for: {query}",
3+
"calculator": lambda expr: str(eval(expr))
4+
}
5+
6+
# This is what you put in the LLM prompt so it knows what tools exist
7+
TOOL_DESCRIPTIONS = """
8+
- search(query): Search the web
9+
- calculator(expr): Evaluate a math expression
10+
"""

0 commit comments

Comments
 (0)