iointel 1.0.0__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.
iointel/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+
2
+ from .src.agents import Agent
3
+ from .src.memory import AsyncMemory, AsyncPostgresMemoryProvider, Memory, PostgresMemoryProvider
4
+ from .src.workflow import Workflow, run_agents
5
+ from .src.agent_methods.data_models.datamodels import PersonaConfig
6
+ from .src.handlers import AsyncLoggingHandler, LoggingHandler
7
+ from .client import client
8
+
9
+
10
+ __all__ = [
11
+ "Agent",
12
+ "AsyncMemory",
13
+ "AsyncPostgresMemoryProvider",
14
+ "Memory",
15
+ "PostgresMemoryProvider",
16
+ "Workflow",
17
+ "run_agents",
18
+ "PersonaConfig",
19
+ "AsyncLoggingHandler",
20
+ "LoggingHandler",
21
+ "client"
22
+ ]
23
+
24
+
25
+ __version__="1.0.0"
File without changes
@@ -0,0 +1,168 @@
1
+ import requests
2
+ import os
3
+ import time
4
+ from typing import Optional, List
5
+ from functools import partial
6
+
7
+ BASE_URL = os.getenv("OPENAI_API_BASE_URL", "").rstrip("/")
8
+ API_KEY = os.getenv("OPENAI_API_KEY")
9
+ BASE_MCP_URL = os.getenv("BASE_MCP_URL")
10
+ try:
11
+ SLOW_MODE_SLEEP = int(os.getenv("SLOW_MODE_SLEEP"))
12
+ except (ValueError, TypeError):
13
+ SLOW_MODE_SLEEP = -1
14
+
15
+ def __make_api_call(method, **kwargs) -> dict:
16
+ start = time.time()
17
+ url = kwargs.pop("url", f"{BASE_URL}/workflows/run")
18
+ headers = kwargs.pop("headers", {})
19
+ if "Authorization" not in headers:
20
+ headers["Authorization"] = f"Bearer {API_KEY}"
21
+ response = requests.request(method, url, headers=headers, **kwargs)
22
+ response.raise_for_status()
23
+ result = response.json()
24
+ if (remain := SLOW_MODE_SLEEP - (time.time() - start)) > 0:
25
+ # HACK avoid triggering rate limit protection if told so
26
+ # by making sure each `__make_api_call()` takes at least `SLOW_MODE_SLEEP` seconds
27
+ time.sleep(remain)
28
+ return result
29
+ __make_post_call = partial(__make_api_call, method="POST")
30
+ __make_get_call = partial(__make_api_call, method="GET")
31
+
32
+
33
+ def schedule_task(task: str) -> dict:
34
+ raise NotImplementedError()
35
+ # payload = {"task": task}
36
+ # return __make_post_call(json=payload)
37
+
38
+ def run_council_task(task: str) -> dict:
39
+ raise NotImplementedError()
40
+ # return __make_post_call(json=payload)
41
+
42
+ def run_reasoning_task(text: str) -> dict:
43
+ payload = {
44
+ "text": text,
45
+ "agent_names": ["reasoning_agent"],
46
+ "args": {
47
+ "type": "solve_with_reasoning"
48
+ }
49
+ }
50
+ return __make_post_call(json=payload)
51
+
52
+ def summarize_task(text: str) -> dict:
53
+ payload = {
54
+ "text": text,
55
+ "agent_names": ["summary_agent"],
56
+ "args": {
57
+ "type": "summarize_text"
58
+ }
59
+ }
60
+ return __make_post_call(json=payload)
61
+
62
+ def sentiment_analysis(text: str) -> dict:
63
+ payload = {
64
+ "text": text,
65
+ "agent_names": ["sentiment_analysis_agent"],
66
+ "args": {
67
+ "type": "sentiment"
68
+ }
69
+ }
70
+ return __make_post_call(json=payload)
71
+
72
+ def extract_entities(text: str) -> dict:
73
+ payload = {
74
+ "text": text,
75
+ "agent_names": ["extractor"],
76
+ "args": {
77
+ "type": "extract_categorized_entities"
78
+ }
79
+ }
80
+ return __make_post_call(json=payload)
81
+
82
+ def translate_text_task(text: str, target_language: str) -> dict:
83
+ payload = {
84
+ "text": text,
85
+ "agent_names": ["translation_agent"],
86
+ "args": {
87
+ "type": "translate_text",
88
+ "target_language": target_language
89
+ }
90
+ }
91
+ return __make_post_call(json=payload)
92
+
93
+ def classify_text(text: str, classify_by: list[str]) -> dict:
94
+ payload = {
95
+ "text": text,
96
+ "agent_names": ["classification_agent"],
97
+ "args": {
98
+ "type": "classify",
99
+ "classify_by": classify_by
100
+ }
101
+ }
102
+ return __make_post_call(json=payload)
103
+
104
+ def moderation_task(text: str, threshold: float = 0.5) -> dict:
105
+ payload = {
106
+ "text": text,
107
+ "agent_names": ["moderation_agent"],
108
+ "args": {
109
+ "type": "moderation",
110
+ "threshold": threshold
111
+ }
112
+ }
113
+ return __make_post_call(json=payload)
114
+
115
+ def custom_workflow(
116
+ text: str,
117
+ name: str,
118
+ objective: str,
119
+ instructions: str = "",
120
+ agents: Optional[List[str]] = None,
121
+ context: Optional[dict] = None
122
+ ) -> dict:
123
+
124
+ payload = {
125
+ "text": text,
126
+ "agent_names": agents or ["custom_agent"],
127
+ "args": {
128
+ "type": "custom",
129
+ "name": name,
130
+ "objective": objective,
131
+ "instructions": instructions,
132
+ "context": context or {},
133
+ }
134
+ }
135
+ return __make_post_call(json=payload)
136
+
137
+ def get_tools() -> dict:
138
+ response = requests.get(f"{BASE_MCP_URL}/mcp/tools")
139
+ response.raise_for_status()
140
+ return response.json()
141
+
142
+ def get_servers() -> dict:
143
+ response = requests.get(f"{BASE_MCP_URL}/mcp/servers")
144
+ response.raise_for_status()
145
+ return response.json()
146
+
147
+ def get_agents() -> dict:
148
+ return __make_get_call(url=f"{BASE_URL}/agents/")
149
+
150
+ def upload_workflow_file(file_path: str) -> dict:
151
+ """
152
+ Uploads a workflow file to the server.
153
+ This file may contain either JSON or YAML content, which the server
154
+ will parse and validate as a WorkflowDefinition.
155
+
156
+ :param file_path: Local path to the file to upload.
157
+ :return: JSON response from the server as a dict.
158
+ :raises: HTTPError if the request fails.
159
+ """
160
+
161
+ raise NotImplementedError()
162
+ # with open(file_path, "rb") as f:
163
+ # return __make_post_call(
164
+ # url=f"{BASE_URL}/workflows/run-file",
165
+ # files={"yaml_file":
166
+ # (os.path.basename(file_path),
167
+ # f,
168
+ # "application/octet-stream")})
File without changes
File without changes
File without changes
@@ -0,0 +1,111 @@
1
+ import os
2
+
3
+ from iointel import Agent
4
+ from ..data_models.datamodels import AgentParams
5
+ from typing import Optional
6
+
7
+
8
+
9
+ AGENT_SPECS = {
10
+ "reminder_agent": {
11
+ "name": "Reminder Agent",
12
+ "instructions": "A simple agent that sends reminders."
13
+ },
14
+ "leader": {
15
+ "name": "Leader",
16
+ "instructions": """
17
+ You are the council leader,
18
+ you lead the council and provide guidance,
19
+ and administer the voting process.
20
+ """
21
+ },
22
+ "council_member1": {
23
+ "name": "Council Member 1",
24
+ "instructions": "You are a council member who provides input and votes on decisions."
25
+ },
26
+ "council_member2": {
27
+ "name": "Council Member 2",
28
+ "instructions": "You are a council member who provides input and votes on decisions."
29
+ },
30
+ "council_member3": {
31
+ "name": "Council Member 3",
32
+ "instructions": "You are a council member who provides input and votes on decisions."
33
+ },
34
+ "coder": {
35
+ "name": "Coder",
36
+ "instructions": "You are an expert python coder who provides code for the task."
37
+ },
38
+ "agent_maker": {
39
+ "name": "Agent Maker",
40
+ "instructions": "You create agents that can perform tasks from the provided code."
41
+ },
42
+ "reasoning_agent": {
43
+ "name": "Reasoning Agent",
44
+ "instructions": "You are an agent that performs reasoning steps."
45
+ },
46
+ "docker_sandbox_agent": {
47
+ "name": "Docker Sandbox Agent",
48
+ "instructions": "You are an agent that runs code in a docker sandbox."
49
+ },
50
+ "summary_agent": {
51
+ "name": "Summary Agent",
52
+ "instructions": "You are an agent that summarizes text."
53
+ },
54
+ "sentiment_analysis_agent": {
55
+ "name": "Sentiment Analysis Agent",
56
+ "instructions": "You are an agent that performs unbiased sentiment analysis on text."
57
+ },
58
+ "extractor": {
59
+ "name": "Named Entity Recognizer",
60
+ "instructions": "You are an agent that extracts named entities from text."
61
+ },
62
+ "default_agent": {
63
+ "name": "Default Agent",
64
+ "instructions": "You are an agent that does a lot of different things, you are dynamic."
65
+ },
66
+ "moderation_agent": {
67
+ "name": "Moderation Agent",
68
+ "instructions": "You are an agent that moderates content."
69
+ },
70
+ "classification_agent": {
71
+ "name": "Classification Agent",
72
+ "instructions": "You are an agent that is an expert in classifying things."
73
+ },
74
+ "translation_agent": {
75
+ "name": "Translation Agent",
76
+ "instructions": "You are an agent that is a polyglot of multiple languages and is an expert in translating text."
77
+ },
78
+ }
79
+
80
+ def create_agent(params: AgentParams) -> Agent:
81
+ """
82
+ Create a Agent instance from the given AgentParams.
83
+ """
84
+ #return Agent(name=params.name, instructions=params.instructions, persona=params.persona, tools=params.tools)
85
+ return Agent(**params.model_dump(exclude_none=True))
86
+
87
+ def create_agents():
88
+ return {
89
+ agent_key: create_agent(
90
+ AgentParams(
91
+ name=spec["name"],
92
+ instructions=spec["instructions"]
93
+ )
94
+ )
95
+ for agent_key, spec in AGENT_SPECS.items()
96
+ }
97
+
98
+
99
+ AGENTS = create_agents() if os.environ.get('LIBRARY_MODE', '').lower() != 'true' else {}
100
+
101
+
102
+ def get_agent(agent_name: str, agent_params: Optional[AgentParams] = None) -> Agent:
103
+ if agent_params is None:
104
+ agent_params = AgentParams(name="", instructions="")
105
+
106
+ # Merge or set the default name/instructions
107
+ spec = AGENT_SPECS.get(agent_name, AGENT_SPECS["default_agent"])
108
+ agent_params.name = agent_params.name or spec["name"]
109
+ agent_params.instructions = agent_params.instructions or spec["instructions"]
110
+
111
+ return create_agent(agent_params)
File without changes
@@ -0,0 +1,333 @@
1
+ import sys
2
+ from pydantic import BaseModel, Field
3
+ from typing import List, Annotated, Optional,Union, Callable
4
+ from datetime import datetime
5
+ import controlflow
6
+ from controlflow.memory.memory import Memory
7
+ from controlflow.memory.async_memory import AsyncMemory
8
+ if sys.version_info < (3, 12):
9
+ from typing_extensions import TypedDict
10
+ else:
11
+ from typing import TypedDict
12
+
13
+ class PersonaConfig(BaseModel):
14
+ """
15
+ A configuration object that describes an agent's persona or character.
16
+ """
17
+ name: Optional[str] = Field(
18
+ None,
19
+ description="If the persona has a specific name or nickname."
20
+ )
21
+ age: Optional[int] = Field(
22
+ None,
23
+ description="Approximate age of the persona (if relevant).",
24
+ ge=1
25
+ )
26
+ role: Optional[str] = Field(
27
+ None,
28
+ description="General role or type, e.g. 'a brave knight', 'a friendly teacher', etc."
29
+ )
30
+ style: Optional[str] = Field(
31
+ None,
32
+ description="A short description of the agent's style or demeanor (e.g., 'formal and polite')."
33
+ )
34
+ domain_knowledge: List[str] = Field(
35
+ default_factory=list,
36
+ description="List of domains or special areas of expertise the agent has."
37
+ )
38
+ quirks: Optional[str] = Field(
39
+ None,
40
+ description="Any unique quirks or mannerisms, e.g. 'likes using puns' or 'always references coffee.'"
41
+ )
42
+ bio: Optional[str] = Field(
43
+ None,
44
+ description="A short biography or personal background for the persona."
45
+ )
46
+ lore: Optional[str] = Field(
47
+ None,
48
+ description="In-universe lore or backstory, e.g. 'grew up in a small village with magical powers.'"
49
+ )
50
+ personality: Optional[str] = Field(
51
+ None,
52
+ description="A more direct statement of the persona's emotional or psychological traits."
53
+ )
54
+ conversation_style: Optional[str] = Field(
55
+ None,
56
+ description="How the character speaks in conversation, e.g., 'often uses slang' or 'very verbose and flowery.'"
57
+ )
58
+ description: Optional[str] = Field(
59
+ None,
60
+ description="A general descriptive text, e.g., 'A tall, lean figure wearing a cloak, with a stern demeanor.'"
61
+ )
62
+
63
+ friendliness: Optional[float] = Field(
64
+ None,
65
+ description="How friendly the agent is, from 0 (hostile) to 1 (friendly).",
66
+ ge=0,
67
+ le=1
68
+ )
69
+ creativity: Optional[float] = Field(
70
+ None,
71
+ description="How creative the agent is, from 0 (very logical) to 1 (very creative).",
72
+ ge=0,
73
+ le=1
74
+ )
75
+ curiosity: Optional[float] = Field(
76
+ None,
77
+ description="How curious the agent is, from 0 (disinterested) to 1 (very curious).",
78
+ ge=0,
79
+ le=1
80
+ )
81
+ empathy: Optional[float] = Field(
82
+ None,
83
+ description="How empathetic the agent is, from 0 (cold) to 1 (very empathetic).",
84
+ ge=0,
85
+ le=1
86
+ )
87
+ humor: Optional[float] = Field(
88
+ None,
89
+ description="How humorous the agent is, from 0 (serious) to 1 (very humorous).",
90
+ ge=0,
91
+ le=1
92
+ )
93
+ formality: Optional[float] = Field(
94
+ None,
95
+ description="How formal the agent is, from 0 (very casual) to 1 (very formal).",
96
+ ge=0,
97
+ le=1
98
+ )
99
+ emotional_stability: Optional[float] = Field(
100
+ None,
101
+ description="How emotionally stable the agent is, from 0 (very emotional) to 1 (very stable).",
102
+ ge=0,
103
+ le=1
104
+ )
105
+
106
+ def to_system_instructions(self) -> str:
107
+ """
108
+ Combine fields into a single string that can be appended to the system instructions.
109
+ Each field is optional; only non-empty fields get appended.
110
+ """
111
+ lines = []
112
+
113
+ # 1. Possibly greet with a name or reference it
114
+ if self.name:
115
+ lines.append(f"Your name is {self.name}.")
116
+
117
+ # 2. Age or approximate range
118
+ if self.age is not None:
119
+ lines.append(f"You are {self.age} years old (approximately).")
120
+
121
+ # 3. High-level role or type
122
+ if self.role:
123
+ lines.append(f"You are {self.role}.")
124
+
125
+ # 4. Style or demeanor
126
+ if self.style:
127
+ lines.append(f"Your style or demeanor is: {self.style}.")
128
+
129
+ # 5. Domain knowledge
130
+ if self.domain_knowledge:
131
+ knowledge_str = ", ".join(self.domain_knowledge)
132
+ lines.append(f"You have expertise or knowledge in: {knowledge_str}.")
133
+
134
+ # 6. Quirks
135
+ if self.quirks:
136
+ lines.append(f"You have the following quirks: {self.quirks}.")
137
+
138
+ # 7. Bio
139
+ if self.bio:
140
+ lines.append(f"Personal background: {self.bio}.")
141
+
142
+ # 8. Lore
143
+ if self.lore:
144
+ lines.append(f"Additional lore/backstory: {self.lore}.")
145
+
146
+ # 9. Personality
147
+ if self.personality:
148
+ lines.append(f"Your personality traits: {self.personality}.")
149
+
150
+ # 10. Conversation style
151
+ if self.conversation_style:
152
+ lines.append(f"In conversation, you speak in this style: {self.conversation_style}.")
153
+
154
+ # 11. General description
155
+ if self.description:
156
+ lines.append(f"General description: {self.description}.")
157
+
158
+ # 12. Personality traits
159
+ if self.friendliness is not None:
160
+ lines.append(f"Your overall Friendliness from 0 to 1 is: {self.friendliness}")
161
+
162
+ if self.creativity is not None:
163
+ lines.append(f"Your overall Creativity from 0 to 1 is: {self.creativity}")
164
+
165
+ if self.curiosity is not None:
166
+ lines.append(f"Your overall Curiosity from 0 to 1 is: {self.curiosity}")
167
+
168
+ if self.empathy is not None:
169
+ lines.append(f"Your overall Empathy from 0 to 1 is: {self.empathy}")
170
+
171
+ if self.humor is not None:
172
+ lines.append(f"Your overall Humor from 0 to 1 is: {self.humor}")
173
+
174
+ if self.formality is not None:
175
+ lines.append(f"Your overall Formality from 0 to 1 is: {self.formality}")
176
+
177
+ if self.emotional_stability is not None:
178
+ lines.append(f"Your overall Emotional stability from 0 to 1 is: {self.emotional_stability}")
179
+
180
+ # Return them joined by newlines, or any separator you prefer
181
+ return "\n".join(lines)
182
+
183
+ ##agent params###
184
+ class AgentParams(BaseModel):
185
+ name: str
186
+ instructions: str
187
+ description: Optional[str] = None
188
+ persona: Optional[PersonaConfig] = None
189
+ model: Optional[Callable] = None
190
+ tools: Optional[List[str]] | Optional[List[Callable]] = Field(default_factory=list)
191
+ llm_rules: Optional[controlflow.llm.rules.LLMRules] = None
192
+ interactive: Optional[bool] = False
193
+ memories: Optional[list[Memory]] | Optional[list[AsyncMemory]] = Field(default_factory=list)
194
+
195
+ #reasoning agent
196
+ class ReasoningStep(BaseModel):
197
+ explanation: str = Field(
198
+ description="""
199
+ A brief (<5 words) description of what you intend to
200
+ achieve in this step, to display to the user.
201
+ """
202
+ )
203
+ reasoning: str = Field(
204
+ description="A single step of reasoning, not more than 1 or 2 sentences."
205
+ )
206
+ found_validated_solution: bool
207
+
208
+
209
+ ##summary
210
+ class SummaryResult(BaseModel):
211
+ summary: str
212
+ key_points: List[str]
213
+
214
+ #translation
215
+ class TranslationResult(BaseModel):
216
+ translated: str
217
+ target_language: str
218
+
219
+
220
+ Activation = Annotated[float, Field(ge=0, le=1)]
221
+
222
+
223
+ class ModerationException(Exception):
224
+ """Exception raised when a message is not allowed."""
225
+
226
+ ...
227
+
228
+
229
+ class ViolationActivation(TypedDict):
230
+ """Violation activation."""
231
+
232
+ extreme_profanity: Annotated[Activation, Field(description="hell / damn are fine")]
233
+ sexually_explicit: Activation
234
+ hate_speech: Activation
235
+ harassment: Activation
236
+ self_harm: Activation
237
+ dangerous_content: Activation
238
+
239
+
240
+ class WorkflowStep(BaseModel):
241
+ type: str
242
+ name: Optional[str] = None
243
+ objective: Optional[str] = None
244
+ instructions: Optional[str] = None
245
+ text: Optional[str] = None # for this step’s prompt
246
+ context: Optional[dict] = None
247
+ target_language: Optional[str] = None
248
+
249
+ class WorkflowDefinition(BaseModel):
250
+ """
251
+ The top-level structure of the YAML.
252
+ name: A human-readable name for the workflow
253
+ agents: The agent definitions
254
+ workflow: The list of steps
255
+ """
256
+ name: str
257
+ text: Optional[str] = None # This holds the main text/prompt
258
+ agents: Optional[List[AgentParams]] = None
259
+ workflow: List[WorkflowStep] = Field(default_factory=list)
260
+
261
+
262
+
263
+ ### logging handlers
264
+
265
+ class BaseEventModel(BaseModel):
266
+ """
267
+ A base model to capture common fields or structure for all events.
268
+ """
269
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
270
+
271
+ class AgentMessageEvent(BaseEventModel):
272
+ event_type: str = "agent_message"
273
+ agent_name: str
274
+ content: str
275
+
276
+ class UserMessageEvent(BaseEventModel):
277
+ event_type: str = "user_message"
278
+ content: str
279
+
280
+ class OrchestratorMessageEvent(BaseEventModel):
281
+ event_type: str = "orchestrator_message"
282
+ content: str
283
+
284
+ class ToolCallEvent(BaseEventModel):
285
+ event_type: str = "tool_call"
286
+ tool_name: str
287
+
288
+ class ToolResultEvent(BaseEventModel):
289
+ event_type: str = "tool_result"
290
+ tool_name: str
291
+ result: str
292
+
293
+ class OrchestratorStartEvent(BaseEventModel):
294
+ event_type: str = "orchestrator_start"
295
+
296
+ class OrchestratorEndEvent(BaseEventModel):
297
+ event_type: str = "orchestrator_end"
298
+
299
+ class AgentMessageDeltaEvent(BaseEventModel):
300
+ event_type: str = "agent_message_delta"
301
+ delta: str
302
+
303
+ class OrchestratorErrorEvent(BaseEventModel):
304
+ event_type: str = "orchestrator_error"
305
+ error: str
306
+
307
+ class EndTurnEvent(BaseEventModel):
308
+ event_type: str = "end_turn"
309
+
310
+ class CatchallEvent(BaseEventModel):
311
+ event_type: str = "catch-all"
312
+ details: dict = {}
313
+
314
+ # Union of all event models
315
+ EventModelUnion = Union[
316
+ AgentMessageEvent,
317
+ UserMessageEvent,
318
+ OrchestratorMessageEvent,
319
+ ToolCallEvent,
320
+ ToolResultEvent,
321
+ OrchestratorStartEvent,
322
+ OrchestratorEndEvent,
323
+ AgentMessageDeltaEvent,
324
+ OrchestratorErrorEvent,
325
+ EndTurnEvent,
326
+ CatchallEvent
327
+ ]
328
+
329
+ class EventsLog(BaseModel):
330
+ """
331
+ Main aggregator for all events.
332
+ """
333
+ events: List[EventModelUnion] = []
File without changes
@@ -0,0 +1,28 @@
1
+ REASONING_INSTRUCTIONS = """
2
+ You are working on solving a difficult problem (the `goal`). Based
3
+ on your previous thoughts and the overall goal, please perform **one
4
+ reasoning step** that advances you closer to a solution. Document
5
+ your thought process and any intermediate steps you take.
6
+
7
+ After marking this task complete for a single step, you will be
8
+ given a new reasoning task to continue working on the problem. The
9
+ loop will continue until you have a valid solution.
10
+
11
+ Complete the task as soon as you have a valid solution.
12
+
13
+ **Guidelines**
14
+
15
+ - You will not be able to brute force a solution exhaustively. You
16
+ must use your reasoning ability to make a plan that lets you make
17
+ progress.
18
+ - Each step should be focused on a specific aspect of the problem,
19
+ either advancing your understanding of the problem or validating a
20
+ solution.
21
+ - You should build on previous steps without repeating them.
22
+ - Since you will iterate your reasoning, you can explore multiple
23
+ approaches in different steps.
24
+ - Use logical and analytical thinking to reason through the problem.
25
+ - Ensure that your solution is valid and meets all requirements.
26
+ - If you find yourself spinning your wheels, take a step back and
27
+ re-evaluate your approach.
28
+ """
File without changes
@@ -0,0 +1,9 @@
1
+ from datetime import datetime
2
+
3
+
4
+
5
+ def get_current_datetime() -> str:
6
+ """
7
+ Return the current datetime as a string in YYYY-MM-DD HH:MM:SS format.
8
+ """
9
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S")