ryanbalch commited on
Commit
70aa23a
·
1 Parent(s): 07554c9

properly implementation of gradio, langgraph, freeplay and zep (including proper sessions);adding user headshots

Browse files
api/scripts/create_play_profile_pic.py CHANGED
@@ -68,10 +68,30 @@ Your output should describe only the image to be generated. No text, captions, o
68
  # print("--> skip")
69
  # # break
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # Generate images for all players
72
  for player in Player.get_players():
73
  print(player.name)
74
- print(player.profile_pic)
75
- response = generate_image(player.profile_pic)
 
 
 
 
 
 
 
76
  player.save_image(response)
77
- break
 
68
  # print("--> skip")
69
  # # break
70
 
71
+
72
+ image_prompt = """
73
+ A realistic studio-style headshot portrait of {profile[name]}, a {profile[age]}-year-old professional soccer player from {profile[nationality]}. He plays as a {profile[position]} and currently wears jersey number {profile[shirt_number]} for the {team_name} soccer team.
74
+
75
+ He is wearing the team's official kit: a {shirt_color} jersey, clean and well-fitted, featuring his number {profile[shirt_number]} on the front. The image should capture a calm and confident expression, suitable for official media. The player has a well-groomed appearance, facing forward with even lighting and a neutral or softly blurred backdrop in studio conditions. No action pose, just a clear, professional profile suitable for player bio pages or trading cards.
76
+ """
77
+
78
+ shirt_colors = {
79
+ "Everglade FC": "emerald green",
80
+ "Fraser Valley United": "dark red",
81
+ "Yucatan Force": "dark orange",
82
+ "Tierra Alta FC": "spring green",
83
+ }
84
+
85
  # Generate images for all players
86
  for player in Player.get_players():
87
  print(player.name)
88
+ # print(player.player_info())
89
+ shirt_color = shirt_colors[player.team]
90
+ text = image_prompt.format(
91
+ profile=player.player_info(),
92
+ team_name=player.team,
93
+ shirt_color=shirt_color)
94
+ # print(text)
95
+ # print(player.profile_pic)
96
+ response = generate_image(text)
97
  player.save_image(response)
 
api/scripts/freeplay_playground.py CHANGED
@@ -1,27 +1,73 @@
1
  import asyncio
2
  from pprint import pprint
3
- from utils.freeplay_helpers import get_formatted_prompt, get_prompt
4
  import datetime
 
5
 
6
- async def main():
7
- template = "casual_fan_prompt"
8
- environment = "latest"
9
- variables = {
10
- "now": datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d'),
11
- "zep_context": "Zep is your daddy",
12
- }
13
- history = [
14
- {"role": "user", "content": "what are some dinner ideas..."},
15
- {"role": "assistant", "content": "here are some dinner ideas..."},
16
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- prompt = get_formatted_prompt(template, environment, variables, history=history)
19
- # print(prompt.system_content)
20
- pprint(prompt.llm_prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- # template_prompt = await get_prompt(template, environment)
23
- # print(template_prompt.messages)
 
 
 
 
24
 
25
  if __name__ == "__main__":
26
- asyncio.run(main())
27
 
 
1
  import asyncio
2
  from pprint import pprint
3
+ from utils.freeplay_helpers import FreeplayClient
4
  import datetime
5
+ import time
6
 
7
+ from tools import (
8
+ PlayerSearchTool,
9
+ GameSearchTool,
10
+ )
11
+
12
+
13
+ available_tools = [
14
+ GameSearchTool(),
15
+ PlayerSearchTool(),
16
+ ]
17
+
18
+
19
+ template = "casual_fan_prompt"
20
+ environment = "latest"
21
+ variables = {
22
+ "now": datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d'),
23
+ "zep_context": "Zep is your daddy",
24
+ }
25
+ # history = [
26
+ # {"role": "user", "content": "what are some dinner ideas..."},
27
+ # {"role": "assistant", "content": "all you should eat is steamed broccoli - you will live forever"},
28
+ # {"role": "user", "content": "i don't want to live forever if all i eat is steamed broccoli"},
29
+ # {"role": "assistant", "content": "silly human"},
30
+ # ]
31
+ # history = [
32
+ # {'role': 'human', 'content': 'tell me about some players in everglade fc'},
33
+ # # {'role': 'tool', 'content': '{"number": 23, "name": "Brian Davis", "age": 27, "nationality": "USA", "shirt_number"...c image full of energy and anticipation."}'},
34
+ # # {'role': 'tool', 'content': '{"number": 10, "name": "Matthew Martin", "age": 24, "nationality": "USA", "shirt_numb...he dramatic flair he brings to the game."}'},
35
+ # {'role': 'ai', 'content': 'Everglade FC is bursting with talent! Here are some standout players to watch:\n\n1. **...rglade FC brings to the field! Go, team! 🌟'},
36
+ # ]
37
+ history = [
38
+ {'role': 'user', 'content': 'tell me about some players in everglade fc'},
39
+ # {'role': 'assistant', 'content': ''},
40
+ {'role': 'tool_call', 'content': '{"number": 23, "name": "Brian Davis", "age": 27, "nationality": "USA", "shirt_number"...c image full of energy and anticipation."}'},
41
+ # {'role': 'tool', 'content': '{"number": 10, "name": "Matthew Martin", "age": 24, "nationality": "USA", "shirt_numb...he dramatic flair he brings to the game."}'},
42
+ {'role': 'assistant', 'content': 'Everglade FC is bursting with talent! Here are some standout players to watch:\n\n1. **...rglade FC brings to the field! Go, team! 🌟'},
43
+ ]
44
 
45
+ fp_client = FreeplayClient()
46
+ prompt = fp_client.get_prompt(template, environment)
47
+
48
+ formatted_prompt = prompt.bind(variables, history=history).format()
49
+
50
+
51
+ def main():
52
+ print(formatted_prompt.llm_prompt)
53
+ print('================')
54
+ tool_schema = [tool.input_schema.schema_json() for tool in available_tools]
55
+ print(tool_schema)
56
+ print('================')
57
+
58
+ state = {
59
+ "start_time": time.time(),
60
+ "messages": history,
61
+ }
62
+ # variables['history'] = history
63
 
64
+ fp_client.create_session()
65
+ fp_client.record_session(
66
+ state=state,
67
+ prompt_vars=variables,
68
+ formatted_prompt=formatted_prompt,
69
+ )
70
 
71
  if __name__ == "__main__":
72
+ main()
73
 
api/scripts/workflow_playground.py CHANGED
@@ -2,24 +2,46 @@ import asyncio
2
  from workflows.base import build_workflow_with_state
3
  from event_handlers import PrintEventHandler
4
  from langchain_core.messages import HumanMessage
 
 
5
 
6
  from prompts import (
7
  casual_fan_prompt,
8
  HumanMessage,
9
  AIMessage,
10
  )
 
11
 
12
 
13
- workflow, state = build_workflow_with_state(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  handler=PrintEventHandler(),
15
- session_id='5aed14ff09fb415ba77439409f458909',
 
 
 
 
 
16
  messages=[
17
  HumanMessage(content="tell me about some players in everglade fc"),
18
  ],
19
  )
20
 
21
  async def main():
22
- await workflow.ainvoke(state)
23
 
24
  if __name__ == "__main__":
25
  asyncio.run(main())
 
2
  from workflows.base import build_workflow_with_state
3
  from event_handlers import PrintEventHandler
4
  from langchain_core.messages import HumanMessage
5
+ from utils.freeplay_helpers import FreeplayClient
6
+ from utils.zep_helpers import ZepClient
7
 
8
  from prompts import (
9
  casual_fan_prompt,
10
  HumanMessage,
11
  AIMessage,
12
  )
13
+ user_id = "[email protected]"
14
 
15
 
16
+ # workflow, state = build_workflow_with_state(
17
+ # handler=PrintEventHandler(),
18
+ # session_id='5aed14ff09fb415ba77439409f458909',
19
+ # messages=[
20
+ # HumanMessage(content="tell me about some players in everglade fc"),
21
+ # ],
22
+ # )
23
+
24
+ zep_session_id = ZepClient() \
25
+ .get_or_create_user(user_id, "Hugh", "Bigly") \
26
+ .create_session() \
27
+ .session_id
28
+ freeplay_session_id = FreeplayClient().create_session().session_id
29
+
30
+ workflow_bundle, state = build_workflow_with_state(
31
  handler=PrintEventHandler(),
32
+ zep_session_id=zep_session_id,
33
+ freeplay_session_id=freeplay_session_id,
34
+ email=user_id,
35
+ first_name="Hugh",
36
+ last_name="Bigly",
37
+ persona="Casual Fan",
38
  messages=[
39
  HumanMessage(content="tell me about some players in everglade fc"),
40
  ],
41
  )
42
 
43
  async def main():
44
+ await workflow_bundle.workflow.ainvoke(state)
45
 
46
  if __name__ == "__main__":
47
  asyncio.run(main())
api/scripts/zep_playground2.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from utils.zep_helpers import ZepClient
3
+
4
+ zep_client = ZepClient()
5
+ user_id = '[email protected]'
6
+
7
+ session_id = zep_client\
8
+ .get_or_create_user(user_id, "Hugh", "Bigly") \
9
+ .create_session() \
10
+ .session_id
11
+ print(session_id)
12
+
13
+ async def main():
14
+ memory = await zep_client.get_memory_async(session_id)
15
+ print(f'Memory: {memory}')
16
+ print(memory.context)
17
+
18
+ if __name__ == "__main__":
19
+ asyncio.run(main())
api/server_gradio.py CHANGED
@@ -6,7 +6,9 @@ from pydantic import BaseModel
6
  from threading import Thread
7
  from langchain_core.messages import HumanMessage, AIMessage
8
  from event_handlers.gradio_handler import GradioEventHandler
9
- from workflows.base import build_workflow
 
 
10
 
11
  lorem_ipsum = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."""
12
  show_state = True
@@ -14,40 +16,98 @@ dev_mode = os.getenv("DEV_MODE", "").lower() == "true"
14
 
15
 
16
  class AppState(BaseModel):
 
 
 
17
  count: int = 0
18
  persona: str = "Casual Fan"
19
- user_query: str = ""
20
  history: list = []
 
 
21
 
22
- def clear(self) -> None:
23
- """Reset the state to default values."""
24
- self.count = 0
25
- self.persona = "Casual Fan"
26
- self.user_query = ""
27
- self.history = []
 
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  with gr.Blocks() as demo:
31
  state = gr.State(AppState())
32
  handler = GradioEventHandler()
33
- workflow_state = {
34
- "session_id": "5aed14ff09fb415ba77439409f458909",
35
- "messages": [],
36
- }
37
 
38
  gr.Markdown("# Huge League Soccer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  llm_response = gr.Textbox(label="LLM Response", lines=10)
40
- count = gr.Number(
41
- label="Count",
42
- interactive=False,
43
- visible=show_state,
44
- )
45
- persona_disp = gr.Text(
46
- label="Persona",
47
- interactive=False,
48
- value=state.value.persona,
49
- visible=show_state,
50
- )
51
 
52
  with gr.Row(scale=1):
53
  with gr.Column(scale=1):
@@ -67,69 +127,173 @@ with gr.Blocks() as demo:
67
  submit_btn = gr.Button("Submit", variant="primary", scale=0)
68
 
69
  clear_state_btn = gr.Button("Clear State", variant="stop", scale=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- @submit_btn.click(inputs=[user_query, state], outputs=[llm_response])
72
- def submit(user_query, state):
73
- state.user_query = user_query or "tell me about some players in everglade fc"
74
- message = HumanMessage(content=state.user_query)
75
- state.history.append(message)
76
- workflow_state["messages"] = state.history
77
-
78
- def start_async_loop():
79
- workflow = build_workflow(handler)
80
- async def run_workflow():
81
- print(workflow_state)
82
- await workflow.ainvoke(workflow_state)
83
- asyncio.run(run_workflow())
84
-
85
- thread = Thread(target=start_async_loop, daemon=True)
86
- thread.start()
87
-
88
- result = ""
89
- while True:
90
- token = handler.queue.get()
91
- from colorama import Fore, Style
92
- print(f'{Fore.GREEN}{token}{Style.RESET_ALL}')
93
- if token is None:
94
- break
95
- if isinstance(token, dict):
96
- if token["type"] == "info":
97
- gr.Info(token["message"])
98
- continue
99
- result += token
100
- yield result
101
-
102
- state.history.append(AIMessage(content=result))
103
-
104
- @user_query.submit(inputs=[user_query, state], outputs=[llm_response])
105
- def user_query_change(user_query, state):
106
- state.user_query = user_query
107
-
108
- result = state.user_query
109
- for i in range(0, len(lorem_ipsum), 4):
110
- time.sleep(0.1)
111
- result += lorem_ipsum[i:i+4]
112
- yield result
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  @persona.change(inputs=[persona, state], outputs=[persona_disp])
115
  def persona_change(persona, state):
116
  state.persona = persona
117
  return persona
118
 
119
- @submit_btn.click(inputs=[state], outputs=[count])
120
- def submit(state):
121
- state.count += 1
122
- return state.count
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- @user_query.submit(inputs=[state], outputs=[count, user_query])
125
- def user_query_change(state):
126
- state.count += 1
127
- return state.count, ''
 
 
 
 
 
 
128
 
129
- @clear_state_btn.click(inputs=[state], outputs=[count, persona_disp, user_query, llm_response])
130
- def clear_state(state):
131
- state.clear()
132
- return state.count, state.persona, state.user_query, ""
133
 
134
 
135
  if __name__ == "__main__":
 
6
  from threading import Thread
7
  from langchain_core.messages import HumanMessage, AIMessage
8
  from event_handlers.gradio_handler import GradioEventHandler
9
+ from workflows.base import build_workflow_with_state
10
+ from utils.freeplay_helpers import FreeplayClient
11
+ from utils.zep_helpers import ZepClient
12
 
13
  lorem_ipsum = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."""
14
  show_state = True
 
16
 
17
 
18
  class AppState(BaseModel):
19
+ email: str = "[email protected]"
20
+ first_name: str = "Hugh"
21
+ last_name: str = "Bigly"
22
  count: int = 0
23
  persona: str = "Casual Fan"
 
24
  history: list = []
25
+ zep_session_id: str = ""
26
+ freeplay_session_id: str = ""
27
 
28
+ def ensure_sessions(self):
29
+ if not self.zep_session_id:
30
+ self.zep_session_id = ZepClient() \
31
+ .get_or_create_user(self.email, self.first_name, self.last_name) \
32
+ .create_session() \
33
+ .session_id
34
+ if not self.freeplay_session_id:
35
+ self.freeplay_session_id = FreeplayClient().create_session().session_id
36
 
37
+ ### Helpers ###
38
+
39
+ def submit_helper(state, handler, user_query):
40
+ state.count += 1
41
+ state.ensure_sessions()
42
+ message = HumanMessage(content=user_query)
43
+ state.history.append(message)
44
+ state = AppState(**state.dict())
45
+ yield state, ""
46
+
47
+ # result = ""
48
+ # for i in range(0, len(lorem_ipsum), 4):
49
+ # time.sleep(0.1)
50
+ # result += lorem_ipsum[i:i+4]
51
+ # yield state, result
52
+
53
+ def start_async_loop():
54
+ workflow_bundle, workflow_state = build_workflow_with_state(
55
+ handler=handler,
56
+ zep_session_id=state.zep_session_id,
57
+ freeplay_session_id=state.freeplay_session_id,
58
+ email=state.email,
59
+ first_name=state.first_name,
60
+ last_name=state.last_name,
61
+ persona=state.persona,
62
+ messages=state.history,
63
+ )
64
+
65
+ async def run_workflow():
66
+ await workflow_bundle.workflow.ainvoke(workflow_state)
67
+
68
+ asyncio.run(run_workflow())
69
+
70
+ thread = Thread(target=start_async_loop, daemon=True)
71
+ thread.start()
72
+
73
+ result = ""
74
+ while True:
75
+ token = handler.queue.get()
76
+ # from colorama import Fore, Style
77
+ # print(f'{Fore.GREEN}{token}{Style.RESET_ALL}')
78
+ if token is None:
79
+ break
80
+ if isinstance(token, dict):
81
+ if token["type"] == "info":
82
+ gr.Info(token["message"])
83
+ continue
84
+ result += token
85
+ yield state, result
86
+
87
+ state.history.append(AIMessage(content=result))
88
+
89
+ ### Interface ###
90
 
91
  with gr.Blocks() as demo:
92
  state = gr.State(AppState())
93
  handler = GradioEventHandler()
 
 
 
 
94
 
95
  gr.Markdown("# Huge League Soccer")
96
+ with gr.Row(scale=1):
97
+ email = gr.Textbox(label="Email",
98
+ type="email",
99
+ interactive=True,
100
+ lines=1,
101
+ value=state.value.email)
102
+ first_name = gr.Textbox(label="First Name",
103
+ lines=1,
104
+ interactive=True,
105
+ value=state.value.first_name)
106
+ last_name = gr.Textbox(label="Last Name",
107
+ lines=1,
108
+ interactive=True,
109
+ value=state.value.last_name)
110
  llm_response = gr.Textbox(label="LLM Response", lines=10)
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  with gr.Row(scale=1):
113
  with gr.Column(scale=1):
 
127
  submit_btn = gr.Button("Submit", variant="primary", scale=0)
128
 
129
  clear_state_btn = gr.Button("Clear State", variant="stop", scale=1)
130
+
131
+ with gr.Row(visible=show_state, variant="compact", scale=1):
132
+ count_disp = gr.Number(
133
+ label="Count",
134
+ interactive=False,
135
+ value=state.value.count,
136
+ scale=0,
137
+ )
138
+ persona_disp = gr.Text(
139
+ label="Persona",
140
+ interactive=False,
141
+ value=state.value.persona,
142
+ scale=0
143
+ )
144
+ zep_session_id_disp = gr.Text(
145
+ label="Zep Session ID",
146
+ interactive=False,
147
+ value=state.value.zep_session_id,
148
+ scale=1
149
+ )
150
+ freeplay_session_id_disp = gr.Text(
151
+ label="Freeplay Session ID",
152
+ interactive=False,
153
+ value=state.value.freeplay_session_id,
154
+ scale=1
155
+ )
156
+
157
+ ### Events
158
+
159
+ @state.change(inputs=[state], outputs=[count_disp, persona_disp, zep_session_id_disp, freeplay_session_id_disp])
160
+ def state_change(state):
161
+ return state.count, state.persona, state.zep_session_id, state.freeplay_session_id
162
+
163
+ @clear_state_btn.click(outputs=[state, llm_response, persona, user_query, email, first_name, last_name])
164
+ def clear_state():
165
+ new_state = AppState()
166
+ return new_state, "", new_state.persona, "", new_state.email, new_state.first_name, new_state.last_name
167
+
168
+ # @submit_btn.click(inputs=[state, user_query], outputs=[state, llm_response])
169
+ # def submit(state, user_query):
170
+ # state.count += 1
171
+ # state.ensure_sessions()
172
+ # state = AppState(**state.dict())
173
+
174
+ # user_query = user_query or "tell me about some players in everglade fc"
175
+ # message = HumanMessage(content=user_query)
176
+ # state.history.append(message)
177
+
178
+ # result = ""
179
+ # yield state, result
180
+ # for i in range(0, len(lorem_ipsum), 4):
181
+ # time.sleep(0.1)
182
+ # result += lorem_ipsum[i:i+4]
183
+ # yield state, result
184
+
185
+
186
+ @submit_btn.click(inputs=[state, user_query], outputs=[state, llm_response])
187
+ def submit(state, user_query):
188
+ user_query = user_query or "tell me about some players in everglade fc"
189
+ yield from submit_helper(state, handler, user_query)
190
+
191
 
192
+ # @submit_btn.click(inputs=[user_query, state], outputs=[llm_response, zep_session_id_disp, freeplay_session_id_disp])
193
+ # def submit(user_query, state):
194
+ # state.ensure_sessions()
195
+ # state.user_query = user_query or "tell me about some players in everglade fc"
196
+ # message = HumanMessage(content=state.user_query)
197
+ # state.history.append(message)
198
+ # # workflow_state["messages"] = state.history
199
+
200
+ # def start_async_loop():
201
+ # workflow, workflow_state = build_workflow_with_state(
202
+ # handler=handler,
203
+ # zep_session_id=state.zep_session_id,
204
+ # freeplay_session_id=state.freeplay_session_id,
205
+ # email=state.email,
206
+ # first_name=state.first_name,
207
+ # last_name=state.last_name,
208
+ # persona=state.persona,
209
+ # messages=state.history,
210
+ # )
211
+
212
+ # async def run_workflow():
213
+ # await workflow.ainvoke(workflow_state)
214
+
215
+ # asyncio.run(run_workflow())
216
+
217
+ # thread = Thread(target=start_async_loop, daemon=True)
218
+ # thread.start()
219
+
220
+ # result = ""
221
+ # while True:
222
+ # token = handler.queue.get()
223
+ # # from colorama import Fore, Style
224
+ # # print(f'{Fore.GREEN}{token}{Style.RESET_ALL}')
225
+ # if token is None:
226
+ # break
227
+ # if isinstance(token, dict):
228
+ # if token["type"] == "info":
229
+ # gr.Info(token["message"])
230
+ # continue
231
+ # result += token
232
+ # yield result, state.zep_session_id, state.freeplay_session_id
233
+
234
+ # state.history.append(AIMessage(content=result))
235
+
236
+ # @user_query.submit(inputs=[user_query, state], outputs=[llm_response, state])
237
+ # def user_query_change(user_query, state):
238
+ # state.user_query = user_query
239
+ # state.zep_session_id = f"zep_{int(time.time())}"
240
+ # state.freeplay_session_id = f"freeplay_{int(time.time())}"
241
+
242
+ # result = state.user_query
243
+ # for i in range(0, len(lorem_ipsum), 4):
244
+ # time.sleep(0.1)
245
+ # result += lorem_ipsum[i:i+4]
246
+ # yield result, state
247
 
248
  @persona.change(inputs=[persona, state], outputs=[persona_disp])
249
  def persona_change(persona, state):
250
  state.persona = persona
251
  return persona
252
 
253
+ # @submit_btn.click(inputs=[state], outputs=[state, llm_response])
254
+ # def submit(state):
255
+ # state.count += 1
256
+ # new_state = AppState(**state.dict())
257
+ # result = 'hello'
258
+ # for i in range(0, len(lorem_ipsum), 4):
259
+ # time.sleep(0.1)
260
+ # result += lorem_ipsum[i:i+4]
261
+ # yield new_state, result
262
+
263
+ # @user_query.submit(inputs=[state], outputs=[count_disp, user_query])
264
+ # def user_query_change(state):
265
+ # state.count += 1
266
+ # return state.count, ''
267
+
268
+ # @clear_state_btn.click(inputs=[state], outputs=[count_disp, persona_disp, user_query, llm_response, zep_session_id_disp, freeplay_session_id_disp])
269
+ # def clear_state(state):
270
+ # state.clear()
271
+ # return state.count, state.persona, state.user_query, "", state.zep_session_id, state.freeplay_session_id
272
+
273
+ # @email.change(inputs=[email, state])
274
+ # def email_change(email, state):
275
+ # state.email = email
276
+
277
+ # @first_name.change(inputs=[first_name, state])
278
+ # def first_name_change(first_name, state):
279
+ # state.first_name = first_name
280
+
281
+ # @last_name.change(inputs=[last_name, state])
282
+ # def last_name_change(last_name, state):
283
+ # state.last_name = last_name
284
 
285
+ # @state.change(inputs=[state], outputs=[gr.ParamViewer()])
286
+ # def state_change(state):
287
+ # # return state.zep_session_id, state.freeplay_session_id
288
+ # docs = {
289
+ # "zep_session_id_ZZZZ": {
290
+ # "default": "None\n",
291
+ # "type": "str | None\n",
292
+ # "description": "Zep session ID.",
293
+ # },
294
+ # }
295
 
296
+ # return gr.ParamViewer(docs)
 
 
 
297
 
298
 
299
  if __name__ == "__main__":
api/tools/__init__.py CHANGED
@@ -1,7 +1,9 @@
 
1
  from .player_search import PlayerSearchTool
2
  from .game_search import GameSearchTool
3
 
4
  __all__ = [
5
  "PlayerSearchTool",
6
  "GameSearchTool",
 
7
  ]
 
1
+ from langchain_core.documents import Document
2
  from .player_search import PlayerSearchTool
3
  from .game_search import GameSearchTool
4
 
5
  __all__ = [
6
  "PlayerSearchTool",
7
  "GameSearchTool",
8
+ "Document",
9
  ]
api/utils/freeplay_helpers.py CHANGED
@@ -1,58 +1,143 @@
1
  # import datetime
2
  import os
 
3
  from functools import lru_cache
4
  from typing import Union, Optional, List
5
  from freeplay import Freeplay, RecordPayload, ResponseInfo, CallInfo
 
6
  from langchain_core.messages import BaseMessage
7
 
8
  FREEPLAY_PROJECT_ID = os.getenv("FREEPLAY_PROJECT_ID")
9
 
10
 
11
  # @lru_cache(maxsize=1)
12
- def get_fp_client():
13
  return Freeplay(
14
  freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
15
  api_base=os.getenv("FREEPLAY_URL"),
16
  )
17
 
18
 
19
- # retreive and format your prompt
20
- def get_formatted_prompt(
21
- template: str,
22
- environment: str = "latest",
23
- variables: dict = {},
24
- history: Optional[List[BaseMessage]] = None,
25
- ):
26
- """
27
- Get a formatted prompt from Freeplay.
28
- """
29
- # get fp client
30
- fpClient = get_fp_client()
31
- # variables = {**variables, "now": datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d')}
32
- # get formatted prompt
33
- formatted_prompt = fpClient.prompts.get_formatted(
34
- project_id=FREEPLAY_PROJECT_ID,
35
- template_name=template,
36
- environment=environment,
37
- variables=variables,
38
- history=history
39
- )
40
- return formatted_prompt
41
-
42
-
43
- async def get_prompt(
44
- template: str,
45
- environment: str = "latest",
46
- ):
47
- """
48
- Get an unformatted prompt template from Freeplay.
49
- """
50
- # get fp client
51
- fpClient = get_fp_client()
52
- # get unformatted prompt template
53
- template_prompt = fpClient.prompts.get(
54
- project_id=FREEPLAY_PROJECT_ID,
55
- template_name=template,
56
- environment=environment
57
- )
58
- return template_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # import datetime
2
  import os
3
+ import time
4
  from functools import lru_cache
5
  from typing import Union, Optional, List
6
  from freeplay import Freeplay, RecordPayload, ResponseInfo, CallInfo
7
+ from freeplay.resources.prompts import FormattedPrompt
8
  from langchain_core.messages import BaseMessage
9
 
10
  FREEPLAY_PROJECT_ID = os.getenv("FREEPLAY_PROJECT_ID")
11
 
12
 
13
  # @lru_cache(maxsize=1)
14
+ def _get_fp_client():
15
  return Freeplay(
16
  freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
17
  api_base=os.getenv("FREEPLAY_URL"),
18
  )
19
 
20
 
21
+ class FreeplayClient:
22
+
23
+ def __init__(self, fp_client: Freeplay = None):
24
+ self.fp_client = fp_client or _get_fp_client()
25
+ self.session = None
26
+ self.session_id = None
27
+ # cache variables for recording
28
+ self._prompt_cache = {}
29
+ self._prompt_vars = None
30
+ self._formatted_prompt = None
31
+
32
+ def create_session(self):
33
+ # create a Freeplay session
34
+ self.session = self.fp_client.sessions.create()
35
+ self.session_id = self.session.session_id
36
+ return self
37
+
38
+ @staticmethod
39
+ def get_fp_client():
40
+ return _get_fp_client()
41
+
42
+ # retreive and format your prompt
43
+ def get_formatted_prompt(
44
+ self,
45
+ template: str,
46
+ environment: str = "latest",
47
+ variables: dict = {},
48
+ history: Optional[List[BaseMessage]] = None,
49
+ ):
50
+ """
51
+ Get a formatted prompt from Freeplay.
52
+ """
53
+ formatted_prompt = self.fp_client.prompts.get_formatted(
54
+ project_id=FREEPLAY_PROJECT_ID,
55
+ template_name=template,
56
+ environment=environment,
57
+ variables=variables,
58
+ history=history,
59
+ )
60
+ return formatted_prompt
61
+
62
+ def get_prompt(
63
+ self,
64
+ template: str,
65
+ environment: str = "latest",
66
+ ):
67
+ """
68
+ Get an unformatted prompt template from Freeplay.
69
+ """
70
+ key = self._make_cache_key(template, environment)
71
+ if key in self._prompt_cache:
72
+ return self._prompt_cache[key]
73
+ template_prompt = self.fp_client.prompts.get(
74
+ project_id=FREEPLAY_PROJECT_ID,
75
+ template_name=template,
76
+ environment=environment
77
+ )
78
+ self._prompt_cache[key] = template_prompt
79
+ return template_prompt
80
+
81
+ def _make_cache_key(self, template: str, environment: str) -> tuple:
82
+ """
83
+ Create a cache key for the prompt cache.
84
+ The key is a tuple of (template, environment).
85
+ Args:
86
+ template (str): The prompt template name.
87
+ environment (str): The environment name.
88
+ Returns:
89
+ tuple: (template, environment)
90
+ """
91
+ return (template, environment)
92
+
93
+ def record_session(
94
+ self,
95
+ state,
96
+ end: Optional[float] = time.time(),
97
+ formatted_prompt: Optional[FormattedPrompt] = None,
98
+ prompt_vars: Optional[dict] = None,
99
+ ):
100
+ prompt_vars = prompt_vars or self._prompt_vars
101
+ formatted_prompt = formatted_prompt or self._formatted_prompt
102
+
103
+ # convert messages to Freeplay format
104
+ if state['messages'] and isinstance(state['messages'][0], dict):
105
+ all_messages = state['messages']
106
+ else:
107
+ all_messages = [{'role': m.type, 'content': m.content} for m in state['messages'] if m.content]
108
+
109
+ # fix session if we landed here and it's missing
110
+ if not self.session:
111
+ self.session = self.fp_client.sessions.restore_session(session_id=state['freeplay_session_id'])
112
+ self.session_id = self.session.session_id
113
+
114
+ # record your LLM call with Freeplay
115
+ payload = RecordPayload(
116
+ all_messages=all_messages,
117
+ inputs=prompt_vars,
118
+ session_info=self.session,
119
+ prompt_info=formatted_prompt.prompt_info,
120
+ call_info=CallInfo.from_prompt_info(formatted_prompt.prompt_info, start_time=state['start_time'], end_time=end),
121
+ response_info=ResponseInfo(
122
+ # is_complete=chat_response.choices[0].finish_reason == 'stop'
123
+ is_complete=True
124
+ )
125
+ )
126
+ self.fp_client.recordings.create(payload)
127
+
128
+ def get_prompt_by_persona(self,
129
+ persona: str,
130
+ variables: dict = {},
131
+ history: Optional[List[BaseMessage]] = None):
132
+ if 'casual' in persona.lower():
133
+ prompt = self.get_prompt(template='casual_fan_prompt', environment='latest')
134
+ elif 'super' in persona.lower():
135
+ prompt = self.get_prompt(template='super_fan_prompt', environment='latest')
136
+ else:
137
+ raise ValueError(f"Unknown persona: {persona}")
138
+
139
+ formatted_prompt = prompt.bind(variables=variables, history=history).format()
140
+ self._prompt_vars = variables
141
+ self._formatted_prompt = formatted_prompt
142
+
143
+ return formatted_prompt
api/utils/zep_helpers.py CHANGED
@@ -1,30 +1,133 @@
 
1
  from functools import lru_cache
2
  from langchain_core.messages import BaseMessage
3
- from zep_cloud.client import AsyncZep
4
  from zep_cloud.types import Message
 
 
5
 
6
 
7
  # @lru_cache(maxsize=1)
8
- def get_zep_client():
9
  return AsyncZep()
10
 
11
 
12
- async def record_session(session_id: str, messages: list[BaseMessage]):
13
- if len(messages) >= 2:
14
- user_message = messages[0]
15
- assistant_message = messages[-1]
16
- messages = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  Message(
18
- role="user",
19
- content=user_message.content,
20
- role_type="user",
21
- ),
22
- Message(
23
- role="assistant",
24
- content=assistant_message.content,
25
- role_type="assistant",
26
- ),
27
- ]
28
- zep_client = get_zep_client()
29
- await zep_client.memory.add(session_id=session_id, messages=messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
 
1
+ import uuid
2
  from functools import lru_cache
3
  from langchain_core.messages import BaseMessage
4
+ from zep_cloud.client import AsyncZep, Zep
5
  from zep_cloud.types import Message
6
+ from zep_cloud.errors import NotFoundError
7
+ from typing import Optional
8
 
9
 
10
  # @lru_cache(maxsize=1)
11
+ def _get_zep_client():
12
  return AsyncZep()
13
 
14
 
15
+ class ZepClient:
16
+
17
+ def __init__(self):
18
+ self.zep_client_async = AsyncZep()
19
+ self.zep_client = Zep()
20
+ self._user = None
21
+ self.session_id = None
22
+
23
+ async def get_memory_async(self, session_id: Optional[str] = None):
24
+ session_id = session_id or self.session_id
25
+ if session_id is None:
26
+ raise ValueError("No session ID provided")
27
+ return await self.zep_client_async.memory.get(session_id=session_id)
28
+
29
+ # async def get_session(self, session_id: str):
30
+ # return await self.zep_client.session.get(session_id=session_id)
31
+
32
+ # async def get_or_create_user(self, email: str, first_name: Optional[str] = None, last_name: Optional[str] = None):
33
+ # if self._user and self._user.user_id == email:
34
+ # return self
35
+ # try:
36
+ # self._user = await self.get_user(email)
37
+ # except NotFoundError:
38
+ # self._user = await self.create_user(email, first_name, last_name)
39
+ # return self
40
+
41
+ def get_or_create_user(self, email: str, first_name: Optional[str] = None, last_name: Optional[str] = None):
42
+ if self._user and self._user.user_id == email:
43
+ return self
44
+ try:
45
+ self.get_user(email)
46
+ except NotFoundError:
47
+ self.create_user(email, first_name, last_name)
48
+ return self
49
+
50
+ def create_session(self):
51
+ session_id = uuid.uuid4().hex # A new session identifier
52
+ self.zep_client.memory.add_session(
53
+ session_id=session_id,
54
+ user_id=self._user.user_id,
55
+ )
56
+ self.session_id = session_id
57
+ return self
58
+
59
+ def create_user(self, email: str, first_name: Optional[str] = None, last_name: Optional[str] = None):
60
+ self._user = self.zep_client.user.add(
61
+ user_id=email,
62
+ email=email,
63
+ first_name=first_name,
64
+ last_name=last_name,
65
+ )
66
+ return self
67
+
68
+ # async def create_user(self, email: str, first_name: Optional[str] = None, last_name: Optional[str] = None):
69
+ # self._user = await self.zep_client.user.add(
70
+ # user_id=email,
71
+ # email=email,
72
+ # first_name=first_name,
73
+ # last_name=last_name,
74
+ # )
75
+ # return self._user
76
+
77
+ # async def get_user(self, email: str):
78
+ # self._user = await self.zep_client.user.get(user_id=email)
79
+ # return self._user
80
+
81
+ def get_user(self, email: str):
82
+ self._user = self.zep_client.user.get(user_id=email)
83
+ return self
84
+
85
+ @staticmethod
86
+ def get_zep_client() -> AsyncZep:
87
+ return _get_zep_client()
88
+
89
+ async def record_session(self,
90
+ messages: list[BaseMessage],
91
+ session_id: Optional[str] = None):
92
+ session_id = session_id or self.session_id
93
+ if session_id is None:
94
+ raise ValueError("No session ID provided")
95
+ if len(messages) >= 2:
96
+ user_message = messages[0]
97
+ assistant_message = messages[-1]
98
+ messages = [
99
+ Message(
100
+ role="user",
101
+ content=user_message.content,
102
+ role_type="user",
103
+ ),
104
  Message(
105
+ role="assistant",
106
+ content=assistant_message.content,
107
+ role_type="assistant",
108
+ ),
109
+ ]
110
+ await self.zep_client_async.memory.add(session_id=session_id, messages=messages)
111
+
112
+
113
+ # async def record_session(session_id: str,
114
+ # messages: list[BaseMessage],
115
+ # zep_client: AsyncZep = None):
116
+ # if len(messages) >= 2:
117
+ # user_message = messages[0]
118
+ # assistant_message = messages[-1]
119
+ # messages = [
120
+ # Message(
121
+ # role="user",
122
+ # content=user_message.content,
123
+ # role_type="user",
124
+ # ),
125
+ # Message(
126
+ # role="assistant",
127
+ # content=assistant_message.content,
128
+ # role_type="assistant",
129
+ # ),
130
+ # ]
131
+ # zep_client = zep_client or get_zep_client()
132
+ # await zep_client.memory.add(session_id=session_id, messages=messages)
133
 
api/workflows/base.py CHANGED
@@ -1,6 +1,9 @@
1
  import asyncio
2
  import datetime
3
  import operator
 
 
 
4
  from functools import partial
5
  from typing import TypedDict, Annotated, Sequence
6
  from langchain_openai import ChatOpenAI
@@ -16,16 +19,13 @@ from langgraph.graph import StateGraph, END
16
  from langchain_core.callbacks import AsyncCallbackHandler
17
  from langgraph.prebuilt import ToolNode
18
 
19
-
20
- from utils.zep_helpers import (
21
- AsyncZep,
22
- get_zep_client,
23
- record_session,
24
- )
25
  from prompts import (
26
  casual_fan_prompt,
27
  )
28
  from tools import (
 
29
  PlayerSearchTool,
30
  GameSearchTool,
31
  )
@@ -42,29 +42,53 @@ llm_with_tools = llm.bind_tools(tools=available_tools)
42
 
43
 
44
  class AgentState(TypedDict):
45
- session_id: str
 
 
 
 
 
 
 
46
  messages: Annotated[Sequence[BaseMessage], operator.add]
47
 
48
 
49
- async def call_model(state: AgentState, handler: AsyncCallbackHandler, zep_client: AsyncZep) -> dict:
50
- session_id = state["session_id"]
51
- memory = await zep_client.memory.get(session_id=session_id)
 
 
52
  messages = state["messages"]
 
53
 
54
- prompt = casual_fan_prompt.format(
55
- zep_context=memory.context,
56
- input=messages,
57
- now=datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d'),
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  )
59
 
60
  # response = await llm_with_tools.ainvoke(prompt, stream=True,
61
  # config={"callbacks" :[handler]})
62
- response = await llm_with_tools.with_config(callbacks=[handler]).ainvoke(prompt, stream=True)
63
 
64
- return {'messages': [response]}
65
 
66
 
67
- async def call_tool(state: AgentState, handler: AsyncCallbackHandler) -> dict:
 
68
  messages = state["messages"]
69
  last_message = messages[-1]
70
  tools_by_name = {tool.name: tool for tool in available_tools}
@@ -81,56 +105,113 @@ async def call_tool(state: AgentState, handler: AsyncCallbackHandler) -> dict:
81
 
82
  results = []
83
  for observation, tool_call in zip(observations, last_message.tool_calls):
 
 
84
  results.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
85
  return {"messages": results}
86
 
87
 
88
- async def should_continue(state: AgentState, handler: AsyncCallbackHandler, zep_client: AsyncZep) -> str:
 
 
 
89
  messages = state["messages"]
90
  last_message = messages[-1]
91
  if 'tool_calls' not in last_message.additional_kwargs:
92
  # inform zep of final response
93
- await record_session(state["session_id"], messages)
 
 
 
 
 
 
 
94
  if hasattr(handler, 'on_workflow_end'):
95
  await handler.on_workflow_end(state)
96
  return 'end'
97
  return 'continue'
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- def build_workflow(handler: AsyncCallbackHandler):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  workflow = StateGraph(AgentState)
102
- zep_client = get_zep_client()
103
- workflow.add_node('agent', partial(call_model, handler=handler, zep_client=zep_client))
 
104
  workflow.add_node('tools', partial(call_tool, handler=handler))
105
  workflow.set_entry_point('agent')
106
 
107
  workflow.add_conditional_edges(
108
  'agent',
109
- partial(should_continue, handler=handler, zep_client=zep_client),
110
  {
111
  'continue': 'tools',
112
  'end': END,
113
- }
114
  )
115
 
116
  workflow.add_edge('tools', 'agent')
117
 
118
- return workflow.compile()
119
 
120
 
121
- def build_workflow_with_state(handler: AsyncCallbackHandler, session_id: str, messages=None):
 
 
 
 
 
 
 
122
  """
123
  Utility to build workflow and initial state in one step.
124
  Args:
125
  handler: AsyncCallbackHandler for this workflow
126
- session_id: unique session identifier
127
  messages: optional initial message list
128
  Returns:
129
  (workflow, state) tuple ready for execution
130
  """
131
- workflow = build_workflow(handler)
132
  state = {
133
- "session_id": session_id,
 
 
 
 
 
134
  "messages": messages or [],
 
 
135
  }
136
- return workflow, state
 
1
  import asyncio
2
  import datetime
3
  import operator
4
+ import time
5
+ from dataclasses import dataclass
6
+ from typing import Any, Tuple, List, Optional
7
  from functools import partial
8
  from typing import TypedDict, Annotated, Sequence
9
  from langchain_openai import ChatOpenAI
 
19
  from langchain_core.callbacks import AsyncCallbackHandler
20
  from langgraph.prebuilt import ToolNode
21
 
22
+ from utils.freeplay_helpers import FreeplayClient
23
+ from utils.zep_helpers import ZepClient
 
 
 
 
24
  from prompts import (
25
  casual_fan_prompt,
26
  )
27
  from tools import (
28
+ Document,
29
  PlayerSearchTool,
30
  GameSearchTool,
31
  )
 
42
 
43
 
44
  class AgentState(TypedDict):
45
+ email: str
46
+ first_name: str
47
+ last_name: str
48
+ zep_session_id: str
49
+ freeplay_session_id: str
50
+ persona: str
51
+ start_time: float
52
+ zep_memory: Optional[str] = None
53
  messages: Annotated[Sequence[BaseMessage], operator.add]
54
 
55
 
56
+ async def call_model(state: AgentState,
57
+ handler: AsyncCallbackHandler,
58
+ zep_client: ZepClient,
59
+ freeplay_client: FreeplayClient) -> dict:
60
+ zep_session_id = state["zep_session_id"]
61
  messages = state["messages"]
62
+ persona = state["persona"]
63
 
64
+ memory = state["zep_memory"]
65
+ if memory is None:
66
+ memory = await zep_client.get_memory_async(session_id=zep_session_id)
67
+ memory = memory.context or "New user/session. No memory."
68
+
69
+ # old_prompt = casual_fan_prompt.format(
70
+ # zep_context=memory,
71
+ # input=messages,
72
+ # now=datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d'),
73
+ # )
74
+ prompt = freeplay_client.get_prompt_by_persona(
75
+ persona=persona,
76
+ variables={
77
+ "now": datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d'),
78
+ "zep_context": memory,
79
+ },
80
+ history=messages,
81
  )
82
 
83
  # response = await llm_with_tools.ainvoke(prompt, stream=True,
84
  # config={"callbacks" :[handler]})
85
+ response = await llm_with_tools.with_config(callbacks=[handler]).ainvoke(prompt.llm_prompt, stream=True)
86
 
87
+ return {'messages': [response], 'zep_memory': memory}
88
 
89
 
90
+ async def call_tool(state: AgentState,
91
+ handler: AsyncCallbackHandler) -> dict:
92
  messages = state["messages"]
93
  last_message = messages[-1]
94
  tools_by_name = {tool.name: tool for tool in available_tools}
 
105
 
106
  results = []
107
  for observation, tool_call in zip(observations, last_message.tool_calls):
108
+ if isinstance(observation, list) and len(observation) > 0 and isinstance(observation[0], Document):
109
+ observation = "\n\n".join(doc.page_content for doc in observation)
110
  results.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
111
  return {"messages": results}
112
 
113
 
114
+ async def should_continue(state: AgentState,
115
+ handler: AsyncCallbackHandler,
116
+ zep_client: ZepClient,
117
+ freeplay_client: FreeplayClient) -> str:
118
  messages = state["messages"]
119
  last_message = messages[-1]
120
  if 'tool_calls' not in last_message.additional_kwargs:
121
  # inform zep of final response
122
+ await zep_client.record_session(
123
+ session_id=state["zep_session_id"],
124
+ messages=messages,
125
+ )
126
+ # inform freeplay of final response
127
+ # TODO: wait for feedback from freeplay about tool messages
128
+ # freeplay_client.record_session(state)
129
+ # trigger on_workflow_end callback
130
  if hasattr(handler, 'on_workflow_end'):
131
  await handler.on_workflow_end(state)
132
  return 'end'
133
  return 'continue'
134
 
135
+ ### WorkflowBundle ###
136
+ @dataclass
137
+ class WorkflowBundle:
138
+ """
139
+ Bundle containing the compiled workflow and its core dependencies.
140
+
141
+ Attributes:
142
+ workflow: The compiled workflow object ready for execution.
143
+ zep_client: The Zep client instance used for memory/context.
144
+ freeplay_client: The Freeplay client instance for session tracking.
145
+ """
146
+ workflow: Any
147
+ zep_client: ZepClient
148
+ freeplay_client: FreeplayClient
149
+
150
 
151
+ def build_workflow(handler: AsyncCallbackHandler) -> WorkflowBundle:
152
+ """
153
+ Build and compile the workflow, returning all core dependencies as a WorkflowBundle.
154
+
155
+ Args:
156
+ handler: AsyncCallbackHandler for this workflow.
157
+
158
+ Returns:
159
+ WorkflowBundle: Contains compiled workflow, zep_client, and freeplay_client.
160
+
161
+ Example usage:
162
+ bundle = build_workflow(handler)
163
+ # Access workflow: bundle.workflow
164
+ # Access zep client: bundle.zep_client
165
+ # Access freeplay client: bundle.freeplay_client
166
+ """
167
  workflow = StateGraph(AgentState)
168
+ zep_client = ZepClient()
169
+ freeplay_client = FreeplayClient()
170
+ workflow.add_node('agent', partial(call_model, handler=handler, zep_client=zep_client, freeplay_client=freeplay_client))
171
  workflow.add_node('tools', partial(call_tool, handler=handler))
172
  workflow.set_entry_point('agent')
173
 
174
  workflow.add_conditional_edges(
175
  'agent',
176
+ partial(should_continue, handler=handler, zep_client=zep_client, freeplay_client=freeplay_client),
177
  {
178
  'continue': 'tools',
179
  'end': END,
180
+ }
181
  )
182
 
183
  workflow.add_edge('tools', 'agent')
184
 
185
+ return WorkflowBundle(workflow=workflow.compile(), zep_client=zep_client, freeplay_client=freeplay_client)
186
 
187
 
188
+ def build_workflow_with_state(handler: AsyncCallbackHandler,
189
+ zep_session_id: Optional[str] = None,
190
+ freeplay_session_id: Optional[str] = None,
191
+ email: Optional[str] = None,
192
+ first_name: Optional[str] = None,
193
+ last_name: Optional[str] = None,
194
+ persona: Optional[str] = None,
195
+ messages: Optional[List[BaseMessage]] = None) -> Tuple[WorkflowBundle, AgentState]:
196
  """
197
  Utility to build workflow and initial state in one step.
198
  Args:
199
  handler: AsyncCallbackHandler for this workflow
200
+ zep_session_id: unique session identifier
201
  messages: optional initial message list
202
  Returns:
203
  (workflow, state) tuple ready for execution
204
  """
205
+ bundle = build_workflow(handler)
206
  state = {
207
+ "zep_session_id": zep_session_id,
208
+ "freeplay_session_id": freeplay_session_id,
209
+ "email": email,
210
+ "first_name": first_name,
211
+ "last_name": last_name,
212
+ "persona": persona,
213
  "messages": messages or [],
214
+ "start_time": time.time(),
215
+ "zep_memory": None,
216
  }
217
+ return bundle, state