Les agents de l'IA sont des machines d'État et non des dags
Synth Machines permet aux utilisateurs de créer et d'exécuter des machines d'État d'agent AI ( Synth ) en fournissant une SynthDefinition pour définir un flux de travail AI structuré.
Les machines d'État sont une construction puissante car elles permettent à un expert du domaine de déconstruire le problème en ensembles d'états et de transitions.
Les transitions entre les états peuvent ensuite appeler un LLM, un outil, un processus de données ou un mélange de nombreuses sorties.
Installez le package. pip install synth_machine[openai,togetherai,anthropic] ou poetry add synth_machine[openai,togetherai,anthropic]
Ajoutez soit de configurer vos clés d'environnement du fournisseur d'API pour lesquelles
# You only need to set the API providers you want to use.
export OPENAI_API_KUY=secret
export ANTHROPIC_API_KEY=secret
export TOGETHER_API_KEY=secret
pip install synth_machine[vllm,llamacpp] ou poetry add synth_machine[vllm,llamacpp]
Vous devrez probablement configurer CUDA, VLLM ou LLAMA.CPP pour un usage local.
Liens utiles:
agent = Synth(
config: dict[SynthDefinition], # Synth state machine defining states, transitions and prompts.
tools=[], # List of tools the agent will use
memory={}, # Any existing memory to add on top of any model_config.initial_memory
rag_runner: Optional[RAG] = None # Define a RAG integration for your agent.
postprocess_functions = [] # Any glue code functions
store : ObjectStore = ObjectStore(":memory:") # Any files created by tools will automatically go to you object store
La SynthDefinition peut être trouvée dans SynthDefinition Docs ou Synth_Machine / Synth_Definition.py. Les modèles de base pydantiques qui composent SynthDefinition seront la représentation la plus précise d'un Synth .
Nous nous attendons à ce que la spécification ait des mises à jour entre les versions principales.
À tout moment, vous pouvez vérifier l'état actuel et les déclencheurs suivants
# Check state
agent.current_state()
# Triggers
agent.interfaces_for_available_triggers()
await agent.trigger(
"[trigger_name]",
params={
"input_1": "hello"
}
)
Les appels de transition par lots publieront toute variable de sortie générée dans cette transition.
await agent.streaming_trigger(
"[trigger_name]",
params={
"input_1": "hello"
}
)
Les réponses en streaming donnent l'un des événements suivants:
class YieldTasks(StrEnum):
CHUNK = "CHUNK"
MODEL_CONFIG = "MODEL_CONFIG"
SET_MEMORY = "SET_MEMORY"
SET_ACTIVE_OUTPUT = "SET_ACTIVE_OUTPUT"
CHUNK : les générations LLM sont envoyées par des morceaux un jeton à la fois.MODEL_CONFIG : donne quel exécuteur est actuellement utilisé pour toute interface frontale spécifique du fournisseur.SET_MEMORP : envoie des événements en définissant de nouvelles variables de mémoireSET_ACTIVE_OUTPUT : donne le déclencheur de sortie de transition actuel. Cela permet aux utilisateurs d'expérimenter à l'aide de trigger , puis de s'intégrer aux générations LLM Stream en temps réel aux utilisateurs utilisant les événements côté serveur (SSE) et trigger_streaming .
Nous offrons plusieurs exécuteurs pour générer des compléments LLM de chat LLM locaux ou API.
openai : https://openai.com/api/pricing/togetherai : https://docs.together.ai/docs/inference-modelsanthropic : https://docs.anthropic.com/en/docs/models-verviewgoogle : https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/overview VLLM : https://github.com/vllm-project/vllmLlama-CPP : https://github.com/ggerganov/llama.cpp Model Config Vous pouvez spécifier le fournisseur et le modèle dans default-model-config et la base de synthé ou model_config sur la sortie de transition.
ModelConfig:
...
executor: [openai|togetherai|anthropic|vllm|llamacpp]
llm_name: [model_name]
La mémoire d'agent est un dictionnaire contenant toutes les variables provisoires que crée dans les états précédents et les entrées humaines / système.
agent.memory
# -> {
# "[memory_key]": [memory_value]
# }
Les fonctions post-traitement ne doivent être utilisées que pour le code de colle de base, toutes les fonctionnalités majeures doivent être intégrées dans les outils.
Allez sur "./tools/tofuTool/api.py pour afficher la fonctionnalité.
Démarrer l'API
cd tools/tofuTool
poetry install
poetry run uvicorn api:app --port=5001 --reload
Récupérer les spécifications de l'API
curl -X GET http://localhost:5001/openapi.json > openapi_schema.json
Définir l'outil
Vous pouvez définir un outil en tant que tel avec uniquement le nom, le point de terminaison de l'API et le schéma OpenAPI de l'outil.
tofu_tool = Tool(
name="tofu_tool",
api_endpoint="http://localhost:5001",
api_spec=tool_spec
)
La génération augmentée de récupération est un outil puissant pour améliorer les réponses LLM en fournissant des exemples sémantiquement similaires ou exerce au matériau que LLM tente de générer.
synth_machine est de manière flexible de telle sorte que vous héritez de synth_machine.RAG et crée:
embed(documents: List[str]) etquery(prompt: str, rag_config: Optional[synth_machine.RAGConfig])Il est facile d'intégrer plusieurs fournisseurs et bases de données vectorielles. Au fil du temps, il y aura des implémentations de chiffon et des chiffons communautaires dans une grande variété de fournisseurs d'incorporation et de bases de données vectorielles.
La classe de chiffon suivante est idéale pour expérimenter avec les configurations de chiffon locales sur CPU.
pip install qdrant-client, fastembed
Définir la classe de chiffon
from synth_machine.rag import RAG
from qdrant_client import AsyncQdrantClient
from fastembed import TextEmbedding
from typing import List, Optional
from qdrant_client.models import Distance, VectorParams, PointStruct
class Qdrant(RAG):
"""
VectorDB: Qdrant - https://github.com/qdrant/qdrant
Embeddings: FastEmbed - https://github.com/qdrant/fastembed
This provides fast and lightweight on-device CPU embeddings creation and
similarity search using Qdrant in memory.
"""
def __init__(
self,
collection_name: str,
embedding_model: str="BAAI/bge-small-en-v1.5",
embedding_dimensions: int=384,
embedding_threads: int=-1,
qdrant_location: str=":memory:",
):
self.embedding_model = TextEmbedding(
model_name=embedding_model,
threads=embedding_threads
)
self.embedding_dimensions = embedding_dimensions
self.qdrant = AsyncQdrantClient(qdrant_location)
self.collection_name = collection_name
async def create_collection(self) -> bool:
if await self.qdrant.collection_exists(self.collection_name):
return True
else:
return await self.qdrant.create_collection(
collection_name=self.collection_name,
vectors_config=VectorParams(
size=self.embedding_dimensions, # maps to 'BAAI/bge-small-en-v1.5' model dimensions
distance=Distance.COSINE
)
)
async def embed(self, documents: List[str], metadata: Optional[List[dict]]=None):
if metadata and len(documents) != len(metadata):
raise ValueError("documents and metadata must be the same length")
embedding_list = list(
self.embedding_model.embed(documents)
)
upsert_response = await self.qdrant.upsert(
collection_name=self.collection_name,
points=[
PointStruct(
id=i,
vector=list(vector),
payload=metadata[i]
)
for i, vector in enumerate(embedding_list)
]
)
return upsert_response.status
async def query(self, prompt: str, rag_config: RAGConfig) -> List[dict]:
embedding = next(self.embedding_model.embed([prompt]))
similar_responses = await self.qdrant.search(
collection_name=self.collection_name,
query_vector=embedding,
limit=rag_config.n
)
return [
point.payload for point in similar_responses
]
Maintenant, lancez la classe QDRANT et fournissez-vous lors de la définition Synth .
qdrant = Qdrant(collection_name="tofu_examples")
await qdrant.create_collection()
agent = Synth(
...
rag_runner=Qdrant
)
Les outils peuvent renvoyer une variété d'objets différents. Tout fichier créé par un outil ira automatiquement sur votre agent.store . Nous utilisons ObjectStore pour le stockage de fichiers, avec ObjectStore(":memory:") comme défaut.
Pour récupérer un fichier: agent.store.get(file_name)
ObjectStore permettant une intégration facile à:
from synth_machine.machine import ObjectStore
agent = Agent(
...
store=ObjectStore("gs://[bucket_name]/[prefix]))
)
Toute fonctionnalité personnalisée peut être définie comme une fonction définie par l'utilisateur (UDF).
Ceux-ci prennent Synth.memory en entrée et vous permet d'exécuter des fonctionnalités personnalisées dans le cadre de la synth-machine .
# Define postprocess function
from synth_machine.user_defined_functions import udf
@udf
def abc_postprocesss(memory):
...
return memory["variable_key"]
agent = Synth(
...
user_defined_functions = {
"abc": abc_postprocess
}
)
...
- key: trigger_udf
inputs:
- key: variable_key
outputs:
- key: example_udf
udf: abc
Remarque: Toute fonctionnalité non triviale doit être un outil et non UDF.