Spaces:
No application file
No application file
properly implementation of gradio, langgraph, freeplay and zep (including proper sessions);adding user headshots
Browse files- api/scripts/create_play_profile_pic.py +23 -3
- api/scripts/freeplay_playground.py +64 -18
- api/scripts/workflow_playground.py +25 -3
- api/scripts/zep_playground2.py +19 -0
- api/server_gradio.py +241 -77
- api/tools/__init__.py +2 -0
- api/utils/freeplay_helpers.py +126 -41
- api/utils/zep_helpers.py +122 -19
- api/workflows/base.py +111 -30
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.
|
75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
4 |
import datetime
|
|
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
|
25 |
if __name__ == "__main__":
|
26 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
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 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=[
|
120 |
-
def submit(state):
|
121 |
-
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
-
@
|
125 |
-
def
|
126 |
-
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
-
|
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
|
13 |
return Freeplay(
|
14 |
freeplay_api_key=os.getenv("FREEPLAY_API_KEY"),
|
15 |
api_base=os.getenv("FREEPLAY_URL"),
|
16 |
)
|
17 |
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
9 |
return AsyncZep()
|
10 |
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
Message(
|
18 |
-
role="
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
messages: Annotated[Sequence[BaseMessage], operator.add]
|
47 |
|
48 |
|
49 |
-
async def call_model(state: AgentState,
|
50 |
-
|
51 |
-
|
|
|
|
|
52 |
messages = state["messages"]
|
|
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
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,
|
|
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 =
|
103 |
-
|
|
|
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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
"""
|
123 |
Utility to build workflow and initial state in one step.
|
124 |
Args:
|
125 |
handler: AsyncCallbackHandler for this workflow
|
126 |
-
|
127 |
messages: optional initial message list
|
128 |
Returns:
|
129 |
(workflow, state) tuple ready for execution
|
130 |
"""
|
131 |
-
|
132 |
state = {
|
133 |
-
"
|
|
|
|
|
|
|
|
|
|
|
134 |
"messages": messages or [],
|
|
|
|
|
135 |
}
|
136 |
-
return
|
|
|
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
|