From c64e4cd55ca135f82220559b931ad35f18415c28 Mon Sep 17 00:00:00 2001 From: Kiran Gopinathan Date: Fri, 2 Jan 2026 16:12:43 +0000 Subject: [PATCH 1/3] added agent example --- docs/source/agent.py | 72 +++++++++++++++++++++++++++++++++++ docs/source/agent_example.rst | 18 +++++++++ docs/source/index.rst | 1 + 3 files changed, 91 insertions(+) create mode 100644 docs/source/agent.py create mode 100644 docs/source/agent_example.rst diff --git a/docs/source/agent.py b/docs/source/agent.py new file mode 100644 index 00000000..11c2d1a2 --- /dev/null +++ b/docs/source/agent.py @@ -0,0 +1,72 @@ +import functools + +from effectful.handlers.llm import Template +from effectful.handlers.llm.providers import ( + LiteLLMProvider, + compute_response, + format_model_input, +) +from effectful.ops.semantics import fwd, handler +from effectful.ops.syntax import defop +from effectful.ops.types import NotHandled + + +class Agent: + def __init__(self): + self.state = [] # persist the list of messages + + @defop + @staticmethod + def current_agent() -> "Agent | None": + return None + + def __init_subclass__(cls): + for method_name in dir(cls): + template = getattr(cls, method_name) + if not isinstance(template, Template): + continue + + @functools.wraps(template) + def wrapper(self, *args, **kwargs): + with handler( + { + Agent.current_agent: lambda: self, + format_model_input: self._format_model_input, + compute_response: self._compute_response, + } + ): + return template(self, *args, **kwargs) + + setattr(cls, method_name, wrapper) + + def _format_model_input(self, template, other, *args, **kwargs): + # update prompt with previous list of messages + prompt = fwd() + if Agent.current_agent() is self: + assert self is other + self.state.extend(prompt) + prompt = self.state + return prompt + + def _compute_response(self, *args, **kwargs): + # save response into persisted state + response = fwd() + if Agent.current_agent() is self: + self.state.append(response.choices[0].message.model_dump()) + return response + + +if __name__ == "__main__" or True: + + class ChatBot(Agent): + @Template.define + def send(self, user_input: str) -> str: + """User writes: {user_input}""" + raise NotHandled + + provider = LiteLLMProvider() + chatbot = ChatBot() + + with handler(provider): + print(chatbot.send("Hi!, how are you? I am in france.")) + print(chatbot.send("Remind me again, where am I?")) diff --git a/docs/source/agent_example.rst b/docs/source/agent_example.rst new file mode 100644 index 00000000..a9993c56 --- /dev/null +++ b/docs/source/agent_example.rst @@ -0,0 +1,18 @@ +Contextual LLM Agents +====================== +Here we give an example of using effectful to implement chatbot-style context-aware LLM agents. + +In the code below, we define a helper class :class:`Agent` which wraps its +subclasses' template operations in a wrapper that stores and persists +the history of prior interactions with the LLM: + - :func:`_format_model_input` wraps every prompt sent to the LLM and + stashes the generated API message into a state variable. + - :func:`_compute_response` wraps the response from the LLM provider and + stashes the returned message into the state. + +Using this we can construct an agent which remembers the context of +the conversation: + +.. literalinclude:: ./agent.py + :language: python + diff --git a/docs/source/index.rst b/docs/source/index.rst index 92aa0207..626c0a56 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,6 +17,7 @@ Table of Contents lambda_example semi_ring_example beam_search_example + agent_example .. toctree:: :maxdepth: 2 From afb220fedbb4e84ec31fc574f69d80fdbd571325 Mon Sep 17 00:00:00 2001 From: Kiran Gopinathan <23038502+kiranandcode@users.noreply.github.com> Date: Sat, 3 Jan 2026 20:36:23 +0000 Subject: [PATCH 2/3] Fix main check condition in agent.py --- docs/source/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/agent.py b/docs/source/agent.py index 11c2d1a2..3a94e47d 100644 --- a/docs/source/agent.py +++ b/docs/source/agent.py @@ -56,7 +56,7 @@ def _compute_response(self, *args, **kwargs): return response -if __name__ == "__main__" or True: +if __name__ == "__main__": class ChatBot(Agent): @Template.define From da265ae1f2dbc423888092ed47f5e63330db99e4 Mon Sep 17 00:00:00 2001 From: Kiran Gopinathan Date: Mon, 5 Jan 2026 19:46:27 +0000 Subject: [PATCH 3/3] removed agent --- effectful/handlers/llm/agent.py | 64 --------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 effectful/handlers/llm/agent.py diff --git a/effectful/handlers/llm/agent.py b/effectful/handlers/llm/agent.py deleted file mode 100644 index f80cf159..00000000 --- a/effectful/handlers/llm/agent.py +++ /dev/null @@ -1,64 +0,0 @@ -import functools -from typing import Optional - -from effectful.handlers.llm import Template -from effectful.handlers.llm.providers import compute_response, format_model_input -from effectful.ops.semantics import fwd, handler -from effectful.ops.syntax import defop - - -class Agent: - '''When inheriting from Agent, Template-valued methods will have the - previous history of the conversation injected prior to their prompts. - - Example: - - >>> class ConversationAgent(Agent): - ... @Template.define - ... def respond(self, message: str) -> str: - ... """Continue the conversation in response to the message '{message}'""" - ... raise NotImplementedError - - Any calls to `agent.format` will have the previous conversation history in their context. - - ''' - - def __init__(self): - self.state = [] - - @defop - @staticmethod - def current_agent() -> Optional["Agent"]: - return None - - def __init_subclass__(cls): - for method_name in dir(cls): - template = getattr(cls, method_name) - if not isinstance(template, Template): - continue - - @functools.wraps(template) - def wrapper(self, *args, **kwargs): - with handler( - { - Agent.current_agent: lambda: self, - format_model_input: self._format_model_input, - compute_response: self._compute_response, - } - ): - return template(self, *args, **kwargs) - - setattr(cls, method_name, wrapper) - - def _format_model_input(self, template, other, *args, **kwargs): - prompt = fwd() - if Agent.current_agent() is self: - assert self is other - prompt = self.state + prompt - return prompt - - def _compute_response(self, *args, **kwargs): - response = fwd() - if Agent.current_agent() is self: - self.state += response.output - return response