Skip to content

Commit d215998

Browse files
authored
Add a few standardizing methods to AgentService (#513)
A handful of user support conversations over the weeks have related to: 1. How do I fetch "my agent" from the service, and 2. How do I modify / build the AgentContext in "situation Y" This PR: 1. Adds a `get_default_agent` method to AgentService that acts as an official way to get the agent (before it just had a special name, but the code makes it unclear if it was `self.agent` or `self._agent` 2. Adds a `build_default_context` method which implements the standard context that's been copy-pasted every time we write a new endpoint. Now these endpoints can all rely on this `build_default_context` method, and developers can be told that overriding that method will -- for example -- alter the mechanics of their Telegram bot
1 parent 15bd58d commit d215998

5 files changed

Lines changed: 61 additions & 55 deletions

File tree

src/steamship/agents/examples/telegram_bot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(self, **kwargs):
7676
client=self.client,
7777
config=TelegramTransportConfig(bot_token=self.config.bot_token),
7878
agent_service=self,
79-
agent=self._agent,
79+
agent=self.get_default_agent(),
8080
)
8181
)
8282

src/steamship/agents/mixins/transports/slack.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from steamship import Block, Steamship
1010
from steamship.agents.llms import OpenAI
1111
from steamship.agents.mixins.transports.transport import Transport
12-
from steamship.agents.schema import Agent, AgentContext, EmitFunc, Metadata
12+
from steamship.agents.schema import Agent, EmitFunc, Metadata
1313
from steamship.agents.service.agent_service import AgentService
1414
from steamship.agents.utils import with_llm
1515
from steamship.invocable import Config, InvocableResponse, InvocationContext, get, post
@@ -370,12 +370,7 @@ def _respond_to_block(self, incoming_message: Block):
370370
"""Respond to a single inbound message from Slack, posting the response back to Slack."""
371371
try:
372372
chat_id = incoming_message.chat_id
373-
374-
if not chat_id:
375-
logging.error(f"No chat id on incoming block {incoming_message}")
376-
return
377-
# TODO: It feels like context is something the Agent should be providing.
378-
context = AgentContext.get_or_create(self.client, context_keys={"chat_id": chat_id})
373+
context = self.agent_service.build_default_context(context_id=chat_id)
379374

380375
context.chat_history.append_user_message(
381376
text=incoming_message.text, tags=incoming_message.tags

src/steamship/agents/mixins/transports/steamship_widget.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
from typing import List, Optional
33

44
from steamship import Block, Steamship, SteamshipError
5-
from steamship.agents.llms import OpenAI
65
from steamship.agents.mixins.transports.transport import Transport
7-
from steamship.agents.schema import Agent, AgentContext, Metadata
6+
from steamship.agents.schema import Agent, Metadata
87
from steamship.agents.service.agent_service import AgentService
9-
from steamship.agents.utils import with_llm
108
from steamship.invocable import Config, InvocationContext, post
119

1210
API_BASE = "https://api.telegram.org/bot"
@@ -60,23 +58,14 @@ def _parse_inbound(self, payload: dict, context: Optional[dict] = None) -> Optio
6058
def answer(self, **payload) -> List[Block]:
6159
"""Endpoint that implements the contract for Steamship embeddable chat widgets. This is a PUBLIC endpoint since these webhooks do not pass a token."""
6260
incoming_message = self.parse_inbound(payload)
63-
context = AgentContext.get_or_create(
64-
self.client, context_keys={"chat_id": incoming_message.chat_id}
65-
)
61+
62+
context = self.agent_service.build_default_context(context_id=incoming_message.chat_id)
63+
6664
context.chat_history.append_user_message(
6765
text=incoming_message.text, tags=incoming_message.tags
6866
)
6967
context.emit_funcs = [self.save_for_emit]
7068

71-
# Add an LLM to the context, using the Agent's if it exists.
72-
llm = None
73-
if hasattr(self.agent, "llm"):
74-
llm = self.agent.llm
75-
else:
76-
llm = OpenAI(client=self.client)
77-
78-
context = with_llm(context=context, llm=llm)
79-
8069
try:
8170
self.agent_service.run_agent(self.agent, context)
8271
except Exception as e:

src/steamship/agents/mixins/transports/telegram.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
from pydantic import Field
77

88
from steamship import Block, Steamship, SteamshipError
9-
from steamship.agents.llms import OpenAI
109
from steamship.agents.mixins.transports.transport import Transport
11-
from steamship.agents.schema import Agent, AgentContext, EmitFunc, Metadata
10+
from steamship.agents.schema import Agent, EmitFunc, Metadata
1211
from steamship.agents.service.agent_service import AgentService
13-
from steamship.agents.utils import with_llm
1412
from steamship.invocable import Config, InvocableResponse, InvocationContext, post
1513
from steamship.utils.kv_store import KeyValueStore
1614

@@ -211,21 +209,13 @@ def telegram_respond(self, **kwargs) -> InvocableResponse[str]:
211209
try:
212210
incoming_message = self.parse_inbound(message)
213211
if incoming_message is not None:
214-
context = AgentContext.get_or_create(self.client, context_keys={"chat_id": chat_id})
212+
context = self.agent_service.build_default_context(chat_id)
213+
215214
context.chat_history.append_user_message(
216215
text=incoming_message.text, tags=incoming_message.tags
217216
)
218217
context.emit_funcs = [self.build_emit_func(chat_id=chat_id)]
219218

220-
# Add an LLM to the context, using the Agent's if it exists.
221-
llm = None
222-
if hasattr(self.agent, "llm"):
223-
llm = self.agent.llm
224-
else:
225-
llm = OpenAI(client=self.client)
226-
227-
context = with_llm(context=context, llm=llm)
228-
229219
response = self.agent_service.run_agent(self.agent, context)
230220
if response is not None:
231221
self.send(response)

src/steamship/agents/service/agent_service.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import List, Optional
44

55
from steamship import Block, SteamshipError, Task
6-
from steamship.agents.llms.openai import ChatOpenAI
6+
from steamship.agents.llms.openai import ChatOpenAI, OpenAI
77
from steamship.agents.logging import AgentLogging
88
from steamship.agents.schema import Action, Agent, FinishAction
99
from steamship.agents.schema.context import AgentContext, Metadata
@@ -165,12 +165,33 @@ def run_agent(self, agent: Agent, context: AgentContext):
165165
logging.info(f"Emitting via function: {func.__name__}")
166166
func(action.output, context.metadata)
167167

168-
@post("prompt")
169-
def prompt(
170-
self, prompt: Optional[str] = None, context_id: Optional[str] = None, **kwargs
171-
) -> List[Block]:
172-
"""Run an agent with the provided text as the input."""
173-
prompt = prompt or kwargs.get("question")
168+
def get_default_agent(self, throw_if_missing: bool = True) -> Optional[Agent]:
169+
"""Return the default agent of this agent service.
170+
171+
This is a helper wrapper to safeguard naming conventions that have formed.
172+
"""
173+
if hasattr(self, "agent"):
174+
return self.agent
175+
elif hasattr(self, "_agent"):
176+
return self._agent
177+
else:
178+
if throw_if_missing:
179+
raise SteamshipError(
180+
message="No Agent object found in the Agent Service. "
181+
"Please name it either self.agent or self._agent."
182+
)
183+
else:
184+
return None
185+
186+
def build_default_context(self, context_id: Optional[str] = None, **kwargs) -> AgentContext:
187+
"""Build's the agent's default context.
188+
189+
The provides a single place to implement (or override) the default context that will be used by endpoints
190+
that transports define. This allows an Agent developer to use, eg, the TelegramTransport but with a custom
191+
type of memory or caching.
192+
193+
The returned context does not have any emit functions yet registered to it.
194+
"""
174195

175196
# AgentContexts serve to allow the AgentService to run agents
176197
# with appropriate information about the desired tasking.
@@ -194,6 +215,28 @@ def prompt(
194215
use_llm_cache=use_llm_cache,
195216
use_action_cache=use_action_cache,
196217
)
218+
219+
# Add a default LLM to the context, using the Agent's if it exists.
220+
llm = None
221+
if agent := self.get_default_agent():
222+
if hasattr(agent, "llm"):
223+
llm = agent.llm
224+
if llm is None:
225+
llm = OpenAI(client=self.client)
226+
227+
context = with_llm(context=context, llm=llm)
228+
229+
return context
230+
231+
@post("prompt")
232+
def prompt(
233+
self, prompt: Optional[str] = None, context_id: Optional[str] = None, **kwargs
234+
) -> List[Block]:
235+
"""Run an agent with the provided text as the input."""
236+
prompt = prompt or kwargs.get("question")
237+
238+
context = self.build_default_context(context_id, **kwargs)
239+
197240
context.chat_history.append_user_message(prompt)
198241

199242
# Add a default LLM
@@ -215,18 +258,7 @@ def sync_emit(blocks: List[Block], meta: Metadata):
215258
context.emit_funcs.append(sync_emit)
216259

217260
# Get the agent
218-
agent: Optional[Agent] = None
219-
if hasattr(self, "agent"):
220-
agent = self.agent
221-
elif hasattr(self, "_agent"):
222-
agent = self._agent
223-
224-
if not agent:
225-
raise SteamshipError(
226-
message="No Agent object found in the Agent Service. "
227-
"Please name it either self.agent or self._agent."
228-
)
229-
261+
agent: Optional[Agent] = self.get_default_agent()
230262
self.run_agent(agent, context)
231263

232264
# Now append the output blocks to the chat history

0 commit comments

Comments
 (0)