Lompat ke konten
dokumentasi.id
Commit a3048a3
v1
Star929

12

Pola Multi-Agent dengan OpenAI Agent SDK

Enam script terakhir repository mendemonstrasikan pola agentik dengan SDK openai-agents==0.9.2 (requirements.txt:94). README menyebut rentang 19_agents_handsoff.py sampai 24_agents_guardrails.py sebagai enam pola kunci untuk membangun sistem multi-agent (README.md:46-53). Setiap script berdiri sendiri sebagai program yang dijalankan lewat asyncio.run(main()) (19_agents_handsoff.py:32-34). 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-20).

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.
Fig. 1: Peta enam pola ke primitive SDKTiap script memetakan satu pola ke agent, aggregator, atau guardrail yang menjadi pusatnya.
ScriptPolaPrimitiveSimbol kunci
19_agents_handsoff.pyHandoff dan delegasihandoffs=[...]customer_service_manager
20_agents_tooluse.pyTool-use / function calling@function_tool, tool_use_behavior="run_llm_again"company_research_agent
21_agents_deterministic.pyRantai deterministiktrace() dan assertget_top_companies_based_on_metric
22_agents_judge_critic.pyJudge dan criticoutput_type=EvaluationFeedbackevaluator
23_agents_parallelization.pyParalelisasiasyncio.gatherresearch_team_leader_aggregator
24_agents_guardrails.pyGuardrail@input_guardrail, @output_guardrailis_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-69), sedangkan versi 24 menambahkan input_guardrails dan output_guardrails pada agent yang sama namanya (24_agents_guardrails.py:101-107).

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.
Fig. 2: Siklus satu Runner.runmain 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-81). Guardrail membaca output terstruktur lewat result.final_output_as(LegalOrOOS) (24_agents_guardrails.py:59-60).

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-15). customer_service_manager memegang handoffs=[german_agent, english_agent] dengan instruksi memilih agent sesuai bahasa request (19_agents_handsoff.py:17-21). 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-30).

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-21). Versi bersama di utils melakukan hal yang sama dan dipakai oleh script 21, 23, dan 24 (utils/api_client.py:7-10).

ToolEndpoint dan parameterBerkas
get_company_overviewhttps://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-38
get_top_companies_ranked/v1/companies/top/?classifications={dimension} tanpa n_stock20_agents_tooluse.py:44-55
get_top_companies_ranked versi rantai deterministikendpoint sama, ditambah &n_stock=321_agents_deterministic.py:40-51
get_company_financials/v1/company/report/{ticker}/?sections=financials23_agents_parallelization.py:16-26
get_revenue_segments/v1/company/get-segments/{ticker}/23_agents_parallelization.py:29-40
get_quarterly_financials/v1/financials/quarterly/{ticker}/?report_date=2024-12-31&approx=true23_agents_parallelization.py:43-54
get_revenue_segments versi berguardrailendpoint sama dengan versi 2324_agents_guardrails.py:88-99

⚠️ SystemExit adalah turunan BaseException, bukan Exception. Tool yang membungkus retrieve_from_endpoint dengan except Exception lalu return None tidak menangkap kegagalan HTTP; proses berhenti lebih dulu (20_agents_tooluse.py:37-41). Annotation -> dict pada helper juga menyesatkan karena fungsi mengembalikan string hasil json.dumps.

Agent riset di script 20 memasang kedua tool sekaligus dengan tool_use_behavior="run_llm_again" (20_agents_tooluse.py:58-63). Query contohnya meminta overview perusahaan SGX berticker D05 (20_agents_tooluse.py:65-72).

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.
Fig. 3: Rantai deterministikSatu 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-106). 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-76). 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-82). 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-65).

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.
Fig. 4: Lingkar judge dan criticPenulis 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-53). 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-64).

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-98). 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-48).

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.
Fig. 5: Fan-out dan fan-in paralelisasiTiga 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-82). 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-111). 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.
Fig. 6: Dua tripwire guardrailGuardrail 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-46). 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-65). Di sisi output, phone_number_or_email menandai tripwire_triggered=True bila jawaban memuat ".com" atau "(62) " (24_agents_guardrails.py:67-86).

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-140).

Jebakan saat menjalankan ulang

⚠️ Nama berkas di docstring sudah usang. 22_agents_judge_critic.py menyuruh python 4_judge_critic.py (22_agents_judge_critic.py:9-10), dan README mencantumkan 22_agents_critic.py untuk pola judge dan critic (README.md:51). 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.