向量库、E2B 沙箱、SQL 等集成需另装 extras,缺失时会报错提示包名;E2B 沙箱需单独申请 API Key;litellm 首次导入会下载模型价格表,可用 LITELLM_LOCAL_MODEL_COST_MAP=True 改用内置副本

pip install dynamiqREADME 未说明,仅要求 Python 3.11 及以上;模型调用走 OpenAI 等外部 API,未提及本地显存或 GPU 要求
编排 RAG 与 LLM Agent 的 Python 框架,用节点和 Workflow 把多智能体流程串起来
面向智能体和 LLM 应用的编排框架,把 RAG、工具调用和多步推理流程串起来,方便搭建和运维生成式 AI 应用。
核心能力
- 用 Node 与 Workflow 构建 DAG 编排多 Agent 流程
- 支持 Agent 依赖声明与输入转换器传递上游输出
- 内置 ReAct Agent,可挂载代码沙箱等工具并异步执行
- 提供 RAG 索引流程:PDF 转换、切分、嵌入、向量库写入
- 按需安装 extras 加载向量库、文件、SQL 等集成
容易踩的坑
- v0.72.0最新正式版2026-11-02 · 2026-11-02 15:23 UTC
What's Changed
- chore(examples): replace retired Claude models in the DAG and use-case examples by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1088
- fix(scale-serp): let the node's search_type apply when the input names none by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1078
- build(deps): bump pypdf, authlib, joserfc and nltk for security fixes by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1089
- fix(nodes)!: restrict Python executor imports to a safe default allow-list by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1076
- test: time out hung tests in the integration-with-creds job by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1091
- perf: run the integration-with-creds job on the runner with more workers by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1092
- perf(llms): cut per-token streaming overhead ~5x and add an opt-in OpenAI SDK transport by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1039
- perf(llms): stream OpenAI SDK chunks as dicts without building their models by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1053
- perf(callbacks): skip the stream serialization copy for read-only callbacks by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1052
- build: dynamiq v0.72.0 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1094
Full Changelog: https://github.com/dynamiq-ai/dynamiq/compare/v0.71.0...v0.72.0
- v0.71.02026-10-30 · 2026-10-30 17:32 UTC
查看更新说明
What's Changed
- docs(vertex): replace retired gemini-2.5-flash LLM ranker model by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1077
- fix(llms): build litellm response models before parallel LLM nodes run by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1072
- chore(examples): default the Claude example helpers to claude-sonnet-5-5 by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1073
- feat(tools): add AWS Bedrock AgentCore web search and fix the search and scrape tools by @vitalii-dynamiq in https://github.com/dynamiq-ai/dynamiq/pull/1075
- feat: update bedrock cohere by @olbychos in https://github.com/dynamiq-ai/dynamiq/pull/1074
- fix(splitters): stop AutoSplitter backtracking on whitespace-only lines by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1083
- fix(evaluations)!: run PythonEvaluator and PythonMonty on a Monty worker pool by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1081
- build: dynamiq v0.71.0 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1084
Full Changelog: https://github.com/dynamiq-ai/dynamiq/compare/v0.70.0...v0.71.0
- v0.70.02026-10-27 · 2026-10-27 17:14 UTC
查看更新说明
What's Changed
- build(deps): pin litellm to 1.103.1 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1057
- build(deps): skip Python releases younger than 7 days by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1058
- chore(examples): replace retired and retiring models by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1056
- fix(examples): use gpt-5.6-sol in the Elasticsearch RAG example by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1060
- fix: default the Gemini embedders to gemini-embedding-001 by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1043
- fix(agents): don't count a paused tool call twice under call_limit by @vitalii-dynamiq in https://github.com/dynamiq-ai/dynamiq/pull/1064
- fix(agents): keep pii values restorable and Map hook approvals answerable by @vitalii-dynamiq in https://github.com/dynamiq-ai/dynamiq/pull/1065
- perf(utils): parse each JSONPath expression once by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1049
- perf(prompts): compile each Jinja template once by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1051
- build(deps): upgrade dependencies with security fixes and drop Python 3.10 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1067
- fix(connections): authenticate Cua cloud computers with the connection's API key by @vitalii-dynamiq in https://github.com/dynamiq-ai/dynamiq/pull/1069
- fix(checkpoints): find workflow checkpoints by flow run ID by @LJK-ikun in https://github.com/dynamiq-ai/dynamiq/pull/1063
- fix(streaming): track every tool call in a function-calling chunk by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1044
- feat: add FXMacroData macro data tool by @roberttidball in https://github.com/dynamiq-ai/dynamiq/pull/979
- perf!: load integrations lazily and move optional SDKs into extras by @vitalii-dynamiq in https://github.com/dynamiq-ai/dynamiq/pull/1061
- build: dynamiq v0.70.0 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1071
New Contributors
- @LJK-ikun made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/1063
- @roberttidball made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/979
Full Changelog: https://github.com/dynamiq-ai/dynamiq/compare/v0.69.0...v0.70.0
- v0.69.02026-10-21 · 2026-10-21 11:04 UTC
查看更新说明
What's Changed
- feat(cli): add memory-store commands by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1015
- fix: send project_id from workflow test and stop returning "None" from the max-loops fallback by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1017
- fix: reasoning effort by @olbychos in https://github.com/dynamiq-ai/dynamiq/pull/1021
- test(agents): run hook examples offline without an OpenAI key by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1026
- fix(embedders): use async params in HuggingFaceEmbedder async methods by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1025
- feat: add Python 3.14 support by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1022
- ci: bump GitHub Actions to their latest versions by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1034
- feat: add opt-in Parallel Search MCP example by @georgeatparallel in https://github.com/dynamiq-ai/dynamiq/pull/988
- fix: follow up on the Python 3.14 support review by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1033
- fix: default image generation and edit nodes to gpt-image-2 by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1037
- fix(converters): keep HTML table rows that contain th cells by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1001
- fix(splitters): keep title delimiter in front of its own chunk in DocumentSplitter by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1004
- fix(splitters): avoid U+FFFD in token splitter chunks cut inside a multi-byte character by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1005
- fix(embedders): allow client-only embedders to read embed_params without a connection by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1030
- fix(storages): declare numeric Turbopuffer attributes on every write by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1032
- feat(tools): add Linkup search tool by @shauryajain21 in https://github.com/dynamiq-ai/dynamiq/pull/960
- fix(pgvector): cast in/not in array to the field's numeric type by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1029
- feat(agents): write hooks and their detector with import paths, like nodes by @delamainer in https://github.com/dynamiq-ai/dynamiq/pull/1019
- chore(images): remove the ImageVariation node by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1041
- fix(storages): parenthesize not in filter so it stays inside AND groups by @moneebullah25 in https://github.com/dynamiq-ai/dynamiq/pull/1031
- docs(examples): add optional Sem stdio code lookup by @rs545837 in https://github.com/dynamiq-ai/dynamiq/pull/1038
- fix(cli): catch node settings the platform refuses before the flow is sent by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1016
- build(deps): bump aiohttp to 3.14.4 and mcp to 1.30.0 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1042
- feat: upd reasoning values by @olbychos in https://github.com/dynamiq-ai/dynamiq/pull/1047
- fix(agents): treat blank tool call arguments as an empty dict by @pcbeingused333 in https://github.com/dynamiq-ai/dynamiq/pull/1046
- build: dynamiq v0.69.0 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1048
New Contributors
- @georgeatparallel made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/988
- @pcbeingused333 made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/1037
- @shauryajain21 made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/960
- @rs545837 made their first contribution in https://github.com/dynamiq-ai/dynamiq/pull/1038
Full Changelog: https://github.com/dynamiq-ai/dynamiq/compare/v0.68.1...v0.69.0
- v0.68.12026-10-13 · 2026-10-13 17:57 UTC
查看更新说明
What's Changed
- feat(mcp): list an MCP server's tools per run with that run's headers by @mykhailobuleshnyi in https://github.com/dynamiq-ai/dynamiq/pull/1013
- build: dynamiq v0.68.1 by @tyaroshko in https://github.com/dynamiq-ai/dynamiq/pull/1014
Full Changelog: https://github.com/dynamiq-ai/dynamiq/compare/v0.68.0...v0.68.1
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Welcome to Dynamiq! 🤖
Dynamiq is your all-in-one Gen AI framework, designed to streamline the development of AI-powered applications. Dynamiq specializes in orchestrating retrieval-augmented generation (RAG) and large language model (LLM) agents.
Getting Started
Ready to dive in? Here's how you can get started with Dynamiq:
Installation
First, let's get Dynamiq installed. You'll need Python 3.11 or later, so make sure that's set up on your machine. Then run:
pip install dynamiq
This installs agents, workflows, every LLM provider, the built-in tools and memory, and reading of PDF, Word, PowerPoint and Excel files. Integrations that need a larger SDK come as extras. Using one without its extra raises an error that names the missing package:
| Extra | For |
|---|---|
vectorstores | Chroma, Elasticsearch, Milvus, OpenSearch, pgvector, Pinecone, Qdrant, Turbopuffer and Weaviate writers, retrievers and memory backends |
files | HTML and Markdown transformers, Markdown to PDF, PDF page images, the HTML header splitter and the Unstructured converter, plus pdfplumber in the Python tools |
data | matplotlib and seaborn for charts in the Python tools |
sql | MySQL, Snowflake and Databricks in the SQL executor |
browser | The Stagehand browser tool |
sandboxes | E2B and Daytona sandboxes and the Python Monty tool |
gcp | Vertex AI RAG search and the Document AI converter |
evals | The BLEU, ROUGE and string similarity metrics |
cua | The CUA desktop tool (Python 3.12 and 3.13) |
all | Every extra except cua |
pip install "dynamiq[vectorstores,files]"
dynamiq imports an integration, and litellm, the first time it is used, so import dynamiq takes a fraction of a second. A multi-threaded server that would rather pay that cost before it serves can set DYNAMIQ_EAGER_IMPORTS=1, which imports every installed integration with its package, as earlier versions did. litellm also downloads its model price list when it is imported; LITELLM_LOCAL_MODEL_COST_MAP=True makes it use the copy it ships with.
Or build the Python package from the source code:
git clone https://github.com/dynamiq-ai/dynamiq.git
cd dynamiq
uv sync --extra all
Documentation
For more examples and detailed guides, please refer to our documentation.
Examples
Simple LLM Flow
Here's a simple example to get you started with Dynamiq:
from dynamiq.nodes.llms.openai import OpenAI
from dynamiq.connections import OpenAI as OpenAIConnection
from dynamiq.prompts import Prompt, Message
# Define the prompt template for translation
prompt_template = """
Translate the following text into English: {{ text }}
"""
# Create a Prompt object with the defined template
prompt = Prompt(messages=[Message(content=prompt_template, role="user")])
# Setup your LLM (Large Language Model) Node
llm = OpenAI(
id="openai", # Unique identifier for the node
connection=OpenAIConnection(api_key="OPENAI_API_KEY"), # Connection using API key
model="gpt-4o", # Model to be used
temperature=0.3, # Sampling temperature for the model
max_tokens=1000, # Maximum number of tokens in the output
prompt=prompt # Prompt to be used for the model
)
# Run the LLM node with the input data
result = llm.run(
input_data={
"text": "Hola Mundo!" # Text to be translated
}
)
# Print the result of the translation
print(result.output)
Simple ReAct Agent with asynchronous execution
An agent that has access to the E2B Code Interpreter and is capable of solving complex coding tasks. E2B needs the
sandboxes extra. Code sandboxes are also available via Daytona (DaytonaInterpreterTool, in the same extra) and
AWS Bedrock AgentCore Code Interpreter (BedrockAgentCoreInterpreterTool, using the standard AWS connection).
import asyncio
from dynamiq.nodes.llms.openai import OpenAI
from dynamiq.connections import OpenAI as OpenAIConnection, E2B as E2BConnection
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.tools.e2b_sandbox import E2BInterpreterTool
# Initialize the E2B tool
e2b_tool = E2BInterpreterTool(
connection=E2BConnection(api_key="E2B_API_KEY")
)
# Setup your LLM
llm = OpenAI(
id="openai",
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.3,
max_tokens=1000,
)
# Create the agent
agent = Agent(
name="react-agent",
llm=llm, # Language model instance
tools=[e2b_tool], # List of tools that the agent can use
role="Senior Data Scientist", # Role of the agent
max_loops=10, # Limit on the number of processing loops
)
async def run_async_agent():
# Run the agent asynchronously with an input
result = await agent.run(
input_data={
"input": "Add the first 10 numbers and tell if the result is prime.",
}
)
print(result.output.get("content"))
# Execute the async function
if __name__ == "__main__":
asyncio.run(run_async_agent())
Configuring Two Parallel Agents with WorkFlow
from dynamiq import Workflow
from dynamiq.nodes.llms import OpenAI
from dynamiq.connections import OpenAI as OpenAIConnection
from dynamiq.nodes.agents import Agent
# Setup your LLM
llm = OpenAI(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.1,
)
# Define the first agent: a question answering agent
first_agent = Agent(
name="Expert Agent",
llm=llm,
role="Professional writer with the goal of producing well-written and informative responses.",
id="agent_1",
max_loops=5
)
# Define the second agent: a poetic writer
second_agent = Agent(
name="Poetic Rewriter Agent",
llm=llm,
role="Professional writer with the goal of rewriting user input as a poem without changing its meaning.",
id="agent_2",
max_loops=5
)
# Create a workflow to run both agents with the same input
# The `Workflow` class simplifies setting up and executing a series of nodes in a pipeline.
# It automatically handles running the agents in parallel where possible.
wf = Workflow()
wf.flow.add_nodes(first_agent)
wf.flow.add_nodes(second_agent)
# Equivalent alternative way to define the workflow:
# from dynamiq.flows import Flow
# wf = Workflow(flow=Flow(nodes=[first_agent, second_agent]))
# Run the workflow with an input
result = wf.run(
input_data={"input": "How are sin(x) and cos(x) connected in electrodynamics?"},
)
# Print the input and output for both agents
print('--- Agent 1: Input ---\n', result.output[first_agent.id].get("input").get('input'))
print('--- Agent 1: Output ---\n', result.output[first_agent.id].get("output").get('content'))
print('--- Agent 2: Input ---\n', result.output[second_agent.id].get("input").get('input'))
print('--- Agent 2: Output ---\n', result.output[second_agent.id].get("output").get('content'))
Configuring Two Sequential Agents with WorkFlow
from dynamiq import Workflow
from dynamiq.nodes.llms import OpenAI
from dynamiq.connections import OpenAI as OpenAIConnection
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.node import InputTransformer, NodeDependency
# Setup your LLM
llm = OpenAI(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.1,
)
first_agent = Agent(
name="Expert Agent",
llm=llm,
role="Professional writer with the goal of producing well-written and informative responses.", # Role of the agent
id="agent_1",
max_loops=5
)
second_agent = Agent(
name="Poetic Rewriter Agent",
llm=llm,
role="Professional writer with the goal of rewriting user input as a poem without changing its meaning.", # Role of the agent
id="agent_2",
depends=[NodeDependency(first_agent)], # Set dependency on the first agent
input_transformer=InputTransformer(
selector={"input": f"${[first_agent.id]}.output.content"} # Extract the output of the first agent as input
),
max_loops=5
)
# Create a workflow to run the agents sequentially based on dependencies.
# Without a workflow, you would need to run `first_agent`, collect its output,
# and then manually pass that output as input to `second_agent`. The workflow automates this process.
wf = Workflow()
wf.flow.add_nodes(first_agent)
wf.flow.add_nodes(second_agent)
# Equivalent alternative way to define the workflow:
# from dynamiq.flows import Flow
# wf = Workflow(flow=Flow(nodes=[first_agent, second_agent]))
# Run the workflow with an input
result = wf.run(
input_data={"input": "How are sin(x) and cos(x) connected in electrodynamics?"},
)
# Print the input and output for both agents
print('--- Agent 1: Input ---\n', result.output[first_agent.id].get("input").get('input'))
print('--- Agent 1: Output ---\n', result.output[first_agent.id].get("output").get('content'))
print('--- Agent 2: Input ---\n', result.output[second_agent.id].get("input").get('input'))
print('--- Agent 2: Output ---\n', result.output[second_agent.id].get("output").get('content'))
Multi-agent orchestration
from dynamiq import Workflow
from dynamiq.connections import OpenAI as OpenAIConnection, ScaleSerp as ScaleSerpConnection
from dynamiq.flows import Flow
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.llms import OpenAI
from dynamiq.nodes.tools.scale_serp import ScaleSerpTool
from dynamiq.nodes.types import Behavior, InferenceMode
llm = OpenAI(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.1,
)
search_tool = ScaleSerpTool(connection=ScaleSerpConnection(api_key="SCALESERP_API_KEY"))
research_agent = Agent(
name="Research Analyst",
role="Find recent market news and provide referenced highlights.",
llm=llm,
tools=[search_tool],
inference_mode=InferenceMode.XML,
max_loops=6,
behaviour_on_max_loops=Behavior.RETURN,
)
writer_agent = Agent(
name="Brief Writer",
role="Turn research highlights into a concise executive brief.",
llm=llm,
inference_mode=InferenceMode.XML,
max_loops=4,
behaviour_on_max_loops=Behavior.RETURN,
)
manager_agent = Agent(
name="Manager",
role=(
"Delegate research and writing to sub-agents.\n"
"Always call tools with {'input': '<task>'} payloads and assemble the final brief."
),
llm=llm,
tools=[research_agent, writer_agent],
inference_mode=InferenceMode.XML,
parallel_tool_calls_enabled=True,
max_loops=8,
behaviour_on_max_loops=Behavior.RETURN,
)
workflow = Workflow(flow=Flow(nodes=[manager_agent]))
result = workflow.run(
input_data={"input": "Summarize the latest developments in battery technology for investors."},
)
print(result.output[manager_agent.id]["output"]["content"])
RAG - document indexing flow
This workflow takes input PDF files, pre-processes them, converts them to vector embeddings, and stores them in the Pinecone vector database. Pinecone, like the other vector stores, needs the vectorstores extra.
The example provided is for an existing index in Pinecone. You can find examples for index creation on the docs/tutorials/rag page.
from io import BytesIO
from dynamiq import Workflow
from dynamiq.connections import OpenAI as OpenAIConnection, Pinecone as PineconeConnection
from dynamiq.nodes.converters import PyPDFConverter
from dynamiq.nodes.splitters.document import DocumentSplitter
from dynamiq.nodes.embedders import OpenAIDocumentEmbedder
from dynamiq.nodes.writers import PineconeDocumentWriter
rag_wf = Workflow()
# PyPDF document converter
converter = PyPDFConverter(document_creation_mode="one-doc-per-page")
rag_wf.flow.add_nodes(converter) # add node to the DAG
# Document splitter
document_splitter = (
DocumentSplitter(
split_by="sentence",
split_length=10,
split_overlap=1,
)
.inputs(documents=converter.outputs.documents) # map converter node output to the expected input of the current node
.depends_on(converter)
)
rag_wf.flow.add_nodes(document_splitter)
# OpenAI vector embeddings
embedder = (
OpenAIDocumentEmbedder(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="text-embedding-3-small",
)
.inputs(documents=document_splitter.outputs.documents)
.depends_on(document_splitter)
)
rag_wf.flow.add_nodes(embedder)
# Pinecone vector storage
vector_store = (
PineconeDocumentWriter(
connection=PineconeConnection(api_key="PINECONE_API_KEY"),
index_name="default",
dimension=1536,
)
.inputs(documents=embedder.outputs.documents)
.depends_on(embedder)
)
rag_wf.flow.add_nodes(vector_store)
# Prepare input PDF files
file_paths = ["example.pdf"]
input_data = {
"files": [
BytesIO(open(path, "rb").read()) for path in file_paths
],
"metadata": [
{"filename": path} for path in file_paths
],
}
# Run RAG indexing flow
rag_wf.run(input_data=input_data)
RAG - document retrieval flow
Simple retrieval RAG flow that searches for relevant documents and answers the original user question using retrieved documents.
from dynamiq import Workflow
from dynamiq.connections import OpenAI as OpenAIConnection, Pinecone as PineconeConnection
from dynamiq.nodes.embedders import OpenAITextEmbedder
from dynamiq.nodes.retrievers import PineconeDocumentRetriever
from dynamiq.nodes.llms import OpenAI
from dynamiq.prompts import Message, Prompt
# Initialize the RAG retrieval workflow
retrieval_wf = Workflow()
# Shared OpenAI connection
openai_connection = OpenAIConnection(api_key="OPENAI_API_KEY")
# OpenAI text embedder for query embedding
embedder = OpenAITextEmbedder(
connection=openai_connection,
model="text-embedding-3-small",
)
retrieval_wf.flow.add_nodes(embedder)
# Pinecone document retriever
document_retriever = (
PineconeDocumentRetriever(
connection=PineconeConnection(api_key="PINECONE_API_KEY"),
index_name="default",
top_k=5,
)
.inputs(embedding=embedder.outputs.embedding)
.depends_on(embedder)
)
retrieval_wf.flow.add_nodes(document_retriever)
# Define the prompt template
prompt_template = """
Please answer the question based on the provided context.
Question: {{ query }}
Context:
{% for document in documents %}
- {{ document.content }}
{% endfor %}
"""
# OpenAI LLM for answer generation
prompt = Prompt(messages=[Message(content=prompt_template, role="user")])
answer_generator = (
OpenAI(
connection=openai_connection,
model="gpt-4o",
prompt=prompt,
)
.inputs(
documents=document_retriever.outputs.documents,
query=embedder.outputs.query,
) # take documents from the vector store node and query from the embedder
.depends_on([document_retriever, embedder])
)
retrieval_wf.flow.add_nodes(answer_generator)
# Run the RAG retrieval flow
question = "What are the line items provided in the invoice?"
result = retrieval_wf.run(input_data={"query": question})
answer = result.output.get(answer_generator.id).get("output", {}).get("content")
print(answer)
Simple Chatbot with Memory
A simple chatbot that uses the Memory module to store and retrieve conversation history.
from dynamiq.connections import OpenAI as OpenAIConnection
from dynamiq.memory import Memory
from dynamiq.memory.backends.in_memory import InMemory
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.llms import OpenAI
AGENT_ROLE = "helpful assistant, goal is to provide useful information and answer questions"
llm = OpenAI(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.1,
)
memory = Memory(backend=InMemory())
agent = Agent(
name="Agent",
llm=llm,
role=AGENT_ROLE,
id="agent",
memory=memory,
)
def main():
print("Welcome to the AI Chat! (Type 'exit' to end)")
while True:
user_input = input("You: ")
user_id = "user"
session_id = "session"
if user_input.lower() == "exit":
break
response = agent.run({"input": user_input, "user_id": user_id, "session_id": session_id})
response_content = response.output.get("content")
print(f"AI: {response_content}")
if __name__ == "__main__":
main()
Graph Orchestrator
Graph Orchestrator allows to create any architecture tailored to specific use cases. Example of simple workflow that manages iterative process of feedback and refinement of email.
from typing import Any
from dynamiq.connections import OpenAI as OpenAIConnection
from dynamiq.nodes.agents.orchestrators.graph import END, START, GraphOrchestrator
from dynamiq.nodes.agents.orchestrators.graph_manager import GraphAgentManager
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.llms import OpenAI
llm = OpenAI(
connection=OpenAIConnection(api_key="OPENAI_API_KEY"),
model="gpt-4o",
temperature=0.1,
)
email_writer = Agent(
name="email-writer-agent",
llm=llm,
role="Write personalized emails taking into account feedback.",
)
def gather_feedback(context: dict[str, Any], **kwargs):
"""Gather feedback about email draft."""
feedback = input(
f"Email draft:\n"
f"{context.get('history', [{}])[-1].get('content', 'No draft')}\n"
f"Type in SEND to send email, CANCEL to exit, or provide feedback to refine email: \n"
)
reiterate = True
result = f"Gathered feedback: {feedback}"
feedback = feedback.strip().lower()
if feedback == "send":
print("####### Email was sent! #######")
result = "Email was sent!"
reiterate = False
elif feedback == "cancel":
print("####### Email was canceled! #######")
result = "Email was canceled!"
reiterate = False
return {"result": result, "reiterate": reiterate}
def router(context: dict[str, Any], **kwargs):
"""Determines next state based on provided feedback."""
if context.get("reiterate", False):
return "generate_sketch"
return END
orchestrator = GraphOrchestrator(
name="Graph orchestrator",
manager=GraphAgentManager(llm=llm),
)
# Attach tasks to the states. These tasks will be executed when the respective state is triggered.
orchestrator.add_state_by_tasks("generate_sketch", [email_writer])
orchestrator.add_state_by_tasks("gather_feedback", [gather_feedback])
# Define the flow between states by adding edges.
# This configuration creates the sequence of states from START -> "generate_sketch" -> "gather_feedback".
orchestrator.add_edge(START, "generate_sketch")
orchestrator.add_edge("generate_sketch", "gather_feedback")
# Add a conditional edge to the "gather_feedback" state, allowing the flow to branch based on a condition.
# The router function will determine whether the flow should go to "generate_sketch" (reiterate) or END (finish the process).
orchestrator.add_conditional_edge("gather_feedback", ["generate_sketch", END], router)
if __name__ == "__main__":
print("Welcome to email writer.")
email_details = input("Provide email details: ")
orchestrator.run(input_data={"input": f"Write and post email, provide feedback about status of email: {email_details}"})
Contributing
We love contributions! Whether it's bug reports, feature requests, or pull requests, head over to our CONTRIBUTING.md to see how you can help.
License
Dynamiq is open-source and available under the Apache 2 License.
Happy coding! 🚀