12
Pola Multi-Agent dengan OpenAI Agent SDK
Enam script terakhir repository mendemonstrasikan pola agentik dengan SDK openai-agents==0.9.2 (requirements.txt:941). README menyebut rentang 19_agents_handsoff.py sampai 24_agents_guardrails.py sebagai enam pola kunci untuk membangun sistem multi-agent (README.md:46-532). Setiap script berdiri sendiri sebagai program yang dijalankan lewat asyncio.run(main()) (19_agents_handsoff.py:32-343). Pola yang memanggil data pasar berbagi satu helper, retrieve_from_endpoint, yang menyuntikkan SECTORS_API_KEY ke header Authorization lalu mengembalikan json.dumps(data) (utils/api_client.py:12-204).
Halaman ini hanya membahas keenam script tersebut. Untuk pengisian variabel lingkungan dan API key, lihat Persiapan Lingkungan dan Konfigurasi API Key.
Peta enam pola ke primitive SDK
graph TD
S19["19_agents_handsoff.py"] -->|"handoffs"| MGR["customer_service_manager"]
S20["20_agents_tooluse.py"] -->|"tools"| CR["company_research_agent"]
S21["21_agents_deterministic.py"] -->|"trace + assert"| DCR["get_top_companies_based_on_metric"]
S22["22_agents_judge_critic.py"] -->|"while True"| EVAL["evaluator"]
S23["23_agents_parallelization.py"] -->|"asyncio.gather"| AGG["research_team_leader_aggregator"]
S24["24_agents_guardrails.py"] -->|"input_guardrails"| RB["company_revenue_breakdown_agent"]
MGR --> GER["german_agent"]
MGR --> ENG["english_agent"]
RB --> IG["is_legal_oos_guardrail"]
RB --> OG["phone_number_or_email"]
style S19 fill:#f59e0b,color:#000
style S20 fill:#f59e0b,color:#000
style S21 fill:#f59e0b,color:#000
style S22 fill:#f59e0b,color:#000
style S23 fill:#f59e0b,color:#000
style S24 fill:#f59e0b,color:#000
style MGR fill:#2d7d46,color:#fff
style CR fill:#2d7d46,color:#fff
style DCR fill:#2d7d46,color:#fff
style EVAL fill:#2d7d46,color:#fff
style AGG fill:#2d7d46,color:#fff
style RB fill:#2d7d46,color:#fff
style GER fill:#8b5cf6,color:#fff
style ENG fill:#8b5cf6,color:#fff
style IG fill:#8b5cf6,color:#fff
style OG fill:#8b5cf6,color:#fff
%% fig: Peta enam pola ke primitive SDK
%% desc: Tiap script memetakan satu pola ke agent, aggregator, atau guardrail yang menjadi pusatnya.| Script | Pola | Primitive | Simbol kunci |
|---|---|---|---|
19_agents_handsoff.py | Handoff dan delegasi | handoffs=[...] | customer_service_manager |
20_agents_tooluse.py | Tool-use / function calling | @function_tool, tool_use_behavior="run_llm_again" | company_research_agent |
21_agents_deterministic.py | Rantai deterministik | trace() dan assert | get_top_companies_based_on_metric |
22_agents_judge_critic.py | Judge dan critic | output_type=EvaluationFeedback | evaluator |
23_agents_parallelization.py | Paralelisasi | asyncio.gather | research_team_leader_aggregator |
24_agents_guardrails.py | Guardrail | @input_guardrail, @output_guardrail | is_legal_oos_guardrail |
Nama company_revenue_breakdown_agent dipakai di dua script dengan definisi berbeda. Versi 23 membawa satu tool dan output_type=str (23_agents_parallelization.py:64-695), sedangkan versi 24 menambahkan input_guardrails dan output_guardrails pada agent yang sama namanya (24_agents_guardrails.py:101-1076).
Siklus satu pemanggilan Runner.run
sequenceDiagram
autonumber
participant M as main
participant R as Runner
participant A as Agent
participant T as function_tool
participant S as Sectors API
M->>R: await Runner.run(agent, input)
R->>A: instructions
A->>T: panggilan tool dengan argumen
T->>S: retrieve_from_endpoint(url)
S-->>T: response JSON
T-->>A: string hasil
A-->>R: final_output
R-->>M: result
M->>M: result.final_output, result.new_items
M->>R: result.to_input_list() untuk turn berikutnya
%% fig: Siklus satu Runner.run
%% desc: main menyerahkan input ke Runner, agent memakai tool, lalu hasil dibaca dari final_output dan new_items.Semua script memakai bentuk yang sama: Runner.run(agent, input) di-await, lalu jawaban dibaca dari final_output. Pola yang menyimpan riwayat percakapan memakai result.to_input_list() sebagai input turn berikutnya dan ItemHelpers.text_message_outputs(result.new_items) untuk mengekstrak teks (22_agents_judge_critic.py:80-817). Guardrail membaca output terstruktur lewat result.final_output_as(LegalOrOOS) (24_agents_guardrails.py:59-608).
Handoff: customer_service_manager
german_agent dan english_agent tidak membawa tool; seluruh perilakunya diatur oleh instructions, yaitu "Always respond in German. Be polite and concise." dan "Always respond in English." (19_agents_handsoff.py:7-159). customer_service_manager memegang handoffs=[german_agent, english_agent] dengan instruksi memilih agent sesuai bahasa request (19_agents_handsoff.py:17-2110). main mengirim query berbahasa Jerman, "Hallo, ich habe ein Problem und muss mit dem Manager sprechen", lalu mencetak result.final_output (19_agents_handsoff.py:23-3011).
Tool-use dan helper retrieve_from_endpoint
20_agents_tooluse.py mendefinisikan helper lokal sendiri dengan headers = {"Authorization": SECTORS_API_KEY} dan raise SystemExit(err) saat HTTPError (20_agents_tooluse.py:13-2112). Versi bersama di utils melakukan hal yang sama dan dipakai oleh script 21, 23, dan 24 (utils/api_client.py:7-1013).
| Tool | Endpoint dan parameter | Berkas |
|---|---|---|
get_company_overview | https://api.sectors.app/v1/company/report/{ticker}/?sections=overview untuk Indonesia, /v1/sgx/company/report/{ticker}/ untuk Singapore, /v1/klse/company/report/{ticker}/ untuk Malaysia; assert country.lower() in ["indonesia", "singapore", "malaysia"] | 20_agents_tooluse.py:23-3814 |
get_top_companies_ranked | /v1/companies/top/?classifications={dimension} tanpa n_stock | 20_agents_tooluse.py:44-5515 |
get_top_companies_ranked versi rantai deterministik | endpoint sama, ditambah &n_stock=3 | 21_agents_deterministic.py:40-5116 |
get_company_financials | /v1/company/report/{ticker}/?sections=financials | 23_agents_parallelization.py:16-2617 |
get_revenue_segments | /v1/company/get-segments/{ticker}/ | 23_agents_parallelization.py:29-4018 |
get_quarterly_financials | /v1/financials/quarterly/{ticker}/?report_date=2024-12-31&approx=true | 23_agents_parallelization.py:43-5419 |
get_revenue_segments versi berguardrail | endpoint sama dengan versi 23 | 24_agents_guardrails.py:88-9920 |
⚠️
SystemExitadalah turunanBaseException, bukanException. Tool yang membungkusretrieve_from_endpointdenganexcept Exceptionlalureturn Nonetidak menangkap kegagalan HTTP; proses berhenti lebih dulu (20_agents_tooluse.py:37-4121). Annotation-> dictpada helper juga menyesatkan karena fungsi mengembalikan string hasiljson.dumps.
Agent riset di script 20 memasang kedua tool sekaligus dengan tool_use_behavior="run_llm_again" (20_agents_tooluse.py:58-6322). Query contohnya meminta overview perusahaan SGX berticker D05 (20_agents_tooluse.py:65-7223).
Rantai deterministik
graph LR
IN["input_prompt"] --> A1["get_top_companies_based_on_metric"]
A1 --> GATE{"assert isinstance(final_output, list)"}
GATE -->|"list"| A2["company_research_agent"]
GATE -->|"bukan list"| FAIL["AssertionError: Invalid tickers"]
A2 --> NOTES[("3_research_notes.txt")]
style IN fill:#f59e0b,color:#000
style A1 fill:#2d7d46,color:#fff
style GATE fill:#f59e0b,color:#000
style A2 fill:#2d7d46,color:#fff
style FAIL fill:#dc2626,color:#fff
style NOTES fill:#336791,color:#fff
%% fig: Rantai deterministik
%% desc: Satu agent memilih ticker, gate assert menyaring output, lalu tiap ticker diriset dan ditulis ke berkas catatan.Seluruh alur dijalankan di dalam satu trace("Deterministic research flow") di main (21_agents_deterministic.py:92-10624). Langkah pertama dipegang get_top_companies_based_on_metric, yang membatasi output_type=List[str] dan memerintahkan ticker dikembalikan tanpa sufiks .JK (21_agents_deterministic.py:71-7625). Gate berikutnya menghentikan alur bila output bukan list: assert isinstance(top_companies_ranked.final_output, list), "Invalid tickers" (21_agents_deterministic.py:104-106). Setelah itu company_research_agent dengan output_type=str diriset per ticker (21_agents_deterministic.py:84-89), dan hasilnya ditambahkan ke 3_research_notes.txt bersama datetime.now().isoformat(); ticker yang gagal ditandai ❌ Failed to get data for {ticker} (21_agents_deterministic.py:108-126).
determine_companies_to_research dengan output_type=ValidTickers didefinisikan di berkas yang sama, tetapi tidak dirujuk oleh main mana pun di berkas itu (21_agents_deterministic.py:78-8229). Tool csv_export juga terdaftar lewat @function_tool dan menulis export.csv, namun tidak dipasang ke daftar tools agent mana pun (21_agents_deterministic.py:53-6530).
Judge dan critic
stateDiagram-v2
[*] --> Generate
Generate --> Evaluate: Runner.run(evaluator, input_items)
Evaluate --> Pass: score == "pass"
Evaluate --> Revise: score expect_improvement atau fail
Revise --> Generate: append Feedback ke input_items
Pass --> [*]
classDef ok fill:#2d7d46,color:#fff
classDef warn fill:#f59e0b,color:#000
classDef derived fill:#8b5cf6,color:#fff
class Generate,Evaluate derived
class Revise warn
class Pass ok
%% fig: Lingkar judge dan critic
%% desc: Penulis merangkum catatan, evaluator memberi skor, dan ringkasan diulang sampai skornya pass.dataclass EvaluationFeedback memuat feedback: str dan score: Literal["pass", "expect_improvement", "fail"] (22_agents_judge_critic.py:50-5331). Agent evaluator memakai model itu sebagai output_type dan diinstruksikan "Never give it a pass on the first try, but be increasingly generous so its chance of passing increases over time." (22_agents_judge_critic.py:56-6432).
Putaran di main berjalan dalam while True di bawah trace("LLM as a judge"): hasil penulis dibaca lewat to_input_list() dan text_message_outputs, evaluator dijalankan pada input_items yang sama, dan bila skornya bukan pass feedback ditambahkan sebagai pesan user berawalan Feedback: (22_agents_judge_critic.py:66-9833). Penulis sendiri membaca berkas 3_research_notes.txt lewat tool read_company_data_from_txt, sehingga keluarannya bergantung pada hasil halaman rantai deterministik (22_agents_judge_critic.py:25-4834).
Paralelisasi
graph LR
IN["input_prompt"] --> G1["company_financials_research_agent"]
IN --> G2["company_revenue_breakdown_agent"]
IN --> G3["company_quarterly_financials_agent"]
G1 --> OUT["outputs: tiga string"]
G2 --> OUT
G3 --> OUT
OUT --> AGG["research_team_leader_aggregator"]
style IN fill:#f59e0b,color:#000
style G1 fill:#2d7d46,color:#fff
style G2 fill:#2d7d46,color:#fff
style G3 fill:#2d7d46,color:#fff
style OUT fill:#8b5cf6,color:#fff
style AGG fill:#2d7d46,color:#fff
%% fig: Fan-out dan fan-in paralelisasi
%% desc: Tiga agent dijalankan serentak lewat asyncio.gather, outputnya digabung, lalu diringkas satu aggregator.Tiga agent riset memakai satu tool masing-masing dan output_type=str, sementara research_team_leader_aggregator tidak memegang tool (23_agents_parallelization.py:57-8235). Di dalam trace("Parallelization"), ketiganya dijalankan serentak dengan asyncio.gather, teksnya diambil lewat ItemHelpers.text_message_outputs, digabung dengan "\n\n".join(outputs), lalu dikirim ke aggregator (23_agents_parallelization.py:86-11136). Pola ini memilih hasil gabungan, bukan memilih satu hasil terbaik di antara ketiganya.
Guardrail
graph TD
U["user_input"] --> IG{"is_legal_oos_guardrail"}
IG -->|"tripwire_triggered = false"| AG["company_revenue_breakdown_agent"]
IG -->|"tripwire_triggered = true"| T1["InputGuardrailTripwireTriggered"]
AG --> OG{"phone_number_or_email"}
OG -->|"tripwire_triggered = false"| OUT["result.final_output"]
OG -->|"tripwire_triggered = true"| T2["OutputGuardrailTripwireTriggered"]
style U fill:#f59e0b,color:#000
style IG fill:#f59e0b,color:#000
style OG fill:#f59e0b,color:#000
style AG fill:#2d7d46,color:#fff
style OUT fill:#2d7d46,color:#fff
style T1 fill:#dc2626,color:#fff
style T2 fill:#dc2626,color:#fff
%% fig: Dua tripwire guardrail
%% desc: Guardrail input dan output berjalan pada agent yang sama; satu tripwire true menghentikan run dan melempar exception.Model LegalOrOOS berisi reasoning: str dan is_legal_or_out_of_scope: bool, dipakai oleh guardrail_agent bernama "Guardrail check" (24_agents_guardrails.py:37-4637). is_legal_oos_guardrail berjalan sebagai @input_guardrail, memanggil Runner.run(guardrail_agent, input, context=context.context), lalu meneruskan final_output.is_legal_or_out_of_scope sebagai tripwire_triggered (24_agents_guardrails.py:49-6538). Di sisi output, phone_number_or_email menandai tripwire_triggered=True bila jawaban memuat ".com" atau "(62) " (24_agents_guardrails.py:67-8639).
main menangkap kedua exception dan menjawab dengan pesan tetap: "I can't help you with that. Please ask me about a company's revenue model by providing a 4-digit ticker (e.g. BBRI)" untuk input, serta "The output contains sensitive information or violates Supertype policies." untuk output. Pada run yang lolos guardrail, input_data = result.to_input_list() dipakai agar percakapan berlanjut seperti punya memori (24_agents_guardrails.py:109-14040).
Jebakan saat menjalankan ulang
⚠️ Nama berkas di docstring sudah usang.
22_agents_judge_critic.pymenyuruhpython 4_judge_critic.py(22_agents_judge_critic.py:9-1041), dan README mencantumkan22_agents_critic.pyuntuk pola judge dan critic (README.md:5142). Jalankan berkas yang benar-benar ada.
21_agents_deterministic.py juga menulis ke 3_research_notes.txt dengan mode append, jadi menjalankan rantai deterministik berulang kali menumpuk catatan lama; berkas itulah yang kemudian dibaca oleh pola judge dan critic.