proscenium 0.0.13__py3-none-any.whl → 0.0.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
proscenium/__init__.py CHANGED
@@ -1,3 +1,182 @@
1
+ from typing import Generator
2
+ from typing import Optional
1
3
  import logging
2
4
 
5
+ from pydantic import BaseModel, Field
6
+ from rich.text import Text
7
+ from rich.console import Console
8
+
3
9
  logging.getLogger(__name__).addHandler(logging.NullHandler())
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+
14
+ def header() -> Text:
15
+ text = Text()
16
+ text.append("Proscenium 🎭\n", style="bold")
17
+ text.append("https://the-ai-alliance.github.io/proscenium/\n")
18
+ # TODO version, timestamp, ...
19
+ return text
20
+
21
+
22
+ control_flow_system_prompt = """
23
+ You control the workflow of an AI assistant. You evaluate user-posted messages and decide what the next step is.
24
+ """
25
+
26
+
27
+ class WantsToHandleResponse(BaseModel):
28
+ """
29
+ The response to whether the Character wants to handle the provided utterance.
30
+ """
31
+
32
+ wants_to_handle: bool = Field(
33
+ description="A boolean indicating whether the Character wants to handle the provided utterance.",
34
+ )
35
+
36
+
37
+ class Prop:
38
+ """
39
+ A `Prop` is a resource available to the `Character`s in a `Scene`.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ console: Optional[Console] = None,
45
+ ):
46
+ self.console = console
47
+
48
+ def name(self) -> str:
49
+ return self.__class__.__name__
50
+
51
+ def description(self) -> str:
52
+ return self.__doc__ or ""
53
+
54
+ def curtain_up_message(self) -> str:
55
+ return f"- {self.name()}, {self.description().strip()}"
56
+
57
+ def already_built(self) -> bool:
58
+ return False
59
+
60
+ def build(self) -> None:
61
+ pass
62
+
63
+
64
+ class Character:
65
+ """
66
+ A `Character` is a participant in a `Scene` that `handle`s utterances from the
67
+ scene by producing its own utterances."""
68
+
69
+ def __init__(self, admin_channel_id: str):
70
+ self.admin_channel_id = admin_channel_id
71
+
72
+ def name(self) -> str:
73
+ return self.__class__.__name__
74
+
75
+ def description(self) -> str:
76
+ return self.__doc__ or ""
77
+
78
+ def curtain_up_message(self) -> str:
79
+ return f"- {self.name()}, {self.description().strip()}"
80
+
81
+ def wants_to_handle(self, channel_id: str, speaker_id: str, utterance: str) -> bool:
82
+ return False
83
+
84
+ def handle(
85
+ channel_id: str, speaker_id: str, utterance: str
86
+ ) -> Generator[tuple[str, str], None, None]:
87
+ pass
88
+
89
+
90
+ class Scene:
91
+ """
92
+ A `Scene` is a setting in which `Character`s interact with each other and
93
+ with `Prop`s. It is a container for `Character`s and `Prop`s.
94
+ """
95
+
96
+ def __init__(self):
97
+ pass
98
+
99
+ def name(self) -> str:
100
+ return self.__class__.__name__
101
+
102
+ def description(self) -> str:
103
+ return self.__doc__ or ""
104
+
105
+ def curtain_up_message(self) -> str:
106
+
107
+ characters_msg = "\n".join(
108
+ [character.curtain_up_message() for character in self.characters()]
109
+ )
110
+
111
+ props_msg = "\n".join([prop.curtain_up_message() for prop in self.props()])
112
+
113
+ return f"""
114
+ Scene: {self.name()}, {self.description().strip()}
115
+
116
+ Characters:
117
+ {characters_msg}
118
+
119
+ Props:
120
+ {props_msg}
121
+ """
122
+
123
+ def props(self) -> list[Prop]:
124
+ return []
125
+
126
+ def prepare_props(self, force_rebuild: bool = False) -> None:
127
+ for prop in self.props():
128
+ if force_rebuild:
129
+ prop.build()
130
+ elif not prop.already_built():
131
+ log.info("Prop %s not built. Building it now.", prop.name())
132
+ prop.build()
133
+
134
+ def characters(self) -> list[Character]:
135
+ return []
136
+
137
+ def places(self) -> dict[str, Character]:
138
+ pass
139
+
140
+ def curtain(self) -> None:
141
+ pass
142
+
143
+
144
+ class Production:
145
+ """
146
+ A `Production` is a collection of `Scene`s."""
147
+
148
+ def __init__(self, admin_channel_id: str, console: Optional[Console] = None):
149
+ self.admin_channel_id = admin_channel_id
150
+ self.console = console
151
+
152
+ def name(self) -> str:
153
+ return self.__class__.__name__
154
+
155
+ def description(self) -> str:
156
+ return self.__doc__ or ""
157
+
158
+ def prepare_props(self, force_rebuild: bool = False) -> None:
159
+ if force_rebuild:
160
+ log.info("Forcing rebuild of all props.")
161
+ else:
162
+ log.info("Building any missing props...")
163
+
164
+ for scene in self.scenes():
165
+ scene.prepare_props(force_rebuild=force_rebuild)
166
+
167
+ def curtain_up_message(self) -> str:
168
+
169
+ scenes_msg = "\n\n".join(
170
+ [scene.curtain_up_message() for scene in self.scenes()]
171
+ )
172
+
173
+ return f"""Production: {self.name()}, {self.description().strip()}
174
+
175
+ {scenes_msg}"""
176
+
177
+ def scenes(self) -> list[Scene]:
178
+ return []
179
+
180
+ def curtain(self) -> None:
181
+ for scene in self.scenes():
182
+ scene.curtain()
@@ -4,8 +4,8 @@ from typing import Optional
4
4
 
5
5
  import logging
6
6
 
7
- from proscenium.core import Prop
8
- from proscenium.core import Character
7
+ from proscenium import Prop
8
+ from proscenium import Character
9
9
  from rich.console import Console
10
10
 
11
11
  logging.getLogger(__name__).addHandler(logging.NullHandler())
@@ -5,7 +5,7 @@ import importlib
5
5
  import yaml
6
6
  from pathlib import Path
7
7
  from rich.console import Console
8
- from proscenium.core import Production
8
+ from proscenium import Production
9
9
 
10
10
  logging.getLogger(__name__).addHandler(logging.NullHandler())
11
11
 
proscenium/bin/bot.py CHANGED
@@ -8,7 +8,7 @@ import logging
8
8
  from pathlib import Path
9
9
  from rich.console import Console
10
10
 
11
- from proscenium.verbs.display import header
11
+ from proscenium import header
12
12
  from proscenium.bin import production_from_config
13
13
  from proscenium.interfaces.slack import SlackProductionProcessor
14
14
 
proscenium/complete.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ This module uses the [`aisuite`](https://github.com/andrewyng/aisuite) library
3
+ to interact with various LLM inference providers.
4
+
5
+ It provides functions to complete a simple chat prompt, evaluate a tool call,
6
+ and apply a list of tool calls to a chat prompt.
7
+
8
+ Providers tested with Proscenium include:
9
+
10
+ # AWS
11
+
12
+ Environment: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
13
+
14
+ Valid model ids:
15
+ - `aws:meta.llama3-1-8b-instruct-v1:0`
16
+
17
+ # Anthropic
18
+
19
+ Environment: `ANTHROPIC_API_KEY`
20
+
21
+ Valid model ids:
22
+ - `anthropic:claude-3-5-sonnet-20240620`
23
+
24
+ # OpenAI
25
+
26
+ Environment: `OPENAI_API_KEY`
27
+
28
+ Valid model ids:
29
+ - `openai:gpt-4o`
30
+
31
+ # Ollama
32
+
33
+ Command line, eg `ollama run llama3.2 --keepalive 2h`
34
+
35
+ Valid model ids:
36
+ - `ollama:llama3.2`
37
+ - `ollama:granite3.1-dense:2b`
38
+ """
39
+
40
+ import logging
41
+
42
+ from rich.console import Group
43
+ from rich.panel import Panel
44
+ from rich.table import Table
45
+ from rich.text import Text
46
+
47
+ from aisuite import Client as AISuiteClient
48
+
49
+ log = logging.getLogger(__name__)
50
+
51
+
52
+ def complete_simple(
53
+ chat_completion_client: AISuiteClient,
54
+ model_id: str,
55
+ system_prompt: str,
56
+ user_prompt: str,
57
+ **kwargs,
58
+ ) -> str:
59
+
60
+ console = kwargs.pop("console", None)
61
+
62
+ messages = [
63
+ {"role": "system", "content": system_prompt},
64
+ {"role": "user", "content": user_prompt},
65
+ ]
66
+
67
+ if console is not None:
68
+
69
+ kwargs_text = "\n".join([str(k) + ": " + str(v) for k, v in kwargs.items()])
70
+
71
+ params_text = Text(
72
+ f"""
73
+ model_id: {model_id}
74
+ {kwargs_text}
75
+ """
76
+ )
77
+
78
+ messages_table = Table(title="Messages", show_lines=True)
79
+ messages_table.add_column("Role", justify="left")
80
+ messages_table.add_column("Content", justify="left") # style="green"
81
+ for message in messages:
82
+ messages_table.add_row(message["role"], message["content"])
83
+
84
+ call_panel = Panel(
85
+ Group(params_text, messages_table), title="complete_simple call"
86
+ )
87
+ console.print(call_panel)
88
+
89
+ response = chat_completion_client.chat.completions.create(
90
+ model=model_id, messages=messages, **kwargs
91
+ )
92
+ response = response.choices[0].message.content
93
+
94
+ if console is not None:
95
+ console.print(Panel(response, title="Response"))
96
+
97
+ return response
@@ -1,5 +1,19 @@
1
+ import logging
2
+
1
3
  from rich.table import Table
2
4
 
5
+ log = logging.getLogger(__name__)
6
+
7
+
8
+ def format_chat_history(chat_history) -> str:
9
+ delimiter = "-" * 80 + "\n"
10
+ return delimiter.join(
11
+ [
12
+ f"{msg['sender']} to {msg['receiver']}:\n\n{msg['content']}\n\n"
13
+ for msg in chat_history
14
+ ]
15
+ )
16
+
3
17
 
4
18
  def messages_table(messages: list) -> Table:
5
19
 
@@ -12,8 +12,8 @@ from slack_sdk.socket_mode import SocketModeClient
12
12
  from slack_sdk.socket_mode.request import SocketModeRequest
13
13
  from slack_sdk.socket_mode.response import SocketModeResponse
14
14
 
15
- from proscenium.core import Production
16
- from proscenium.core import Character
15
+ from proscenium import Production
16
+ from proscenium import Character
17
17
  from proscenium.admin import Admin
18
18
 
19
19
  log = logging.getLogger(__name__)
@@ -6,7 +6,7 @@ from rich.table import Table
6
6
  from pymilvus import MilvusClient
7
7
  from pymilvus import model
8
8
 
9
- from proscenium.verbs.complete import complete_simple
9
+ from proscenium.complete import complete_simple
10
10
 
11
11
  log = logging.getLogger(__name__)
12
12
 
@@ -1,19 +1,194 @@
1
1
  from typing import Optional
2
+ from typing import Any
2
3
  import logging
4
+ import json
3
5
 
4
- from rich.console import Console
5
- from rich.panel import Panel
6
+ from rich.console import Group
7
+ from rich.table import Table
6
8
  from rich.text import Text
9
+ from rich.panel import Panel
10
+ from rich.console import Console
11
+
12
+ from aisuite import Client as AISuiteClient
13
+ from aisuite.framework.message import ChatCompletionMessageToolCall
7
14
 
8
- from proscenium.verbs.complete import (
9
- complete_for_tool_applications,
10
- evaluate_tool_calls,
11
- complete_with_tool_results,
12
- )
15
+ from gofannon.base import BaseTool
16
+
17
+ from proscenium.history import messages_table
13
18
 
14
19
  log = logging.getLogger(__name__)
15
20
 
16
21
 
22
+ def evaluate_tool_call(tool_map: dict, tool_call: ChatCompletionMessageToolCall) -> Any:
23
+
24
+ function_name = tool_call.function.name
25
+ # TODO validate the arguments?
26
+ function_args = json.loads(tool_call.function.arguments)
27
+
28
+ log.info(f"Evaluating tool call: {function_name} with args {function_args}")
29
+
30
+ function_response = tool_map[function_name](**function_args)
31
+
32
+ log.info(f" Response: {function_response}")
33
+
34
+ return function_response
35
+
36
+
37
+ def tool_response_message(
38
+ tool_call: ChatCompletionMessageToolCall, tool_result: Any
39
+ ) -> dict:
40
+
41
+ return {
42
+ "role": "tool",
43
+ "tool_call_id": tool_call.id,
44
+ "name": tool_call.function.name,
45
+ "content": json.dumps(tool_result),
46
+ }
47
+
48
+
49
+ def evaluate_tool_calls(tool_call_message, tool_map: dict) -> list[dict]:
50
+
51
+ tool_call: ChatCompletionMessageToolCall
52
+
53
+ log.info("Evaluating tool calls")
54
+
55
+ new_messages: list[dict] = []
56
+
57
+ for tool_call in tool_call_message.tool_calls:
58
+ function_response = evaluate_tool_call(tool_map, tool_call)
59
+ new_messages.append(tool_response_message(tool_call, function_response))
60
+
61
+ log.info("Tool calls evaluated")
62
+
63
+ return new_messages
64
+
65
+
66
+ def complete_for_tool_applications(
67
+ chat_completion_client: AISuiteClient,
68
+ model_id: str,
69
+ messages: list,
70
+ tool_desc_list: list,
71
+ temperature: float,
72
+ console: Optional[Console] = None,
73
+ ):
74
+
75
+ if console is not None:
76
+ panel = complete_with_tools_panel(
77
+ "complete for tool applications",
78
+ model_id,
79
+ tool_desc_list,
80
+ messages,
81
+ temperature,
82
+ )
83
+ console.print(panel)
84
+
85
+ response = chat_completion_client.chat.completions.create(
86
+ model=model_id,
87
+ messages=messages,
88
+ temperature=temperature,
89
+ tools=tool_desc_list, # tool_choice="auto",
90
+ )
91
+
92
+ return response
93
+
94
+
95
+ def complete_with_tool_results(
96
+ chat_completion_client: AISuiteClient,
97
+ model_id: str,
98
+ messages: list,
99
+ tool_call_message: dict,
100
+ tool_evaluation_messages: list[dict],
101
+ tool_desc_list: list,
102
+ temperature: float,
103
+ console: Optional[Console] = None,
104
+ ):
105
+
106
+ messages.append(tool_call_message)
107
+ messages.extend(tool_evaluation_messages)
108
+
109
+ if console is not None:
110
+ panel = complete_with_tools_panel(
111
+ "complete call with tool results",
112
+ model_id,
113
+ tool_desc_list,
114
+ messages,
115
+ temperature,
116
+ )
117
+ console.print(panel)
118
+
119
+ response = chat_completion_client.chat.completions.create(
120
+ model=model_id,
121
+ messages=messages,
122
+ )
123
+
124
+ return response.choices[0].message.content
125
+
126
+
127
+ def process_tools(tools: list[BaseTool]) -> tuple[dict, list]:
128
+ applied_tools = [F() for F in tools]
129
+ tool_map = {f.name: f.fn for f in applied_tools}
130
+ tool_desc_list = [f.definition for f in applied_tools]
131
+ return tool_map, tool_desc_list
132
+
133
+
134
+ def parameters_table(parameters: list[dict]) -> Table:
135
+
136
+ table = Table(title="Parameters", show_lines=False, box=None)
137
+ table.add_column("name", justify="right")
138
+ table.add_column("type", justify="left")
139
+ table.add_column("description", justify="left")
140
+
141
+ for name, props in parameters["properties"].items():
142
+ table.add_row(name, props["type"], props["description"])
143
+
144
+ # TODO denote required params
145
+
146
+ return table
147
+
148
+
149
+ def function_description_panel(fd: dict) -> Panel:
150
+
151
+ fn = fd["function"]
152
+
153
+ text = Text(f"{fd['type']} {fn['name']}: {fn['description']}\n")
154
+
155
+ pt = parameters_table(fn["parameters"])
156
+
157
+ panel = Panel(Group(text, pt))
158
+
159
+ return panel
160
+
161
+
162
+ def function_descriptions_panel(function_descriptions: list[dict]) -> Panel:
163
+
164
+ sub_panels = [function_description_panel(fd) for fd in function_descriptions]
165
+
166
+ panel = Panel(Group(*sub_panels), title="Function Descriptions")
167
+
168
+ return panel
169
+
170
+
171
+ def complete_with_tools_panel(
172
+ title: str, model_id: str, tool_desc_list: list, messages: list, temperature: float
173
+ ) -> Panel:
174
+
175
+ text = Text(
176
+ f"""
177
+ model_id: {model_id}
178
+ temperature: {temperature}
179
+ """
180
+ )
181
+
182
+ panel = Panel(
183
+ Group(
184
+ text, function_descriptions_panel(tool_desc_list), messages_table(messages)
185
+ ),
186
+ title=title,
187
+ )
188
+
189
+ return panel
190
+
191
+
17
192
  def apply_tools(
18
193
  model_id: str,
19
194
  system_message: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proscenium
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: Declare Collaborative, Asynchronous Human To Agent Interactions
5
5
  Author-email: Adam Pingel <oss@pingel.org>
6
6
  License-Expression: Apache-2.0
@@ -25,15 +25,17 @@ Dynamic: license-file
25
25
 
26
26
  # Proscenium
27
27
 
28
- [![PyPI version](https://img.shields.io/pypi/v/proscenium.svg)](https://pypi.org/project/proscenium/)
28
+ [![PyPI version](https://img.shields.io/pypi/v/proscenium)](https://pypi.org/project/proscenium/)
29
29
  [![PyPI - Downloads](https://img.shields.io/pypi/dm/proscenium)](https://pypi.org/project/proscenium/)
30
30
  [![CI](https://github.com/The-AI-Alliance/proscenium/actions/workflows/pytest.yml/badge.svg)](https://github.com/The-AI-Alliance/proscenium/actions/workflows/pytest.yml)
31
- [![PyPI](https://img.shields.io/pypi/v/proscenium)](https://pypi.org/project/proscenium/)
32
31
  [![License](https://img.shields.io/github/license/The-AI-Alliance/proscenium)](https://github.com/The-AI-Alliance/proscenium/tree/main?tab=Apache-2.0-1-ov-file#readme)
33
32
  [![Issues](https://img.shields.io/github/issues/The-AI-Alliance/proscenium)](https://github.com/The-AI-Alliance/proscenium/issues)
34
33
  [![GitHub stars](https://img.shields.io/github/stars/The-AI-Alliance/proscenium?style=social)](https://github.com/The-AI-Alliance/proscenium/stargazers)
35
34
 
36
- Proscenium is a small, experimental library of composable glue that allows for succinct construction of enterprise AI applications. It was started in February 2025 and is still in early development.
35
+ Proscenium is a small, experimental library for
36
+ declaring collaborative, asynchronous human to agent interactions
37
+ in enterprise AI applications.
38
+ It was started in February 2025 and is still in early development.
37
39
 
38
40
  See the [website](https://the-ai-alliance.github.io/proscenium/) for quickstart info, goals, and other links.
39
41
 
@@ -0,0 +1,19 @@
1
+ proscenium/__init__.py,sha256=n6X5CqhlDL3lagVL0756myyNwVxHyGokngxkbRzRkxA,4574
2
+ proscenium/complete.py,sha256=WVp-WB420jzikuUmPYVRQ0wI3bgRGZO_-ZCBBX_yJbw,2239
3
+ proscenium/history.py,sha256=AzLfn-raqlDNEJrKQYUJNN8kMogr08evfjOi9Mzwlqo,1445
4
+ proscenium/admin/__init__.py,sha256=5ENOL6CqlZJilCZFt3O6gwbHSvFMFcxEf9f8e2QmcTM,1016
5
+ proscenium/bin/__init__.py,sha256=RcD50sEH58LMEayUez9uugTigG7XnFB6kdILvro_xzg,1155
6
+ proscenium/bin/bot.py,sha256=IaQP33AOCTBcNtx4gb7kCwa2aXTR8XuKuLAfYwPJtE4,1699
7
+ proscenium/interfaces/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
8
+ proscenium/interfaces/slack.py,sha256=ebx3oc2CJuzbCJ9eYyCRyhtbojaCaWsgfscmwVv8I3E,10195
9
+ proscenium/patterns/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
10
+ proscenium/patterns/graph_rag.py,sha256=1HH1xdlAA6ypvYdP4dWFm-KXrGPUmm0T4qIdAU8mgvE,1763
11
+ proscenium/patterns/rag.py,sha256=HJzLYIqcxhAZNQ0JXhfHe9JV1rr41m9iFQGSxjryGBg,2304
12
+ proscenium/patterns/tools.py,sha256=CjVSySlWqd308ToYiwVSZG-NKnvUlWIGYfh-GY5BXMk,6164
13
+ proscenium/util/__init__.py,sha256=FC1hjA37VvmVpF9-OlYNp9TjArH6etr6KiAvF9t_6lI,679
14
+ proscenium-0.0.14.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
+ proscenium-0.0.14.dist-info/METADATA,sha256=6SRKhXXMTp7Zpt0Q5kQ7KtwBHF2pJ8dDdiJFr31yxRE,2148
16
+ proscenium-0.0.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ proscenium-0.0.14.dist-info/entry_points.txt,sha256=bbrDWv4WKIx9UROGXcyH0eo40F8SNpDQKvXxoE8BTHI,58
18
+ proscenium-0.0.14.dist-info/top_level.txt,sha256=aiTgQy73e21wnSSxM5iICXmXT3zldN8cFKeXpvJ5Xx4,11
19
+ proscenium-0.0.14.dist-info/RECORD,,
@@ -1,172 +0,0 @@
1
- from typing import Generator
2
- from typing import Optional
3
- import logging
4
-
5
- from pydantic import BaseModel, Field
6
- from rich.console import Console
7
-
8
- logging.getLogger(__name__).addHandler(logging.NullHandler())
9
-
10
- log = logging.getLogger(__name__)
11
-
12
- control_flow_system_prompt = """
13
- You control the workflow of an AI assistant. You evaluate user-posted messages and decide what the next step is.
14
- """
15
-
16
-
17
- class WantsToHandleResponse(BaseModel):
18
- """
19
- The response to whether the Character wants to handle the provided utterance.
20
- """
21
-
22
- wants_to_handle: bool = Field(
23
- description="A boolean indicating whether the Character wants to handle the provided utterance.",
24
- )
25
-
26
-
27
- class Prop:
28
- """
29
- A `Prop` is a resource available to the `Character`s in a `Scene`.
30
- """
31
-
32
- def __init__(
33
- self,
34
- console: Optional[Console] = None,
35
- ):
36
- self.console = console
37
-
38
- def name(self) -> str:
39
- return self.__class__.__name__
40
-
41
- def description(self) -> str:
42
- return self.__doc__ or ""
43
-
44
- def curtain_up_message(self) -> str:
45
- return f"- {self.name()}, {self.description().strip()}"
46
-
47
- def already_built(self) -> bool:
48
- return False
49
-
50
- def build(self) -> None:
51
- pass
52
-
53
-
54
- class Character:
55
- """
56
- A `Character` is a participant in a `Scene` that `handle`s utterances from the
57
- scene by producing its own utterances."""
58
-
59
- def __init__(self, admin_channel_id: str):
60
- self.admin_channel_id = admin_channel_id
61
-
62
- def name(self) -> str:
63
- return self.__class__.__name__
64
-
65
- def description(self) -> str:
66
- return self.__doc__ or ""
67
-
68
- def curtain_up_message(self) -> str:
69
- return f"- {self.name()}, {self.description().strip()}"
70
-
71
- def wants_to_handle(self, channel_id: str, speaker_id: str, utterance: str) -> bool:
72
- return False
73
-
74
- def handle(
75
- channel_id: str, speaker_id: str, utterance: str
76
- ) -> Generator[tuple[str, str], None, None]:
77
- pass
78
-
79
-
80
- class Scene:
81
- """
82
- A `Scene` is a setting in which `Character`s interact with each other and
83
- with `Prop`s. It is a container for `Character`s and `Prop`s.
84
- """
85
-
86
- def __init__(self):
87
- pass
88
-
89
- def name(self) -> str:
90
- return self.__class__.__name__
91
-
92
- def description(self) -> str:
93
- return self.__doc__ or ""
94
-
95
- def curtain_up_message(self) -> str:
96
-
97
- characters_msg = "\n".join(
98
- [character.curtain_up_message() for character in self.characters()]
99
- )
100
-
101
- props_msg = "\n".join([prop.curtain_up_message() for prop in self.props()])
102
-
103
- return f"""
104
- Scene: {self.name()}, {self.description().strip()}
105
-
106
- Characters:
107
- {characters_msg}
108
-
109
- Props:
110
- {props_msg}
111
- """
112
-
113
- def props(self) -> list[Prop]:
114
- return []
115
-
116
- def prepare_props(self, force_rebuild: bool = False) -> None:
117
- for prop in self.props():
118
- if force_rebuild:
119
- prop.build()
120
- elif not prop.already_built():
121
- log.info("Prop %s not built. Building it now.", prop.name())
122
- prop.build()
123
-
124
- def characters(self) -> list[Character]:
125
- return []
126
-
127
- def places(self) -> dict[str, Character]:
128
- pass
129
-
130
- def curtain(self) -> None:
131
- pass
132
-
133
-
134
- class Production:
135
- """
136
- A `Production` is a collection of `Scene`s."""
137
-
138
- def __init__(self, admin_channel_id: str, console: Optional[Console] = None):
139
- self.admin_channel_id = admin_channel_id
140
- self.console = console
141
-
142
- def name(self) -> str:
143
- return self.__class__.__name__
144
-
145
- def description(self) -> str:
146
- return self.__doc__ or ""
147
-
148
- def prepare_props(self, force_rebuild: bool = False) -> None:
149
- if force_rebuild:
150
- log.info("Forcing rebuild of all props.")
151
- else:
152
- log.info("Building any missing props...")
153
-
154
- for scene in self.scenes():
155
- scene.prepare_props(force_rebuild=force_rebuild)
156
-
157
- def curtain_up_message(self) -> str:
158
-
159
- scenes_msg = "\n\n".join(
160
- [scene.curtain_up_message() for scene in self.scenes()]
161
- )
162
-
163
- return f"""Production: {self.name()}, {self.description().strip()}
164
-
165
- {scenes_msg}"""
166
-
167
- def scenes(self) -> list[Scene]:
168
- return []
169
-
170
- def curtain(self) -> None:
171
- for scene in self.scenes():
172
- scene.curtain()
@@ -1,3 +0,0 @@
1
- import logging
2
-
3
- logging.getLogger(__name__).addHandler(logging.NullHandler())
@@ -1,209 +0,0 @@
1
- """
2
- This module uses the [`aisuite`](https://github.com/andrewyng/aisuite) library
3
- to interact with various LLM inference providers.
4
-
5
- It provides functions to complete a simple chat prompt, evaluate a tool call,
6
- and apply a list of tool calls to a chat prompt.
7
-
8
- Providers tested with Proscenium include:
9
-
10
- # AWS
11
-
12
- Environment: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
13
-
14
- Valid model ids:
15
- - `aws:meta.llama3-1-8b-instruct-v1:0`
16
-
17
- # Anthropic
18
-
19
- Environment: `ANTHROPIC_API_KEY`
20
-
21
- Valid model ids:
22
- - `anthropic:claude-3-5-sonnet-20240620`
23
-
24
- # OpenAI
25
-
26
- Environment: `OPENAI_API_KEY`
27
-
28
- Valid model ids:
29
- - `openai:gpt-4o`
30
-
31
- # Ollama
32
-
33
- Command line, eg `ollama run llama3.2 --keepalive 2h`
34
-
35
- Valid model ids:
36
- - `ollama:llama3.2`
37
- - `ollama:granite3.1-dense:2b`
38
- """
39
-
40
- from typing import Optional
41
- from typing import Any
42
- import logging
43
- import json
44
-
45
- from rich.console import Console
46
- from rich.console import Group
47
- from rich.panel import Panel
48
- from rich.table import Table
49
- from rich.text import Text
50
-
51
- from aisuite import Client as AISuiteClient
52
- from aisuite.framework.message import ChatCompletionMessageToolCall
53
-
54
- from proscenium.verbs.display.tools import complete_with_tools_panel
55
-
56
- log = logging.getLogger(__name__)
57
-
58
-
59
- def complete_simple(
60
- chat_completion_client: AISuiteClient,
61
- model_id: str,
62
- system_prompt: str,
63
- user_prompt: str,
64
- **kwargs,
65
- ) -> str:
66
-
67
- console = kwargs.pop("console", None)
68
-
69
- messages = [
70
- {"role": "system", "content": system_prompt},
71
- {"role": "user", "content": user_prompt},
72
- ]
73
-
74
- if console is not None:
75
-
76
- kwargs_text = "\n".join([str(k) + ": " + str(v) for k, v in kwargs.items()])
77
-
78
- params_text = Text(
79
- f"""
80
- model_id: {model_id}
81
- {kwargs_text}
82
- """
83
- )
84
-
85
- messages_table = Table(title="Messages", show_lines=True)
86
- messages_table.add_column("Role", justify="left")
87
- messages_table.add_column("Content", justify="left") # style="green"
88
- for message in messages:
89
- messages_table.add_row(message["role"], message["content"])
90
-
91
- call_panel = Panel(
92
- Group(params_text, messages_table), title="complete_simple call"
93
- )
94
- console.print(call_panel)
95
-
96
- response = chat_completion_client.chat.completions.create(
97
- model=model_id, messages=messages, **kwargs
98
- )
99
- response = response.choices[0].message.content
100
-
101
- if console is not None:
102
- console.print(Panel(response, title="Response"))
103
-
104
- return response
105
-
106
-
107
- def evaluate_tool_call(tool_map: dict, tool_call: ChatCompletionMessageToolCall) -> Any:
108
-
109
- function_name = tool_call.function.name
110
- # TODO validate the arguments?
111
- function_args = json.loads(tool_call.function.arguments)
112
-
113
- log.info(f"Evaluating tool call: {function_name} with args {function_args}")
114
-
115
- function_response = tool_map[function_name](**function_args)
116
-
117
- log.info(f" Response: {function_response}")
118
-
119
- return function_response
120
-
121
-
122
- def tool_response_message(
123
- tool_call: ChatCompletionMessageToolCall, tool_result: Any
124
- ) -> dict:
125
-
126
- return {
127
- "role": "tool",
128
- "tool_call_id": tool_call.id,
129
- "name": tool_call.function.name,
130
- "content": json.dumps(tool_result),
131
- }
132
-
133
-
134
- def evaluate_tool_calls(tool_call_message, tool_map: dict) -> list[dict]:
135
-
136
- tool_call: ChatCompletionMessageToolCall
137
-
138
- log.info("Evaluating tool calls")
139
-
140
- new_messages: list[dict] = []
141
-
142
- for tool_call in tool_call_message.tool_calls:
143
- function_response = evaluate_tool_call(tool_map, tool_call)
144
- new_messages.append(tool_response_message(tool_call, function_response))
145
-
146
- log.info("Tool calls evaluated")
147
-
148
- return new_messages
149
-
150
-
151
- def complete_for_tool_applications(
152
- chat_completion_client: AISuiteClient,
153
- model_id: str,
154
- messages: list,
155
- tool_desc_list: list,
156
- temperature: float,
157
- console: Optional[Console] = None,
158
- ):
159
-
160
- if console is not None:
161
- panel = complete_with_tools_panel(
162
- "complete for tool applications",
163
- model_id,
164
- tool_desc_list,
165
- messages,
166
- temperature,
167
- )
168
- console.print(panel)
169
-
170
- response = chat_completion_client.chat.completions.create(
171
- model=model_id,
172
- messages=messages,
173
- temperature=temperature,
174
- tools=tool_desc_list, # tool_choice="auto",
175
- )
176
-
177
- return response
178
-
179
-
180
- def complete_with_tool_results(
181
- chat_completion_client: AISuiteClient,
182
- model_id: str,
183
- messages: list,
184
- tool_call_message: dict,
185
- tool_evaluation_messages: list[dict],
186
- tool_desc_list: list,
187
- temperature: float,
188
- console: Optional[Console] = None,
189
- ):
190
-
191
- messages.append(tool_call_message)
192
- messages.extend(tool_evaluation_messages)
193
-
194
- if console is not None:
195
- panel = complete_with_tools_panel(
196
- "complete call with tool results",
197
- model_id,
198
- tool_desc_list,
199
- messages,
200
- temperature,
201
- )
202
- console.print(panel)
203
-
204
- response = chat_completion_client.chat.completions.create(
205
- model=model_id,
206
- messages=messages,
207
- )
208
-
209
- return response.choices[0].message.content
@@ -1,9 +0,0 @@
1
- from rich.text import Text
2
-
3
-
4
- def header() -> Text:
5
- text = Text()
6
- text.append("Proscenium 🎭\n", style="bold")
7
- text.append("https://the-ai-alliance.github.io/proscenium/\n")
8
- # TODO version, timestamp, ...
9
- return text
@@ -1,64 +0,0 @@
1
- from rich.console import Group
2
- from rich.table import Table
3
- from rich.text import Text
4
- from rich.panel import Panel
5
-
6
- from .chat import messages_table
7
-
8
-
9
- def parameters_table(parameters: list[dict]) -> Table:
10
-
11
- table = Table(title="Parameters", show_lines=False, box=None)
12
- table.add_column("name", justify="right")
13
- table.add_column("type", justify="left")
14
- table.add_column("description", justify="left")
15
-
16
- for name, props in parameters["properties"].items():
17
- table.add_row(name, props["type"], props["description"])
18
-
19
- # TODO denote required params
20
-
21
- return table
22
-
23
-
24
- def function_description_panel(fd: dict) -> Panel:
25
-
26
- fn = fd["function"]
27
-
28
- text = Text(f"{fd['type']} {fn['name']}: {fn['description']}\n")
29
-
30
- pt = parameters_table(fn["parameters"])
31
-
32
- panel = Panel(Group(text, pt))
33
-
34
- return panel
35
-
36
-
37
- def function_descriptions_panel(function_descriptions: list[dict]) -> Panel:
38
-
39
- sub_panels = [function_description_panel(fd) for fd in function_descriptions]
40
-
41
- panel = Panel(Group(*sub_panels), title="Function Descriptions")
42
-
43
- return panel
44
-
45
-
46
- def complete_with_tools_panel(
47
- title: str, model_id: str, tool_desc_list: list, messages: list, temperature: float
48
- ) -> Panel:
49
-
50
- text = Text(
51
- f"""
52
- model_id: {model_id}
53
- temperature: {temperature}
54
- """
55
- )
56
-
57
- panel = Panel(
58
- Group(
59
- text, function_descriptions_panel(tool_desc_list), messages_table(messages)
60
- ),
61
- title=title,
62
- )
63
-
64
- return panel
@@ -1,13 +0,0 @@
1
- import logging
2
- from rich.text import Text
3
-
4
- log = logging.getLogger(__name__)
5
-
6
-
7
- def header() -> Text:
8
- text = Text(
9
- """[bold]Proscenium[/bold] :performing_arts:
10
- [bold]The AI Alliance[/bold]"""
11
- )
12
- # TODO version, timestamp, ...
13
- return text
@@ -1,11 +0,0 @@
1
- import logging
2
- from gofannon.base import BaseTool
3
-
4
- log = logging.getLogger(__name__)
5
-
6
-
7
- def process_tools(tools: list[BaseTool]) -> tuple[dict, list]:
8
- applied_tools = [F() for F in tools]
9
- tool_map = {f.name: f.fn for f in applied_tools}
10
- tool_desc_list = [f.definition for f in applied_tools]
11
- return tool_map, tool_desc_list
@@ -1,13 +0,0 @@
1
- import logging
2
-
3
- log = logging.getLogger(__name__)
4
-
5
-
6
- def format_chat_history(chat_history) -> str:
7
- delimiter = "-" * 80 + "\n"
8
- return delimiter.join(
9
- [
10
- f"{msg['sender']} to {msg['receiver']}:\n\n{msg['content']}\n\n"
11
- for msg in chat_history
12
- ]
13
- )
@@ -1,26 +0,0 @@
1
- proscenium/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
2
- proscenium/admin/__init__.py,sha256=31XUp83v7oCEXTeJb-GgnIitfs2EWovr3QJOewZ0zSg,1026
3
- proscenium/bin/__init__.py,sha256=ThVsDG6BmnZ86gYaZLGMDcNmR6fVwBLJG3k_JawfzOY,1160
4
- proscenium/bin/bot.py,sha256=cuHj2-ldPXGRf75Ts_J_y4WfDgTB49iN3sNmnoKKFus,1713
5
- proscenium/core/__init__.py,sha256=aSUqPMn2LjZ0C_l9Tx6yqqlfCzM7oSljZxHosJyjlLU,4335
6
- proscenium/interfaces/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
7
- proscenium/interfaces/slack.py,sha256=zfj1D4O6ucVdw6HFnBIMGAbZUb5XK17c-57une42C48,10205
8
- proscenium/patterns/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
9
- proscenium/patterns/graph_rag.py,sha256=1HH1xdlAA6ypvYdP4dWFm-KXrGPUmm0T4qIdAU8mgvE,1763
10
- proscenium/patterns/rag.py,sha256=iNPL7i8zfhEGIZAI7FjAs5yKgN1iV_E-PcJiQFrm7lE,2310
11
- proscenium/patterns/tools.py,sha256=f2CD6f7CYiSs0Tm1Ff1sOL5Ti6DqJ5HQvMI7NmIgqNs,1740
12
- proscenium/util/__init__.py,sha256=FC1hjA37VvmVpF9-OlYNp9TjArH6etr6KiAvF9t_6lI,679
13
- proscenium/verbs/__init__.py,sha256=nDWNd6_TSf4vDQuHVBoAf4QfZCB3ZUFQ0M7XvifNJ-g,78
14
- proscenium/verbs/complete.py,sha256=oRvIt6NR9yhzEHpT1BRKAmf05hVXI91v83A-7OSo-zs,5147
15
- proscenium/verbs/display.py,sha256=hHFmktyJtjYLi4I1-8HUfmsuoMTIxc6JFfczASBsCbI,260
16
- proscenium/verbs/invoke.py,sha256=-Bk7Pp0EEwRTS0MJUlViZeUNo8wxnDKJj5q78KU4CdM,339
17
- proscenium/verbs/remember.py,sha256=Hh9BDRAYf7MGeMD4MzU73p6Q28KrSiLWPx4GjTW1amQ,296
18
- proscenium/verbs/display/__init__.py,sha256=GXuvaMld8tzfJGngHdwVT-YLnuRmW2G0pMdti9Vj53s,238
19
- proscenium/verbs/display/chat.py,sha256=2THBUdhG3cIIVZOnJ_AMYL4nWXKFG2cuSkX6wkm48yQ,1148
20
- proscenium/verbs/display/tools.py,sha256=eR5g-r7MGKFZY0qg-ndkW3p0mfbupV0UaAUFqJPfnNM,1491
21
- proscenium-0.0.13.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
22
- proscenium-0.0.13.dist-info/METADATA,sha256=50Uqw56v2u2hdl8onbJWYfXSqPMgSdK_0Y6djW68OrA,2229
23
- proscenium-0.0.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- proscenium-0.0.13.dist-info/entry_points.txt,sha256=bbrDWv4WKIx9UROGXcyH0eo40F8SNpDQKvXxoE8BTHI,58
25
- proscenium-0.0.13.dist-info/top_level.txt,sha256=aiTgQy73e21wnSSxM5iICXmXT3zldN8cFKeXpvJ5Xx4,11
26
- proscenium-0.0.13.dist-info/RECORD,,