๊ธฐ๋Šฅ ์ƒ์„ธ ๋ณด๊ณ 

09. LLM ์—์ด์ „ํŠธ (Planner / Critic / Reporter, vLLM)

๋Œ€์ƒ ๋ชจ๋“ˆ: AG_src/llm/prompts.py, AG_src/llm/provider.py, AG_src/agents/{base_agent,planner,critic,reporter}.py, AG_src/config/pipeline_config.yaml ์ ˆ๋Œ€ ๊ทœ์น™: ๋ชจ๋“  ์ฃผ์žฅ์€ file_path:line ์ธ์šฉ. ๋ฏธํ™•์ธ ์‚ฌํ•ญ์€ "๋ฏธ๊ฒ€์ฆ"์œผ๋กœ ๋ช…์‹œ. ๋ณธ ๋ถ„์„์€ ์ฝ๊ธฐ ์ „์šฉ.


โ‘  ๋™์ž‘ ์›๋ฆฌ

1.1 3๊ฐœ LLM ์—์ด์ „ํŠธ์˜ ์—ญํ•  (Co-Scientist ๋ฃจํ”„)

๋ณธ ํŒŒ์ดํ”„๋ผ์ธ์€ PyRosetta mutateโ†’dock ๋ฃจํ”„๋ฅผ LLM์ด "๊ฐ€์„ค ์ฃผ๋„"๋กœ ์กฐ์ข…ํ•˜๋Š” Co-Scientist ๊ตฌ์กฐ๋‹ค. ์„ธ ์—์ด์ „ํŠธ๊ฐ€ ๊ฐ iteration์—์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

  • PlannerAgent (AG_src/agents/planner.py:212) โ€” "์—ฐ๊ตฌ ์„ค๊ณ„/์‹คํ—˜ ๊ธฐํš" (planner.py:226). ๊ฐ iteration์˜ ExperimentPlan(๊ฐ€์„ค + ํŒŒ๋ผ๋ฏธํ„ฐ + ๋ณ€์ด ๊ฐ€์ด๋“œ)์„ ์ƒ์„ฑยท๊ฐฑ์‹ ํ•œ๋‹ค (planner.py:241 create_initial_plan, planner.py:335 update_plan).
  • ScientistCriticAgent (AG_src/agents/critic.py:266) โ€” "๋น„ํŒ์  ๊ฒ€ํ† /์›์ธ ๋ถ„์„" (critic.py:285). QC ๊ฒฐ๊ณผ/๋žญํฌ ํ…Œ์ด๋ธ”์„ ํ•ด์„ํ•ด ์‹คํŒจ ์œ ํ˜•์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋‹ค์Œ iteration ํŒŒ๋ผ๋ฏธํ„ฐ ๋ณ€๊ฒฝ(์ตœ๋Œ€ 2๊ฐœ)์„ ์ œ์•ˆํ•œ๋‹ค (critic.py:297 analyze_results).
  • ReporterAgent (AG_src/agents/reporter.py:53) โ€” "์ตœ์ข… ๋ฆฌํฌํŠธ/๊ทธ๋ฆผ ์ž๋™ํ™”" (reporter.py:74). iteration ์š”์•ฝ Markdownยทlab notebookยทPyMOL ๋ Œ๋” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค (reporter.py:197 generate_summary_report).

์„ธ ์—์ด์ „ํŠธ ๋ชจ๋‘ BaseAgent(AG_src/agents/base_agent.py:56)๋ฅผ ์ƒ์†ํ•˜๋ฉฐ, BaseAgent.has_llm(base_agent.py:197)์œผ๋กœ LLM ์—ฐ๊ฒฐ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๊ณ  llm_generate_json(base_agent.py:221)์œผ๋กœ JSON ์‘๋‹ต์„ ๋ฐ›๋Š”๋‹ค.

1.2 vLLM ๋ฐฑ์—”๋“œ + Qwen3-32B no-think

LLM ํ˜ธ์ถœ์€ VLLMProvider(AG_src/llm/provider.py:251)๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค.

  • ์—”๋“œํฌ์ธํŠธ: OpenAI ํ˜ธํ™˜ POST /v1/chat/completions (provider.py:304).
  • ๋ชจ๋ธ: ์„ค์ •์ƒ qwen3-32b, base_url: http://localhost:8000, temperature: 0.3, max_tokens: 4096, timeout: 120 (AG_src/config/pipeline_config.yaml:225-231).
  • no-think ๊ฐ•์ œ: payload ์ตœ์ƒ์œ„์— chat_template_kwargs.enable_thinking=False๋ฅผ ์ง์ ‘ ์‚ฝ์ž…ํ•œ๋‹ค (provider.py:299). ์ด๋Š” Qwen3 thinking ๋ชจ๋ธ์ด content=null + reasoning๋งŒ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋ณด์ˆ˜์ ์œผ๋กœ ์‘๋‹ตํ•˜๋Š” ํ˜„์ƒ(M4 ๋ฒ„๊ทธ)์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค (provider.py:83-85, provider.py:272-274). ์„ค์ • ๊ธฐ๋ณธ๊ฐ’๋„ enable_thinking: false (pipeline_config.yaml:232).
  • thinking์ด ์ผœ์ง„ ๊ฒฝ์šฐ์˜ ์•ˆ์ „๋ง: content๊ฐ€ ๋น„๋ฉด reasoning/reasoning_content๋กœ ํด๋ฐฑํ•˜๊ณ , ๋‘˜ ๋‹ค ์—†์œผ๋ฉด ํ† ํฐ ์ดˆ๊ณผ ๊ฐ€๋Šฅ์„ฑ์„ ๊ฒฝ๊ณ ํ•˜๋ฉฐ None ๋ฐ˜ํ™˜ (provider.py:327-343).

create_provider(provider.py:351)๊ฐ€ ํŒฉํ† ๋ฆฌ๋‹ค. config.llm.provider ๊ฐ’์œผ๋กœ vllm/ollama/none์„ ์„ ํƒํ•˜๋ฉฐ (provider.py:397-420), config=None์ด๋ฉด NoneProvider(provider.py:165)๋ฅผ ๋ฐ˜ํ™˜ํ•ด LLM ์—†์ด ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๋ชจ๋“œ๋กœ ์ž‘๋™ํ•œ๋‹ค (provider.py:383-384). agent๋ณ„ override๋„ ์ง€์›: agent_name ์ง€์ • ์‹œ llm.agents.<name> ์„น์…˜์ด ์ƒ์œ„ llm ๊ฐ’์„ ๋ฎ์–ด์“ด๋‹ค (provider.py:386-396). ๋‹ค๋งŒ ์„ค์ • ํŒŒ์ผ์—์„œ agents: ๋ธ”๋ก์€ ํ˜„์žฌ ์ฃผ์„ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ์–ด ์„ธ ์—์ด์ „ํŠธ๊ฐ€ ๋™์ผ ๋ชจ๋ธ(qwen3-32b)์„ ๊ณต์œ ํ•œ๋‹ค (pipeline_config.yaml:241-254).

1.3 JSON ์Šคํ‚ค๋งˆ ๊ฐ•์ œ์™€ ํ”„๋กฌํ”„ํŠธ ๊ตฌ์กฐ

prompts.py๋Š” ์—์ด์ „ํŠธ๋ณ„ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ(SYSTEM_PROMPTS, prompts.py:77)์™€ ์ถœ๋ ฅ JSON ์Šคํ‚ค๋งˆ(OUTPUT_SCHEMAS, prompts.py:121)๋ฅผ ๋ถ„๋ฆฌ ๊ด€๋ฆฌํ•œ๋‹ค.

  • JSON ๋ชจ๋“œ: VLLMProvider.generate๊ฐ€ json_mode=True์ผ ๋•Œ response_format={"type":"json_object"}๋ฅผ ์„ค์ •ํ•œ๋‹ค (provider.py:301-302).
  • ์Šคํ‚ค๋งˆ ์ฃผ์ž…: ๊ฐ user ํ”„๋กฌํ”„ํŠธ ๋์— ํ•ด๋‹น ์Šคํ‚ค๋งˆ๋ฅผ \``json ... code ๋ธ”๋ก๋กœ ์ฒจ๋ถ€ํ•œ๋‹ค (์˜ˆ: plannerprompts.py:320-325, criticprompts.py:400-405, reporterprompts.py:574-577`).
  • planner๋Š” ๋‘ ๋ชจ๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค โ€” default(RFdiffusion/MPNN/ESMFold ํฌํ•จ, prompts.py:32)์™€ pyrosetta_only(prompts.py:46). ํ›„์ž๋Š” mutation_guidance(focus_positions + suggested_mutations)๋ฅผ ๊ฐ•์ œํ•˜๊ณ , RFdiffusion ๋“ฑ ์™ธ๋ถ€ ๋„๊ตฌ ์–ธ๊ธ‰์„ ๊ธˆ์ง€ํ•œ๋‹ค (prompts.py:50-60, ์Šคํ‚ค๋งˆ prompts.py:122-143).
  • ํŒŒ์‹ฑ ๊ฒฌ๊ณ ์„ฑ: ์‘๋‹ต์—์„œ <think>...</think> ๋ธ”๋ก ์ œ๊ฑฐ(_strip_think_blocks, provider.py:427) ํ›„, ์ง์ ‘ json.loads โ†’ ์‹คํŒจ ์‹œ ๋งˆํฌ๋‹ค์šด ์ฝ”๋“œ๋ธ”๋ก/์ค‘๊ด„ํ˜ธ ์ถ”์ถœ(_extract_json_block, provider.py:446)๋กœ 3๋‹จ๊ณ„ ํด๋ฐฑํ•œ๋‹ค (provider.py:146-151).

1.4 fail-open ํด๋ฐฑ (LLM ์‹คํŒจ ์‹œ ๊ทœ์น™ ๊ธฐ๋ฐ˜)

์„ธ ์—์ด์ „ํŠธ๋Š” ๋ชจ๋‘ "LLM ์šฐ์„ , ์‹คํŒจ ์‹œ ๊ทœ์น™ ๊ธฐ๋ฐ˜" ๊ตฌ์กฐ๋‹ค.

  • Planner: LLM ๊ณ„ํš ์ƒ์„ฑ ์‹œ๋„(planner.py:265-278) โ†’ None์ด๊ฑฐ๋‚˜ validate_plan ์‹คํŒจ ์‹œ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๊ธฐ๋ณธ ํŒŒ๋ผ๋ฏธํ„ฐ(_DEFAULT_PARAMETERS, planner.py:84)๋กœ ํด๋ฐฑ.
  • Critic: _analyze_via_llm(critic.py:567)๊ฐ€ None์ด๋ฉด FAILURE_ACTION_MAP(critic.py:150) ๊ธฐ๋ฐ˜ ๊ทœ์น™ ๋ถ„์„์œผ๋กœ ํด๋ฐฑ (critic.py:328-336).
  • Reporter: _summarize_via_llm(reporter.py:516)๊ฐ€ None์ด๋ฉด ํ…œํ”Œ๋ฆฟ ๊ธฐ๋ฐ˜ Markdown์œผ๋กœ ํด๋ฐฑ (reporter.py:222-231).
  • ์ „์†ก ๊ณ„์ธต ํšŒ๋ณต๋ ฅ: _http_post_json(provider.py:46)์ด ์ผ์‹œ์  ์˜ค๋ฅ˜(URLError/timeout/5xx)์— ๋Œ€ํ•ด ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„(๊ธฐ๋ณธ 1+2ํšŒ, provider.py:42-43), HTTP 4xx๋Š” ์˜๊ตฌ ์˜ค๋ฅ˜๋กœ ์ฆ‰์‹œ ์ค‘๋‹จ (provider.py:65-69). ์ด๋Š” vLLM ์ˆœ๊ฐ„ ์žฅ์• ๊ฐ€ ์กฐ์šฉํžˆ ๊ทœ์น™ ๊ธฐ๋ฐ˜์œผ๋กœ degrade๋˜๋Š” ๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•œ D4/F18 ๋ณด๊ฐ•์ด๋‹ค (provider.py:39-41).

์ฃผ์˜(๋ฏธ๊ฒ€์ฆ ์œ„ํ—˜): fail-open ๊ตฌ์กฐ์ด๋ฏ€๋กœ vLLM์ด ๋‹ค์šด๋˜์–ด๋„ ํŒŒ์ดํ”„๋ผ์ธ์€ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ๊ทœ์น™ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ณ„์† ๋ˆ๋‹ค. ๋กœ๊ทธ ๊ฒฝ๊ณ ๋Š” ๋‚จ์ง€๋งŒ(planner.py:276-278 ๋“ฑ), "LLM์ด ์‹ค์ œ๋กœ ๊ฐ€์„ค์„ ์ฃผ๋„ํ–ˆ๋Š”์ง€"๋Š” ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด์•ผ ์•Œ ์ˆ˜ ์žˆ๋‹ค โ€” ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ๋ฏธ๊ฒ€์ฆ.


โ‘ก ์˜ํ–ฅ โ€” ๊ฐ€์„ค ์ฃผ๋„ ํƒ์ƒ‰

ํ•ต์‹ฌ ๊ฐ€์น˜๋Š” "ํƒ์ƒ‰์ด ๋ฌด์ž‘์œ„/๊ทธ๋ฆฌ๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ LLM ๊ฐ€์„ค๋กœ ์œ ๋„๋œ๋‹ค"๋Š” ์ ์ด๋‹ค.

  • Planner๊ฐ€ falsifiable ๊ฐ€์„ค์„ ๋ช…์‹œํ•˜๊ณ (prompts.py:42, prompts.py:57), pyrosetta_only ๋ชจ๋“œ์—์„œ ๊ตฌ์ฒด์  ๋ณ€์ด ๊ฐ€์ด๋“œ(์–ด๋А ์œ„์น˜๋ฅผ ์–ด๋–ค ์ž”๊ธฐ๋กœ)๋ฅผ ์ƒ์„ฑํ•ด ๋‹ค์Œ mutateโ†’dock ๋ผ์šด๋“œ์˜ ํƒ์ƒ‰ ๊ณต๊ฐ„์„ ์ขํžŒ๋‹ค (prompts.py:126-135).
  • Critic์ด ๊ฒฐ๊ณผ๋ฅผ 6๊ฐœ ์‹คํŒจ ์œ ํ˜•(structural/sequence/docking/stability/selectivity)์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ (critic.py:72-79, ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ prompts.py:98), ์›์ธ์— ๋งž๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ/๋ณ€์ด ๋ณ€๊ฒฝ์„ ์ œ์•ˆํ•ด ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„๋ฅผ ๋‹ซ๋Š”๋‹ค.
  • ์„ ํƒ์„ฑ ์ •๋ ฌ(2026-06-10 ๋ณด๊ฐ•): planner/critic ์–‘์ชฝ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ์บ ํŽ˜์ธ์˜ 1์ฐจ ๋ชฉํ‘œ๋ฅผ "๊ฐ•ํ•œ SSTR2 ๊ฒฐํ•ฉ"์ด ์•„๋‹ˆ๋ผ "SSTR2 ์„ ํƒ์„ฑ(ฮ”margin>0)"์œผ๋กœ ์žฌ์ •์˜ํ•œ๋‹ค. planner ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋Š” SSTR2-๊ณ ์œ  ์˜์—ญ(ECL2/ECL3/TM5/TM6)๊ณผ ๋ณด์กด ์œ„์น˜ ๋ณด์กด ์ „๋žต์„ ์ง€์‹œํ•˜๊ณ (prompts.py:61-75), critic ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋Š” ฮ”marginโ‰ค0์ด๋ฉด "์บ ํŽ˜์ธ ๋ฏธ์„ฑ๊ณต"์œผ๋กœ ์ง„๋‹จํ•˜๋ผ๊ณ  ์ง€์‹œํ•œ๋‹ค (prompts.py:86-95). user ํ”„๋กฌํ”„ํŠธ์—๋Š” in-loop ์„ ํƒ์„ฑ ๋ฆฌ๋”๋ณด๋“œ๊ฐ€ ์ฃผ์ž…๋œ๋‹ค (planner prompts.py:278-291, critic prompts.py:372-398). ์ด ์‹ ํ˜ธ๋Š” ScientistCriticAgent.execute๊ฐ€ ์ปจํ…์ŠคํŠธ์˜ selectivity_leaderboard/best_delta_margin์„ ๋ชจ์•„ ์ „๋‹ฌํ•œ๋‹ค (critic.py:550-556).

์ˆœํšจ๊ณผ: LLM์ด "ฮ”margin์„ ์–‘์ˆ˜๋กœ ๋งŒ๋“œ๋Š” ๋ณ€์ด"๋ผ๋Š” ๊ณผํ•™์  ๋ชฉํ‘œ๋ฅผ ์ง์ ‘ ์ถ”๋ก ํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด, ๋‹จ์ˆœ ddG ์ตœ์ ํ™”์˜ ํ•จ์ •(off-target๋„ ๊ฐ•ํ•˜๊ฒŒ ๊ฒฐํ•ฉ)์„ ํšŒํ”ผํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ํƒ์ƒ‰์„ ํŽธํ–ฅ์‹œํ‚จ๋‹ค.


โ‘ข ๊ด€๋ จ Action Item โ€” "ํฐ LLM ๊ฒฐ์ •"

์‚ฌ์šฉ์ž ๋ฉ”๋ชจ๋ฆฌ(sstr2-goal-decisions)์˜ "ํฐ LLMยทreal PyRosettaยท์ „๋ฉด ํ†ตํ•ฉ" ๊ฒฐ์ •์ด ๋ณธ ๋ชจ๋“ˆ์— ๋ฐ˜์˜๋˜์–ด ์žˆ๋‹ค.

  • ollama qwen3:8b โ†’ vLLM qwen3.5-35b-a3b(MoE Active 3B) โ†’ qwen3-32b๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๋œ ์ด๋ ฅ์ด ์„ค์ • ์ฃผ์„์— ๊ธฐ๋ก๋จ (pipeline_config.yaml:220-226). ํ˜„์žฌ ์šด์˜ ๋ชจ๋ธ์€ qwen3-32b, GPU2 ์„œ๋น™, "~40 tok/s, JSON+no-think ๊ฒ€์ฆ๋จ" (pipeline_config.yaml:226).
  • "ํฐ LLM"์œผ๋กœ ์ „ํ™˜ํ•˜๋ฉด์„œ thinking ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”๊ฐ€ ํ•„์ˆ˜ ์กฐ๊ฑด์ด ๋จ(๋ณด์ˆ˜์  ์‘๋‹ตยทcontent null ๋ฐฉ์ง€) โ€” enable_thinking=false ์ •์ฑ…์œผ๋กœ ์ฝ”๋“œยท์„ค์ • ์–‘์ชฝ์— ๊ณ ์ • (provider.py:299, pipeline_config.yaml:232).
  • agent๋ณ„ ๋‹ค๋ฅธ LLM ์šด์˜ ์˜ต์…˜(M3, planner=CoT ๊ฐ•ํ•œ ๋ชจ๋ธ, critic/reporter=๋น ๋ฅธ MoE)์ด ์ฝ”๋“œ(provider.py:386-396)์™€ ์„ค์ • ์ฃผ์„(pipeline_config.yaml:241-254)์— ์ค€๋น„๋˜์–ด ์žˆ์œผ๋‚˜ ํ˜„์žฌ ๋น„ํ™œ์„ฑ(์ฃผ์„) โ€” ํ™•์žฅ ์—ฌ์ง€๋กœ ์‹๋ณ„๋จ, ํ™œ์„ฑํ™” ์—ฌ๋ถ€ ๋ฏธ๊ฒ€์ฆ.

โ‘ฃ ์™„์„ฑ๋„ ํ‰๊ฐ€

์™„์„ฑ๋„: ์•ฝ 85%

๊ทผ๊ฑฐ:

ํ•ญ๋ชฉ ์ƒํƒœ ๊ทผ๊ฑฐ
3 ์—์ด์ „ํŠธ LLM ํ†ตํ•ฉ ์™„๋ฃŒ planner/critic/reporter ๋ชจ๋‘ _*_via_llm ๊ฒฝ๋กœ ๊ตฌํ˜„ (planner.py:532, critic.py:567, reporter.py:516)
vLLM/Ollama/None ํ”„๋กœ๋ฐ”์ด๋” ์™„๋ฃŒ 3์ข… + ํŒฉํ† ๋ฆฌ (provider.py:165,190,251,351)
JSON ์Šคํ‚ค๋งˆ ๊ฐ•์ œ + ํŒŒ์‹ฑ ํด๋ฐฑ ์™„๋ฃŒ response_format + 3๋‹จ๊ณ„ ์ถ”์ถœ (provider.py:301, 146-151)
no-think + ์ „์†ก ์žฌ์‹œ๋„ ์™„๋ฃŒ enable_thinking (provider.py:299), ๋ฐฑ์˜คํ”„ (provider.py:46)
fail-open ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ ์™„๋ฃŒ ์„ธ ์—์ด์ „ํŠธ ์ „๋ถ€ ํด๋ฐฑ ๊ฒฝ๋กœ ๋ณด์œ 
์„ ํƒ์„ฑ in-loop ํ”ผ๋“œ๋ฐฑ ์™„๋ฃŒ planner/critic ํ”„๋กฌํ”„ํŠธ + execute ๋ฐฐ์„  (prompts.py:278, critic.py:550)
agent๋ณ„ LLM override ๊ตฌํ˜„๋์œผ๋‚˜ ๋น„ํ™œ์„ฑ ์ฝ”๋“œ ์กด์žฌ(provider.py:386), ์„ค์ • ์ฃผ์„ ์ฒ˜๋ฆฌ(pipeline_config.yaml:241)

๊ฐ์  ์‚ฌ์œ (๋ฏธ์™„/๋ถˆ์ผ์น˜):

  1. Critic LLM ์Šคํ‚ค๋งˆ์™€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์˜ selectivity ์ฐจ์› ๋ถˆ์ผ์น˜(๋ฏธ๊ฒ€์ฆ ์œ„ํ—˜): ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋Š” ์‹คํŒจ ์œ ํ˜•์— selectivity๋ฅผ ์ถ”๊ฐ€ํ•˜๋ผ๊ณ  ์ง€์‹œ(prompts.py:98)ํ•˜๋‚˜, OUTPUT_SCHEMAS["critic"].failure_analysis์—๋Š” structural/sequence/docking/stability๋งŒ ์žˆ๊ณ  selectivity ํ•„๋“œ๊ฐ€ ์—†๋‹ค (prompts.py:170-175). LLM์ด selectivity๋ฅผ ์–ด๋””์— ๋‹ด์„์ง€ ์Šคํ‚ค๋งˆ์ƒ ๋ชจํ˜ธ โ€” ์‹ค์ œ ์‘๋‹ต ์ฒ˜๋ฆฌ ์˜ํ–ฅ์€ ๋ฏธ๊ฒ€์ฆ.
  2. planner default ์Šคํ‚ค๋งˆ์™€ _PYROSETTA_ONLY_STEPS์˜ ์ž”์กด RFdiffusion ํ”์ : default ์Šคํ‚ค๋งˆ/์Šคํ… ํ…œํ”Œ๋ฆฟ์—๋Š” ์—ฌ์ „ํžˆ RFdiffusion/MPNN/ESMFold ๊ตฌ์กฐ๊ฐ€ ๋‚จ์•„ ์žˆ๋‹ค(prompts.py:144-167, planner.py:108-155). ์šด์˜ ๋ชจ๋“œ๊ฐ€ pyrosetta_only์ผ ๋•Œ๋งŒ ์ •ํ•ฉ โ€” ๋ชจ๋“œ ์„ค์ • ์œ„์น˜๋Š” ๋ณธ ๋ถ„์„ ๋ฒ”์œ„ ๋ฐ–(๋ฏธ๊ฒ€์ฆ).
  3. Reporter ํด๋ฐฑ ๋ณด๊ณ ์„œ์˜ ๋ชจ๋ธ๋ช… ํ•˜๋“œ์ฝ”๋”ฉ ์˜ค๋ฅ˜: LLM ๋ณด๊ณ ์„œ ํ—ค๋”์— "์ƒ์„ฑ ๋ฐฉ์‹: LLM (Qwen 2.5 7B)"๋กœ ์ž˜๋ชป ๊ธฐ์žฌ(reporter.py:572). ์‹ค์ œ ๋ชจ๋ธ์€ qwen3-32b โ€” ์‚ฌ์†Œํ•˜๋‚˜ ๋ช…๋ฐฑํ•œ ํ‘œ๊ธฐ ๋ฒ„๊ทธ.
  4. prompts.py ๋ชจ๋“ˆ docstring์˜ ๋ชจ๋ธ๋ช… ๋…ธํ›„ํ™”: "Qwen 2.5 7B / Qwen 3.5-35B-A3B"๋กœ ๊ธฐ์žฌ(prompts.py:4), ํ˜„ ์šด์˜ ๋ชจ๋ธ qwen3-32b์™€ ๋ถˆ์ผ์น˜.

โ‘ค ํ•™์ˆ  ๊ฐ€์น˜ โ€” Agentic AI Scientist

  • Co-Scientist / AI Scientist ํŒจ๋Ÿฌ๋‹ค์ž„ ๊ตฌํ˜„์ฒด: Planner(๊ฐ€์„ค ์ƒ์„ฑ)โ†’์‹คํ—˜(mutate-dock)โ†’Critic(์›์ธ ๋ถ„์„ยท๋ฐ˜์ฆ)โ†’Reporter(๊ธฐ๋ก)์˜ ๋‹ซํžŒ ์ž์œจ ๊ณผํ•™ ๋ฃจํ”„๋ฅผ LLM์œผ๋กœ ๊ตฌ๋™ํ•œ๋‹ค. ์ด๋Š” "AI๊ฐ€ ๊ฐ€์„ค์„ ์„ธ์šฐ๊ณ  ๊ฒ€์ฆยท์ˆ˜์ •ํ•˜๋Š”" agentic discovery ํ๋ฆ„(์˜ˆ: Google Co-Scientist, Sakana AI Scientist ๊ณ„์—ด)์˜ ๋„๋ฉ”์ธ ํŠนํ™” ์ ์šฉ์ด๋‹ค.
  • ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•œ ๊ฐ€์„ค(falsifiability) ๊ฐ•์ œ: ํ”„๋กฌํ”„ํŠธ๊ฐ€ "falsifiable hypothesis"์™€ "๋‹ค์Œ iteration์˜ pLDDT/ddG/๋„ํ‚น ๋ถ„ํฌ๋กœ ๊ฒ€์ฆ"์„ ๋ช…์‹œ(prompts.py:42, critic.py:521-523)ํ•ด, ๋‹จ์ˆœ ์ƒ์„ฑ์ด ์•„๋‹ˆ๋ผ ๊ณผํ•™์  ๋ฐฉ๋ฒ•๋ก (๊ฐ€์„ค-๊ฒ€์ฆ)์„ LLM ์ถœ๋ ฅ์— ๊ตฌ์กฐ์ ์œผ๋กœ ๋ถ€๊ณผํ•œ๋‹ค.
  • ํ™˜๊ฐ ๋ฐฉ์ง€ ์„ค๊ณ„: JSON ์Šคํ‚ค๋งˆ ๊ฐ•์ œ, no-think, ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ, ์„ ํƒ์„ฑ ๊ฐ™์€ ์ •๋Ÿ‰ ๋ชฉํ‘œ(ฮ”margin) ์ง๊ฒฐ โ€” LLM์˜ ๋น„๊ฒฐ์ •์„ฑ์„ ๋„๋ฉ”์ธ ์ œ์•ฝ์œผ๋กœ ๊ฐ€๋‘๋Š” ๋ฐฉ์‹์€ "์‹ ๋ขฐ ๊ฐ€๋Šฅํ•œ ๊ณผํ•™ ์—์ด์ „ํŠธ" ์—ฐ๊ตฌ์— ์‚ฌ๋ก€ ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค. ํŠนํžˆ critic ํ”„๋กฌํ”„ํŠธ๊ฐ€ "๊ฐ•ํ•œ ddG๊ฐ€ ๋‚˜์œ ์„ ํƒ์„ฑ์„ ๊ฐ€๋ฆฌ์ง€ ์•Š๊ฒŒ ํ•˜๋ผ"(prompts.py:94-95)๊ณ  ๋ช…์‹œํ•œ ๊ฒƒ์€ ๋‹ค๋ชฉ์  ์ตœ์ ํ™”์—์„œ LLM judge์˜ ํ•จ์ •์„ ์„ ์ œ ์ฐจ๋‹จํ•œ ์„ค๊ณ„.
  • ์žฌํ˜„์„ฑ/์ถ”์ ์„ฑ: ๋ณ€๊ฒฝ์„ ์ตœ๋Œ€ 2๊ฐœ๋กœ ์ œํ•œ(์›์ธ-๊ฒฐ๊ณผ ์ถ”์ , critic.py:276)ํ•˜๊ณ  changes_from_prev๋ฅผ ๊ธฐ๋ก(planner.py:570-574)ํ•˜๋Š” ๊ฒƒ์€ ์‹คํ—˜ ๋…ธํŠธ๋ถ ์ž๋™ํ™”์˜ ํ•™์ˆ ์  ๋ชจ๋ฒ” ์‚ฌ๋ก€.

โ‘ฅ ์‚ฌ์šฉ๋ฒ•

6.1 ํ”„๋กœ๋ฐ”์ด๋” ์ƒ์„ฑ

from AG_src.llm.provider import create_provider
import yaml

config = yaml.safe_load(open("AG_src/config/pipeline_config.yaml"))
provider = create_provider(config)                    # ์ƒ์œ„ llm ์„ค์ • ์‚ฌ์šฉ (vllm qwen3-32b)
provider = create_provider(config, agent_name="critic")  # agent๋ณ„ override ์ ์šฉ (ํ™œ์„ฑ ์‹œ)
provider = create_provider(None)                      # NoneProvider โ†’ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๋ชจ๋“œ

(์‹œ๊ทธ๋‹ˆ์ฒ˜: provider.py:351)

6.2 ์—์ด์ „ํŠธ์— LLM ์ฃผ์ž…

from AG_src.agents.planner import PlannerAgent

planner = PlannerAgent(llm_provider=provider, planner_mode="pyrosetta_only")
plan = planner.create_initial_plan(receptor_config={...}, constraints={...})
# ๋˜๋Š” context ๊ธฐ๋ฐ˜
result = planner.execute({"iteration": 1, "receptor_config": {...}, "constraints": {...}})

(์ƒ์„ฑ์ž: planner.py:223, execute: planner.py:501. critic: critic.py:281/critic.py:534, reporter: reporter.py:67/reporter.py:465)

6.3 ์ง์ ‘ JSON ์ƒ์„ฑ / ๋ณ€์ด ์ƒ์„ฑ ํ”„๋กฌํ”„ํŠธ

from AG_src.llm.prompts import build_variant_generation_prompt, VARIANT_DESIGN_SYSTEM_PROMPT

prompt = build_variant_generation_prompt("AGCKNFFWKTFTSC", [1,2,4,5,6,11,12,13], n_mutations=3)
result = provider.generate_json(prompt, system_prompt=VARIANT_DESIGN_SYSTEM_PROMPT)

(prompts.py:461, prompts.py:445)

6.4 ์ „์ œ ์กฐ๊ฑด

  • vLLM ์„œ๋ฒ„๊ฐ€ http://localhost:8000์—์„œ qwen3-32b๋ฅผ ์„œ๋น™ ์ค‘์ด์–ด์•ผ ํ•จ (pipeline_config.yaml:226-227). ๋ฏธ๊ฐ€๋™ ์‹œ ์ž๋™์œผ๋กœ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ.
  • timeout 120s, max_tokens 4096 (pipeline_config.yaml:229-231).

โ‘ฆ ํ•„์š” ์ด์œ 

  1. ๋ฌด์ž‘์œ„ ํƒ์ƒ‰์˜ ๋น„ํšจ์œจ ํšŒํ”ผ: SST-14 ๊ฐ€๋ณ€ ์œ„์น˜ 8๊ณณ(pipeline_config.yaml:196)์— ๋Œ€ํ•œ ์กฐํ•ฉ ํญ๋ฐœ์„ LLM ๊ฐ€์„ค๋กœ ์ขํ˜€, mutateโ†’dock ๊ฐ™์€ ๊ณ ๋น„์šฉ(ํ›„๋ณด๋‹น ์ตœ๋Œ€ 1800s, pipeline_config.yaml:209) ํ‰๊ฐ€๋ฅผ ๊ฐ€์น˜ ๋†’์€ ๋ณ€์ด์— ์ง‘์ค‘์‹œํ‚จ๋‹ค.
  2. ์„ ํƒ์„ฑ์ด๋ผ๋Š” ๋‹ค๋ชฉ์  ํ•จ์ • ํ•ด๊ฒฐ: ddG๋งŒ ์ตœ์ ํ™”ํ•˜๋ฉด native SST-14์ฒ˜๋Ÿผ pan-agonist๊ฐ€ ๋˜์–ด๋ฒ„๋ฆฌ๋Š” ํ•จ์ •์„, "ฮ”margin์„ ์–‘์ˆ˜๋กœ"๋ผ๋Š” ๋ช…์‹œ์  ๋ชฉํ‘œ๋ฅผ LLM์— ๋ถ€๊ณผํ•ด ํšŒํ”ผํ•œ๋‹ค (prompts.py:61-75, prompts.py:86-95).
  3. ์ž์œจ ์‹คํ—˜ ๋ฃจํ”„์˜ ๋‘๋‡Œ: ์ฝ”๋“œ ๊ธฐ๋ฐ˜ ์—์ด์ „ํŠธ(qc_ranker, diversity_manager, builder, pipeline_config.yaml:234)๋Š” "์‹คํ–‰"์„ ๋‹ด๋‹นํ•˜๊ณ , LLM ์—์ด์ „ํŠธ๋Š” "์™œ/๋ฌด์—‡์„ ๋‹ค์Œ์—"๋ผ๋Š” ์˜์‚ฌ๊ฒฐ์ •์„ ๋‹ด๋‹นํ•œ๋‹ค โ€” Co-Scientist ๋ถ„์—…์˜ ํ•ต์‹ฌ ๋‘๋‡Œ ์—ญํ• .
  4. ์šด์˜ ์•ˆ์ •์„ฑ: vLLM ์žฅ์• ยทํ† ํฐ ์ดˆ๊ณผยทJSON ๊นจ์ง์—๋„ ํŒŒ์ดํ”„๋ผ์ธ์ด ๋ฉˆ์ถ”์ง€ ์•Š๋„๋ก fail-open + ์žฌ์‹œ๋„ + ๋‹ค๋‹จ๊ณ„ ํŒŒ์‹ฑ์„ ๊ฐ–์ถฐ, ๋ฌดํ•œ ๋ฐœ๊ตด ์—”์ง„(๋ฉ”๋ชจ๋ฆฌ sstr2-selectivity-goal) ๊ฐ™์€ ์žฅ๊ธฐ ๋ฌด์ธ ์šด์˜์„ ๊ฐ€๋Šฅ์ผ€ ํ•œ๋‹ค.

๊ฒ€์ฆ ์ธ์šฉ ๋ชฉ๋ก

  • AG_src/llm/provider.py:46-82 โ€” _http_post_json ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„, 4xx ์˜๊ตฌ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
  • AG_src/llm/provider.py:122-151 โ€” generate_json, think ๋ธ”๋ก ์ œ๊ฑฐ + JSON ํด๋ฐฑ
  • AG_src/llm/provider.py:165-183 โ€” NoneProvider (๊ทœ์น™ ๊ธฐ๋ฐ˜ ๋ชจ๋“œ)
  • AG_src/llm/provider.py:251-344 โ€” VLLMProvider, enable_thinking, content/reasoning ํด๋ฐฑ
  • AG_src/llm/provider.py:299 โ€” chat_template_kwargs.enable_thinking payload ์ฃผ์ž…
  • AG_src/llm/provider.py:301-302 โ€” response_format JSON ๋ชจ๋“œ
  • AG_src/llm/provider.py:351-420 โ€” create_provider ํŒฉํ† ๋ฆฌ + agent override
  • AG_src/llm/provider.py:427-470 โ€” _strip_think_blocks, _extract_json_block
  • AG_src/llm/prompts.py:32-75 โ€” planner default / pyrosetta_only ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์„ ํƒ์„ฑ ๊ฐ€์ด๋“œ)
  • AG_src/llm/prompts.py:80-113 โ€” critic / reporter ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์„ ํƒ์„ฑ ์ •๋ ฌ)
  • AG_src/llm/prompts.py:121-205 โ€” OUTPUT_SCHEMAS (4์ข… JSON ์Šคํ‚ค๋งˆ)
  • AG_src/llm/prompts.py:228-327 โ€” format_planner_prompt (+ ์„ ํƒ์„ฑ ๋ฆฌ๋”๋ณด๋“œ ์ฃผ์ž…)
  • AG_src/llm/prompts.py:330-407 โ€” format_critic_prompt (+ ์„ ํƒ์„ฑ ์„น์…˜)
  • AG_src/llm/prompts.py:445-532 โ€” VARIANT_DESIGN_SYSTEM_PROMPT, build_variant_generation_prompt
  • AG_src/llm/prompts.py:535-579 โ€” format_reporter_prompt
  • AG_src/agents/base_agent.py:56-239 โ€” BaseAgent, has_llm, llm_generate_json
  • AG_src/agents/planner.py:212-613 โ€” PlannerAgent, LLM ๋ถ„๊ธฐ + ๊ทœ์น™ ํด๋ฐฑ + sanitize
  • AG_src/agents/critic.py:150-259 โ€” FAILURE_ACTION_MAP (๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ)
  • AG_src/agents/critic.py:266-634 โ€” ScientistCriticAgent, _analyze_via_llm, ์„ ํƒ์„ฑ ๋ฐฐ์„ 
  • AG_src/agents/reporter.py:53-586 โ€” ReporterAgent, _summarize_via_llm, ํด๋ฐฑ ํ…œํ”Œ๋ฆฟ
  • AG_src/agents/reporter.py:572 โ€” ๋ชจ๋ธ๋ช… ํ‘œ๊ธฐ ์˜ค๋ฅ˜ ("Qwen 2.5 7B")
  • AG_src/config/pipeline_config.yaml:219-254 โ€” LLM ์„ค์ • (vllm qwen3-32b, enable_thinking false, agent override ์ฃผ์„)