From 0abe269b00ccbd74bbeed09c05925a0518a525a3 Mon Sep 17 00:00:00 2001
From: Diwank Singh Tomer
Date: Wed, 2 Oct 2024 17:21:27 -0400
Subject: [PATCH 1/2] doc: Update README.md to add a callout for hacktoberfest
people
Signed-off-by: Diwank Singh Tomer
---
README-CN.md | 29 +++++++++++-
README.md | 124 +++++++++++----------------------------------------
2 files changed, 54 insertions(+), 99 deletions(-)
diff --git a/README-CN.md b/README-CN.md
index 22bd8cb81..8dca6dcbb 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -26,6 +26,30 @@
+*****
+
+## 🌟 诚邀贡献者!
+
+我们很高兴欢迎新的贡献者加入 Julep 项目!我们创建了几个"适合新手的问题"来帮助您入门。以下是您可以贡献的方式:
+
+1. 查看我们的 [CONTRIBUTING.md](CONTRIBUTING.md) 文件,了解如何贡献的指南。
+2. 浏览我们的[适合新手的问题](https://github.com/julep-ai/julep/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22),找到一个您感兴趣的任务。
+3. 如果您有任何问题或需要帮助,请随时在我们的 [Discord](https://discord.com/invite/JTSBGRZrzj) 频道上联系我们。
+
+您的贡献,无论大小,对我们都很宝贵。让我们一起创造令人惊叹的东西吧!🚀
+
+### 🎉 DevFest.AI 2024年10月
+
+激动人心的消息!我们将在整个2024年10月参与 DevFest.AI 活动!🗓️
+
+- 在此活动期间为 Julep 做出贡献,有机会赢得超棒的 Julep 周边和礼品!🎁
+- 加入来自世界各地的开发者,为 AI 仓库做出贡献并参与精彩的活动。
+- 非常感谢 DevFest.AI 组织这个fantastic的活动!
+
+> [!TIP]
+> 准备好加入这场盛会了吗?**[发推文开始参与](https://twitter.com/intent/tweet?text=Pumped%20to%20be%20participating%20in%20%40devfestai%20with%20%40julep_ai%20building%20%23ai%20%23agents%20%23workflows%20Let's%20gooo!%20https%3A%2F%2Fgit.new%2Fjulep)**,让我们开始编码吧!🖥️
+
+![Julep DevFest.AI](https://media.giphy.com/media/YjyUeyotft6epaMHtU/giphy.gif)
*****
@@ -50,6 +74,9 @@
请继续关注我们即将发布的稳定版本的更多更新!📢
+
+*****
+
## 简介
Julep 是一个开源平台,用于创建具有可定制工作流的持久 AI 代理。它提供了开发、管理和部署 AI 驱动应用程序的工具,注重灵活性和易用性。
@@ -634,4 +661,4 @@ results = client.documents.search(
## 致谢
-我们要感谢所有贡献者和开源社区为他们宝贵的资源和贡献。
+我们要感谢所有贡献者和开源社区为他们宝贵的资源和贡献。
\ No newline at end of file
diff --git a/README.md b/README.md
index 37a3651be..af87426b1 100644
--- a/README.md
+++ b/README.md
@@ -7,104 +7,6 @@
The **Quick Start Guide Focused README** is the most promising for optimizing the time to first workflow. It allows developers to get hands-on experience quickly, which is essential for engagement and understanding.
-* * *
-
-**Outline for the README:**
-
-1. **Title and Badges**
- * Julep Logo or Title
- * Build status, npm version, license badges
-2. **Introduction**
- * _Briefly explain what Julep is and its purpose._
- * Emphasize how it simplifies building persistent AI agents with workflows.
-3. **Features**
- * _Highlight key features with a focus on "tasks" (AI workflows)._
- * Mention support for persistent sessions, tool integration, and document management.
-4. **Installation**
- * _Provide npm installation command:_
-
- bash
-
- Copy code
-
- `npm install julep`
-
-5. **Quick Start Guide**
- * **Step 1: Import Julep**
- * _Show how to import Julep into a project._
-
- javascript
-
- Copy code
-
- `const Julep = require('julep');`
-
- * **Step 2: Initialize the Agent**
- * _Guide on creating a new agent with basic settings._
-
- javascript
-
- Copy code
-
- `const agent = new Julep.Agent({ name: 'MyAgent', model: 'gpt-4-turbo', });`
-
- * **Step 3: Chat with the Agent**
- * _Provide a simple example of a chat with the agent._
-
- javascript
-
- Copy code
-
- `const response = await client.sessions.chat({ session_id: session.id, message: 'Hello, how are you?' });`
-
- * **Step 4: Define a Task (Workflow)**
- * _Provide a simple example of a task definition._
-
- javascript
-
- Copy code
-
- `const task = { name: 'GreetingTask', main: [ { prompt: 'Say hello to the user.', }, ], }; agent.addTask(task);`
-
- * **Step 5: Execute the Task**
- * _Show how to run the task and handle the output._
-
- javascript
-
- Copy code
-
- `agent.executeTask('GreetingTask').then((output) => { console.log(output); });`
-
-6. **Understanding Tasks**
- * _Explain what tasks are and how they function within Julep._
- * Describe different types of workflow steps.
- * Prompt, Tool Call, Evaluate, etc.
- * _Note:_ Link to detailed documentation for each step type.
-7. **Advanced Features**
- * _Briefly mention other capabilities:_
- * Adding tools to agents.
- * Managing sessions and users.
- * Document integration and search.
-8. **API Reference**
- * _Link to full API documentation._
- * Mention key endpoints for agents, tasks, and executions.
-9. **Examples and Tutorials**
- * _Provide links to example projects or further tutorials._
- * Encourage users to explore and build upon provided examples.
-10. **Contributing**
- * _Instructions for contributing to the project._
- * Link to contribution guidelines and code of conduct.
-11. **Support and Community**
- * _Information on how to get help._
- * Links to community forums, chat groups, or issue trackers.
-12. **License**
- * _State the project's license._
- * Provide a link to the LICENSE file.
-13. **Acknowledgements**
- * _Credit to contributors and used resources._
-
-* * *
-
**Notes:**
* **Code Examples:** Ensure all code snippets are easy to understand and copy-paste ready.
@@ -144,6 +46,31 @@ The **Quick Start Guide Focused README** is the most promising for optimizing th
*****
+## 🌟 Call for Contributors!
+
+We're excited to welcome new contributors to the Julep project! We've created several "good first issues" to help you get started. Here's how you can contribute:
+
+1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines on how to contribute.
+2. Browse our [good first issues](https://github.com/julep-ai/julep/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to find a task that interests you.
+3. If you have any questions or need help, don't hesitate to reach out on our [Discord](https://discord.com/invite/JTSBGRZrzj) channel.
+
+Your contributions, big or small, are valuable to us. Let's build something amazing together! 🚀
+
+### 🎉 DevFest.AI October 2024
+
+Exciting news! We're participating in DevFest.AI throughout October 2024! 🗓️
+
+- Contribute to Julep during this event and get a chance to win awesome Julep merch and swag! 🎁
+- Join developers from around the world in contributing to AI repositories and participating in amazing events.
+- A big thank you to DevFest.AI for organizing this fantastic initiative!
+
+> [!TIP]
+> Ready to join the fun? **[Tweet to start participating](https://twitter.com/intent/tweet?text=Pumped%20to%20be%20participating%20in%20%40devfestai%20with%20%40julep_ai%20building%20%23ai%20%23agents%20%23workflows%20Let's%20gooo!%20https%3A%2F%2Fgit.new%2Fjulep)** and let's get coding! 🖥️
+
+![Julep DevFest.AI](https://media.giphy.com/media/YjyUeyotft6epaMHtU/giphy.gif)
+
+*****
+
## 🎉🚀 **Exciting News: Julep 1.0 Alpha Release!** 🚀🎉
We're thrilled to announce the **alpha** release of Julep 1.0! 🥳
@@ -165,6 +92,7 @@ We're thrilled to announce the **alpha** release of Julep 1.0! 🥳
Stay tuned for more updates as we approach our stable release! 📢
+*****
## Introduction
From 46ed8facb35c1b17978bbb21bdf20246c19d3bf0 Mon Sep 17 00:00:00 2001
From: Hamada Salhab
Date: Thu, 3 Oct 2024 00:47:50 +0300
Subject: [PATCH 2/2] feat(agents-api): Add static checking for Jinja templates
& Python expressions in task creation | Add validation for subworkflows
(#570)
Closes #535
> [!IMPORTANT]
> Add static validation for Python expressions and Jinja templates in
task creation and validate subworkflows in `openapi_model.py` and
`steps.tsp`.
>
> - **Validation**:
> - Add `validate_python_expression()` and `validate_jinja_template()`
in `openapi_model.py`.
> - Integrate validation into `EvaluateStep`, `ToolCallStep`,
`PromptStep`, `SetStep`, `LogStep`, `ReturnStep`, `YieldStep`,
`IfElseWorkflowStep`, and `MapReduceStep` in `openapi_model.py`.
> - **Models**:
> - Update `CreateTaskRequest` in `openapi_model.py` to validate
subworkflows using `WorkflowType`.
> - Add `YieldStep` to `MappableWorkflowStep` and
`NonConditionalWorkflowStep` in `steps.tsp`.
> - **Misc**:
> - Reorder `YieldStep` in `Tasks.py` to maintain consistency.
>
> This description was created by [](https://www.ellipsis.dev?ref=julep-ai%2Fjulep&utm_source=github&utm_medium=referral)
for 9952ad5700812126c0aa7f1bfa26467e88b60aab. It will automatically
update as commits are pushed.
---------
Co-authored-by: Diwank Singh Tomer
---
agents-api/agents_api/autogen/Tasks.py | 26 ++-
.../agents_api/autogen/openapi_model.py | 216 +++++++++++++++++-
typespec/tasks/steps.tsp | 3 +-
3 files changed, 233 insertions(+), 12 deletions(-)
diff --git a/agents-api/agents_api/autogen/Tasks.py b/agents-api/agents_api/autogen/Tasks.py
index 48dba4ad7..9dd531c47 100644
--- a/agents-api/agents_api/autogen/Tasks.py
+++ b/agents-api/agents_api/autogen/Tasks.py
@@ -35,10 +35,10 @@ class CaseThen(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
)
"""
@@ -63,10 +63,10 @@ class CaseThenUpdateItem(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
)
"""
@@ -130,10 +130,10 @@ class CreateTaskRequest(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| IfElseWorkflowStep
| SwitchStep
@@ -227,6 +227,7 @@ class ForeachDo(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
)
"""
The steps to run for each iteration
@@ -251,6 +252,7 @@ class ForeachDoUpdateItem(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
)
"""
The steps to run for each iteration
@@ -324,10 +326,10 @@ class IfElseWorkflowStep(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
)
"""
@@ -342,10 +344,10 @@ class IfElseWorkflowStep(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| None,
Field(None, alias="else"),
@@ -376,10 +378,10 @@ class IfElseWorkflowStepUpdateItem(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
)
"""
@@ -394,10 +396,10 @@ class IfElseWorkflowStepUpdateItem(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| None,
Field(None, alias="else"),
@@ -462,6 +464,7 @@ class Main(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
)
"""
The steps to run for each iteration
@@ -503,6 +506,7 @@ class MainModel(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
)
"""
The steps to run for each iteration
@@ -543,6 +547,7 @@ class ParallelStep(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
],
Field(max_length=100),
]
@@ -569,6 +574,7 @@ class ParallelStepUpdateItem(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
],
Field(max_length=100),
]
@@ -596,10 +602,10 @@ class PatchTaskRequest(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| IfElseWorkflowStepUpdateItem
| SwitchStepUpdateItem
@@ -874,10 +880,10 @@ class Task(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| IfElseWorkflowStep
| SwitchStep
@@ -1009,10 +1015,10 @@ class UpdateTaskRequest(BaseModel):
| LogStep
| EmbedStep
| SearchStep
+ | YieldStep
| ReturnStep
| SleepStep
| ErrorWorkflowStep
- | YieldStep
| WaitForInputStep
| IfElseWorkflowStep
| SwitchStep
diff --git a/agents-api/agents_api/autogen/openapi_model.py b/agents-api/agents_api/autogen/openapi_model.py
index 5c5a8c86f..48811eb20 100644
--- a/agents-api/agents_api/autogen/openapi_model.py
+++ b/agents-api/agents_api/autogen/openapi_model.py
@@ -1,10 +1,12 @@
# ruff: noqa: F401, F403, F405
+import ast
from typing import Annotated, Any, Generic, Literal, Self, Type, TypeVar, get_args
from uuid import UUID
+import jinja2
from litellm.utils import _select_tokenizer as select_tokenizer
from litellm.utils import token_counter
-from pydantic import AwareDatetime, Field
+from pydantic import AwareDatetime, Field, field_validator, model_validator, validator
from ..common.utils.datetime import utcnow
from .Agents import *
@@ -152,6 +154,179 @@ def from_model_input(
)
+# Patch Task Workflow Steps
+# --------------------------------------
+
+
+def validate_python_expression(expr: str) -> tuple[bool, str]:
+ try:
+ ast.parse(expr)
+ return True, ""
+ except SyntaxError as e:
+ return False, f"SyntaxError in '{expr}': {str(e)}"
+
+
+def validate_jinja_template(template: str) -> tuple[bool, str]:
+ env = jinja2.Environment()
+ try:
+ parsed_template = env.parse(template)
+ for node in parsed_template.body:
+ if isinstance(node, jinja2.nodes.Output):
+ for child in node.nodes:
+ if isinstance(child, jinja2.nodes.Name):
+ # Check if the variable is a valid Python expression
+ is_valid, error = validate_python_expression(child.name)
+ if not is_valid:
+ return (
+ False,
+ f"Invalid Python expression in Jinja template '{template}': {error}",
+ )
+ return True, ""
+ except jinja2.exceptions.TemplateSyntaxError as e:
+ return False, f"TemplateSyntaxError in '{template}': {str(e)}"
+
+
+_EvaluateStep = EvaluateStep
+
+
+class EvaluateStep(_EvaluateStep):
+ @field_validator("evaluate")
+ def validate_evaluate_expressions(cls, v):
+ for key, expr in v.items():
+ is_valid, error = validate_python_expression(expr)
+ if not is_valid:
+ raise ValueError(f"Invalid Python expression in key '{key}': {error}")
+ return v
+
+
+_ToolCallStep = ToolCallStep
+
+
+class ToolCallStep(_ToolCallStep):
+ @field_validator("arguments")
+ def validate_arguments(cls, v):
+ if isinstance(v, dict):
+ for key, expr in v.items():
+ if isinstance(expr, str):
+ is_valid, error = validate_python_expression(expr)
+ if not is_valid:
+ raise ValueError(
+ f"Invalid Python expression in arguments key '{key}': {error}"
+ )
+ return v
+
+
+_PromptStep = PromptStep
+
+
+class PromptStep(_PromptStep):
+ @field_validator("prompt")
+ def validate_prompt(cls, v):
+ if isinstance(v, str):
+ is_valid, error = validate_jinja_template(v)
+ if not is_valid:
+ raise ValueError(f"Invalid Jinja template in prompt: {error}")
+ elif isinstance(v, list):
+ for item in v:
+ if "content" in item:
+ is_valid, error = validate_jinja_template(item["content"])
+ if not is_valid:
+ raise ValueError(
+ f"Invalid Jinja template in prompt content: {error}"
+ )
+ return v
+
+
+_SetStep = SetStep
+
+
+class SetStep(_SetStep):
+ @field_validator("set")
+ def validate_set_expressions(cls, v):
+ for key, expr in v.items():
+ is_valid, error = validate_python_expression(expr)
+ if not is_valid:
+ raise ValueError(
+ f"Invalid Python expression in set key '{key}': {error}"
+ )
+ return v
+
+
+_LogStep = LogStep
+
+
+class LogStep(_LogStep):
+ @field_validator("log")
+ def validate_log_template(cls, v):
+ is_valid, error = validate_jinja_template(v)
+ if not is_valid:
+ raise ValueError(f"Invalid Jinja template in log: {error}")
+ return v
+
+
+_ReturnStep = ReturnStep
+
+
+class ReturnStep(_ReturnStep):
+ @field_validator("return_")
+ def validate_return_expressions(cls, v):
+ for key, expr in v.items():
+ is_valid, error = validate_python_expression(expr)
+ if not is_valid:
+ raise ValueError(
+ f"Invalid Python expression in return key '{key}': {error}"
+ )
+ return v
+
+
+_YieldStep = YieldStep
+
+
+class YieldStep(_YieldStep):
+ @field_validator("arguments")
+ def validate_yield_arguments(cls, v):
+ if isinstance(v, dict):
+ for key, expr in v.items():
+ is_valid, error = validate_python_expression(expr)
+ if not is_valid:
+ raise ValueError(
+ f"Invalid Python expression in yield arguments key '{key}': {error}"
+ )
+ return v
+
+
+_IfElseWorkflowStep = IfElseWorkflowStep
+
+
+class IfElseWorkflowStep(_IfElseWorkflowStep):
+ @field_validator("if_")
+ def validate_if_expression(cls, v):
+ is_valid, error = validate_python_expression(v)
+ if not is_valid:
+ raise ValueError(f"Invalid Python expression in if condition: {error}")
+ return v
+
+
+_MapReduceStep = MapReduceStep
+
+
+class MapReduceStep(_MapReduceStep):
+ @field_validator("over")
+ def validate_over_expression(cls, v):
+ is_valid, error = validate_python_expression(v)
+ if not is_valid:
+ raise ValueError(f"Invalid Python expression in over: {error}")
+ return v
+
+ @field_validator("reduce")
+ def validate_reduce_expression(cls, v):
+ if v is not None:
+ is_valid, error = validate_python_expression(v)
+ if not is_valid:
+ raise ValueError(f"Invalid Python expression in reduce: {error}")
+ return v
+
+
# Workflow related models
# -----------------------
@@ -228,6 +403,29 @@ class Task(_Task):
# Patch some models to allow extra fields
# --------------------------------------
+WorkflowType = RootModel[
+ list[
+ EvaluateStep
+ | ToolCallStep
+ | PromptStep
+ | GetStep
+ | SetStep
+ | LogStep
+ | EmbedStep
+ | SearchStep
+ | ReturnStep
+ | SleepStep
+ | ErrorWorkflowStep
+ | YieldStep
+ | WaitForInputStep
+ | IfElseWorkflowStep
+ | SwitchStep
+ | ForeachStep
+ | ParallelStep
+ | MapReduceStep
+ ]
+]
+
_CreateTaskRequest = CreateTaskRequest
@@ -240,6 +438,22 @@ class CreateTaskRequest(_CreateTaskRequest):
}
)
+ @model_validator(mode="after")
+ def validate_subworkflows(self) -> Self:
+ subworkflows = {
+ k: v
+ for k, v in self.model_dump().items()
+ if k not in _CreateTaskRequest.model_fields
+ }
+
+ for workflow_name, workflow_definition in subworkflows.items():
+ try:
+ WorkflowType.model_validate(workflow_definition)
+ setattr(self, workflow_name, WorkflowType(workflow_definition))
+ except Exception as e:
+ raise ValueError(f"Invalid subworkflow '{workflow_name}': {str(e)}")
+ return self
+
CreateOrUpdateTaskRequest = CreateTaskRequest
diff --git a/typespec/tasks/steps.tsp b/typespec/tasks/steps.tsp
index 3495def1b..2267ae320 100644
--- a/typespec/tasks/steps.tsp
+++ b/typespec/tasks/steps.tsp
@@ -49,7 +49,8 @@ alias MappableWorkflowStep =
| SetStep
| LogStep
| EmbedStep
- | SearchStep;
+ | SearchStep
+ | YieldStep;
alias NonConditionalWorkflowStep =
| MappableWorkflowStep