Agenci AI do produkcji przy użyciu FastAPI, LangGraph i czystej architektury
Granice warstw, typowany stan grafu, usługi poddawalne testom oraz sposób implementacji zapewniający łatwą konserwację agentów.
To przewodnik pokazuje, jak przejść od surowców do działającego systemu w ramach tematu: „Jak projektuję agenty sztucznej inteligencji do produkcji przy użyciu FastAPI, LangGraph i Clean Architecture”. Skupiamy się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu dodać do repozytorium, nie musząc zgadywać intencji autora. Aby uzyskać ogólny obraz, zdefiniuj wprowadzenia, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać oddzielnie od kodu aplikacji. Pliki środowiskowe, magazyny haseł oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całego grafu.
@router.post("/suppliers")
async def create_supplier(request: SupplierRequest):
policies = opensearch.search(
index="supplier-policies",
query=request.description,
)
response = bedrock.converse(
modelId=MODEL_ID,
messages=build_messages(request, policies),
)
supplier = Supplier(
name=request.name,
tax_id=request.tax_id,
)
db.add(supplier)
db.commit()
sqs.send_message(
QueueUrl=SUPPLIER_QUEUE,
MessageBody=serialize(supplier),
)
return {"status": "created"}
FastAPI to interfejs — nie sama aplikacja
Ponieważ FastAPI to interfejs — a nie sama aplikacja, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno normalny przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Zanim przystąpi się do masowego importu, należy użyć ograniczeń i indeksów. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, bez powstawania duplikatów.
@router.post("/suppliers")
async def create_supplier(
request: CreateSupplierRequest,
use_case: CreateSupplierUseCase = Depends(
get_create_supplier_use_case
),
):
command = CreateSupplierCommand(
name=request.name,
tax_id=request.tax_id,
country=request.country,
)
result = await use_case.execute(command)
return CreateSupplierResponse.from_result(result)
HTTP Request
↓
FastAPI
↓
Application
↓
Domain
↓
Ports
↓
Adapters
Aplikacja komunikuje się z funkcjonalnościami, a nie z technologiami
Aby aplikacja komunikowała się z możliwościami, a nie z technologiami, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na jedną konkretne odpowiedzialność, a nie na skomplikowany łańcuch operacji. Przed masowym importem należy stosować ograniczenia i indeksy. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, bez powstawania duplikatów.
class CreateSupplierUseCase:
def __init__(
self,
repository: SupplierRepository,
policy_service: SupplierPolicyService,
event_publisher: EventPublisher,
):
self.repository = repository
self.policy_service = policy_service
self.event_publisher = event_publisher
async def execute(
self,
command: CreateSupplierCommand,
) -> Supplier:
existing = await self.repository.find_by_tax_id(
command.tax_id
)
if existing:
raise SupplierAlreadyExists(command.tax_id)
policy = await self.policy_service.evaluate(
country=command.country
)
supplier = Supplier.create(
name=command.name,
tax_id=command.tax_id,
country=command.country,
requires_approval=policy.requires_approval,
)
await self.repository.add(supplier)
await self.event_publisher.publish(
SupplierCreated(supplier.id)
)
return supplier
Porty tworzą granicę
Dla Portów należy najpierw ustalić granice, zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzucaj ciche, częściowe ukończenie zadań. Zastosuj ograniczenia i indeksy przed masowym importem danych. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, zamiast powodować problemy z duplikatami. Dla Portów należy najpierw ustalić granice, zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, bez konieczności czytania całego kodu.
ph.from typing import Protocol
class SupplierRepository(Protocol):
async def find_by_tax_id(
self,
tax_id: str,
) -> Supplier | None:
...
async def add(
self,
supplier: Supplier,
) -> None:
...
class PolicyRetriever(Protocol):
async def retrieve(
self,
query: str,
) -> list[PolicyDocument]:
...
class LLMProvider(Protocol):
async def reason(
self,
context: AgentContext,
) -> AgentDecision:
...
class EventPublisher(Protocol):
async def publish(
self,
event: DomainEvent,
) -> None:
...
SupplierRepository
PolicyRetriever
LLMProvider
EventPublisher
PostgreSQL
Amazon OpenSearch
Amazon Bedrock
AWS SQS
Gdzie mieści się Amazon Bedrock
W przypadku pytania „Gdzie mieści się Amazon Bedrock” należy zdefiniować dane wejściowe, osobę odpowiedzialną za daną czynność oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić tę czynność na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Należy sprawdzić próbkę wyników po pierwszej partii. Proby typu Cypher pomagają szybciej wykryć zmiany w etykietach i brakujące właściwości niż testy czatowe od początku do końca.
class BedrockLLMProvider(LLMProvider):
def __init__(self, client, model_id: str):
self.client = client
self.model_id = model_id
async def reason(
self,
context: AgentContext,
) -> AgentDecision:
response = self.client.converse(
modelId=self.model_id,
messages=build_messages(context),
)
return map_bedrock_response(response)
Application
↓
LLMProvider
↑
BedrockLLMProvider
Gdzie mieści się RAG
Aby określić, gdzie powinien znajdować się RAG, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, łatwe do przetestowania jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Należy sprawdzić próbkę danych po pierwszej partii. Narzędzia typu Cypher skuteczniej wykrywają zmiany w etykietach i brakujące właściwości niż testy czatowe od początku do końca.
FastAPI
↓
OpenSearch
↓
LLM
class OpenSearchPolicyRetriever(PolicyRetriever):
def __init__(
self,
opensearch_client,
embedding_provider,
):
self.client = opensearch_client
self.embedding_provider = embedding_provider
async def retrieve(
self,
query: str,
) -> list[PolicyDocument]:
vector = await self.embedding_provider.embed(query)
results = self.client.search(
index="supplier-policies",
body=build_vector_query(vector),
)
return map_documents(results)
RAG dostarcza kontekstu. Nie podejmuje decyzji.
Dla RAG dostarcza kontekstu. Nie podejmuje decyzji. Przed modyfikacją kodu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Sprawdź próbkę wyników po pierwszej partii danych. Proby typu Cypher wykrywają zmiany w etykietach oraz brakujące właściwości taniej niż testy czatowe od początku do końca. Dla RAG dostarcza kontekstu. Nie podejmuje decyzji. Przed modyfikacją kodu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu dostępnym dla operatorów.
można przeprowadzić audyt bez czytania całego grafu.RAG
↓
LLM
↓
"Looks fine"
↓
Create Supplier
RAG
↓
Relevant Context
↓
Agent Reasoning
↓
Application
↓
Domain Rules
↓
Decision
Gdzie mieści się LangGraph
Aby określić, gdzie mieści się LangGraph, należy zdefiniować dane wejściowe, osobę odpowiedzialną za daną etap oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić daną etap na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżki naprawcze. Próby ponownego wykonania, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Modeluj węzły i relacje dla pytań, które zamierzasz zadać, a nie dla każdego rzeczownika w dokumencie. Rozproszone, typowane krawędzie są lepsze od gęstych, niejasnych grafów.
Understand Request
↓
Retrieve Policies
↓
Evaluate Information
↓
Need More Data?
↙ ↘
Yes No
↓ ↓
Ask User Continue
↓
Approval Required?
↙ ↘
Yes No
↓ ↓
Human Approval Continue
↘ ↙
Request Action
graph = StateGraph(AgentState)
graph.add_node(
"understand_intent",
understand_intent,
)
graph.add_node(
"retrieve_policies",
retrieve_policies,
)
graph.add_node(
"evaluate",
evaluate_request,
)
graph.add_node(
"human_approval",
request_human_approval,
)
graph.add_node(
"request_action",
request_application_action,
)
graph.add_edge(
"understand_intent",
"retrieve_policies",
)
graph.add_edge(
"retrieve_policies",
"evaluate",
)
graph.add_conditional_edges(
"evaluate",
determine_next_step,
{
"approval": "human_approval",
"execute": "request_action",
},
)
LangGraph nie powinien stać się nowym monolitem
Aby LangGraph nie stał się nowym monolitem, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej są małe, testowalne jednostki niż rozbudowane skrypty. Gdy jakiś krok zawodzi, powinno to wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Twórz węzły i relacje modelu dla pytań, które zamierzasz zadać, a nie dla każdego rzeczownika w dokumencie. Rozproszone, typowane krawędzie są lepsze od gęstych, tajemniczych grafów.
def evaluate_node(state):
if state.amount > 100_000:
state.requires_approval = True
if state.country == "BR":
...
if state.supplier_type == "CRITICAL":
...
decision = supplier_policy.evaluate(
supplier=supplier,
context=context,
)
LangGraph → orchestrates
Domain → decides
Gdzie pasuje MCP
Aby określić, gdzie pasuje MCP, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Modeluj węzły i relacje odpowiadające pytaniom, które zamierzasz zadać, a nie każdemu rzeczownikowi w dokumencie. Rozproszone, typowane łącza są lepsze od gęstych, tajemniczych grafów. Aby określić, gdzie pasuje MCP, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całego grafu.
LLM
↓
PostgreSQL
LLM
↓
SAP
Agent
↓
MCP Tool
↓
Application API
↓
Use Case
↓
Domain
↓
Infrastructure
create_supplier
execute_sql
PostgreSQL to również adapter
W przypadku PostgreSQL to również adapter należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno normalny przebieg procesu, jak i ścieżkę przywracania. Próby ponownych działań, kontrole ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Przed masowym wprowadzaniem danych należy używać ograniczeń i indeksów. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, zamiast powodować problemy z duplikatami.
class SupplierRepository(Protocol):
async def add(
self,
supplier: Supplier,
) -> None:
...
class PostgresSupplierRepository(
SupplierRepository
):
def __init__(self, session):
self.session = session
async def add(
self,
supplier: Supplier,
) -> None:
entity = SupplierModel.from_domain(
supplier
)
self.session.add(entity)
FastAPI → doesn't know PostgreSQL exists
Application → doesn't know PostgreSQL existsDomain → doesn't know PostgreSQL existsInfrastructure → does
SAP powinien znajdować się za kolejną granicą
Aby SAP znajdował się za kolejną granicą, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, testowalnych jednostek niż rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces. Przed masowym wprowadzaniem danych należy stosować ograniczenia i indeksy. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, bez powstawania duplikatów.
CreateSupplierUseCase
↓
Persist State
↓
SupplierCreated
↓
AWS SQS
↓
Integration Worker
↓
SAP
Kompletowanie architektury
Aby stworzyć architekturę, zdefiniuj wprowadzane dane, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy wprowadzanymi danymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzucaj ciche, częściowe ukończenie zadań. Używaj ograniczeń i indeksów przed masowym importem danych. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, zamiast powodować problemy z duplikatami. Aby stworzyć architekturę, zdefiniuj wprowadzane dane, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez trudności.
całej grafie.USER
│
▼
FastAPI
│
▼
LangGraph
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
RAG LLM MCP
│ │ │
▼ ▼ │
OpenSearch Bedrock │
▼
Application
│
▼
Domain
│
┌─────────┴─────────┐
│ │
▼ ▼
PostgreSQL AWS SQS
│
▼
Worker
│
▼
SAP
Dwa światy
Dla projektu „Dwa światy” należy najpierw zdefiniować dane wejściowe, osobę odpowiedzialną za daną fazę oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić tę fazę na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżki naprawcze. Próby ponownego wykonania, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dopiero późniejszej optymalizacji. Należy sprawdzić próbkę sąsiednich elementów po pierwszej partii danych. Narzędzia typu Cypher skuteczniej wykrywają zmiany w etykietach i brakujące właściwości niż testy czatowe od początku do końca.
PROBABILISTICLLM
RAG
Natural Language
Intent Understanding
Agent Reasoning
Tool Selection
DETERMINISTIC
Authorization
Business Rules
Transactions
Persistence
Idempotency
Integration
Auditability
Praktyczny test architektury
Aby przeprowadzić praktyczny test architektury, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, łatwych do przetestowania jednostek niż rozbudowanych skryptów. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Sprawdź próbkę obszaru po pierwszej partii danych. Narzędzia typu Cypher skuteczniej wykrywają zmiany w etykietach i brakujące właściwości niż testy czatowe od początku do końca.
async def test_supplier_requires_approval():
repository = FakeSupplierRepository()
policies = FakePolicyService(
requires_approval=True
)
events = FakeEventPublisher()
use_case = CreateSupplierUseCase(
repository=repository,
policy_service=policies,
event_publisher=events,
)
supplier = await use_case.execute(
CreateSupplierCommand(
name="ACME",
tax_id="123",
country="BR",
)
)
assert supplier.requires_approval is True
Zmiana modelu powinna być nudna
Aby zmiana modelu była nudna, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Sprawdź próbkę danych po pierwszej partii. Proby typu Cypher wykrywają zmiany w etykietach i brakujące właściwości taniej niż testy czatowe od początku do końca. Aby zmiana modelu była nudna, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności ich czytania.
całą grafę.LLMProvider
▲
│
┌─────────┴─────────┐
│ │
BedrockProvider AnotherProvider
FastAPI
Application
Domain
Business Rules
Czysta architektura to nie struktura folderów
W przypadku czystej architektury to nie struktura folderów należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później w celu udoskonalenia. Modeluj węzły i relacje odpowiadające pytaniom, które zamierzasz zadać, a nie każdemu rzeczownikowi w dokumencie. Rozproszone, typowane krawędzie są lepsze od gęstych, tajemniczych grafów.
src/
├── api/
├── application/
├── domain/
├── agents/
├── ports/
└── infrastructure/
Sztuczna inteligencja sprawia, że te granice stają się ważniejsze
Z powodu tego, że sztuczna inteligencja sprawia, że te granice stają się ważniejsze, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy dany krok zawiedzie, powód awarii powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Twórz węzły i relacje modelu dla pytań, które zamierzasz zadać, a nie dla każdego rzeczownika w dokumencie. Rozproszone, typowane krawędzie są lepsze od gęstych, niejasnych grafów.
Architektura w jednym zdaniu
Dla „Architektury w jednym zdaniu” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę fazę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Modeluj węzły i relacje odpowiadające pytaniom, które zamierzasz zadać, a nie każdemu rzeczownikowi w dokumencie. Rozproszone, typowane łącza są lepsze od gęstych, tajemniczych grafów. Dla „Architektury w jednym zdaniu” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całego tekstu.
cały graf.Ostatnia myśl
W ramach ostatniej myśli należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Należy stosować ograniczenia i indeksy przed masowym importem danych. Unikalność kluczy biznesowych sprawia, że późniejsze łączenie danych przebiega w sposób przewidywalny, bez powstawania duplikatów.
Prompt
↓
LLM
↓
Tool
↓
Database
Listwa kontrolna operacyjna
Literatura pokrewna
- Anatomia zespołu biznesowego w AI: Orkiestracja agentów za pomocą LangGraph i FastAPI — Przewodnik po platformie open-source do zarządzania wieloma agentami: jak koordynowane są agenci typu współzałożyciel, menedżer i specjalista, jak wymieniają się informacjami, jak wstrzymują pracę w oczekiwaniu na zatwierdzenie i jak składają raporty.
- Orkiestracja agentów badaczy i programistów za pomocą LangGraph — Budowa aplikacji wieloagentowej na bazie LangGraph z wykorzystaniem promptów typu soul, modeli Ollama, funkcji wyszukiwania Tavily, narzędzi do przekazywania zadań oraz punktów kontrolnych Postgres dla procesów prowadzonych przez orkiestratora.