mahendra0120 commited on
Commit
1fc5cfb
·
verified ·
1 Parent(s): 04b0118

Delete ASP/qwen_agents/Profiler_agent/.ipynb_checkpoints

Browse files
ASP/qwen_agents/Profiler_agent/.ipynb_checkpoints/OUTPUT-checkpoint.md DELETED
@@ -1,39 +0,0 @@
1
- # Output Formatting
2
-
3
- Write your response as a Markdown report with these sections, in order:
4
-
5
- ## Crime Scene Report
6
- Describe what is visible across the case images: setting, victim positioning
7
- (if applicable), signs of planning vs. improvisation, anything inconsistent
8
- with a normal/expected version of the scene (e.g. a living room, park, or
9
- other setting), and any apparent staging, concealment, or lack thereof.
10
-
11
- ## Victimology
12
- What the images/notes suggest about victim selection, means of access, and
13
- relationship (if any) between offender and victim.
14
-
15
- ## Organization Level
16
- Classify the scene as **Organized**, **Disorganized**, or **Mixed**, based on
17
- the classic distinction:
18
- - *Organized*: planning evident, victim/scene control, concealment or careful
19
- disposal, deliberate cleanup — associated with higher intelligence and
20
- social integration.
21
- - *Disorganized*: little planning, scene left as-is, minimal concealment,
22
- higher apparent anxiety/impulsivity.
23
- - *Mixed*: elements of both.
24
-
25
- ## Offender Typology
26
- Using the classification frameworks in `REFERENCE.md` (Palermo/Mastronardi's
27
- typology and the Holmes & Holmes typology), identify which types best fit
28
- the evidence; for example (not exhaustive): Visionary, Missionary (and its
29
- subtypes), Hedonistic, Power/Control-oriented, or Lust-oriented, including
30
- any atypical variant if applicable. You do not need to force a single type
31
- if the evidence is ambiguous — say so and name the top 1-2 candidates.
32
-
33
- ## Rationale & Confidence
34
- 2-4 sentences tying your classification directly to specific evidence from
35
- the Forensic agent's report and the case images. State your confidence
36
- explicitly (Low / Medium / High) based on how much evidence is actually
37
- available. This is an analytical classification for casework support, not a
38
- certainty — frame it as the most likely interpretation of the evidence, not
39
- a definitive claim.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ASP/qwen_agents/Profiler_agent/.ipynb_checkpoints/__init__-checkpoint.py DELETED
File without changes
ASP/qwen_agents/Profiler_agent/.ipynb_checkpoints/image_viewer_mcp-checkpoint.py DELETED
@@ -1,174 +0,0 @@
1
- import io
2
- import os
3
- import re
4
- import sys
5
- from dataclasses import dataclass, field
6
- from pathlib import Path
7
- from fastmcp import FastMCP
8
- from fastmcp.exceptions import ToolError
9
- from fastmcp.utilities.types import Image
10
- from PIL import Image as PILImage
11
-
12
- DEFAULT_SKILL_DIR = Path(__file__).parent / ".agents" / "skills" / "criminal-behavioral-analysis"
13
- IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".webp"}
14
- IMAGE_MARKDOWN = re.compile(r"!\[(?P<alt>.*?)\]\((?P<path>.*?)\)")
15
- ADVANCED_HEADING = "# Advanced version"
16
-
17
-
18
- @dataclass
19
- class Section:
20
- number: int
21
- label: str
22
- title: str
23
- images: list[Path] = field(default_factory=list)
24
-
25
-
26
- class AdvancedImageLibrary:
27
- def __init__(self, skill_dir: Path, max_side: int = 1600, max_per_call: int = 4):
28
- self.skill_dir = skill_dir.resolve()
29
- self.long_dir = self.skill_dir / "assets" / "long"
30
- self.reference_file = self.skill_dir / "references" / "REFERENCE.md"
31
- self.max_side = max_side
32
- self.max_per_call = max_per_call
33
- titles, self.descriptions = self.read_reference()
34
- self.sections = self.read_sections(titles)
35
-
36
- def read_reference(self) -> tuple[list[str], dict[Path, str]]:
37
- titles: list[str] = []
38
- alts: dict[Path, str] = {}
39
- if not self.reference_file.exists():
40
- return titles, alts
41
- in_advanced = False
42
- for line in self.reference_file.read_text(encoding="utf-8").splitlines():
43
- if line.strip() == ADVANCED_HEADING:
44
- in_advanced = True
45
- elif in_advanced and line.startswith("## "):
46
- titles.append(line[3:].strip())
47
- elif in_advanced:
48
- for match in IMAGE_MARKDOWN.finditer(line):
49
- alts[(self.skill_dir / match["path"]).resolve()] = match["alt"].strip()
50
- return titles, alts
51
-
52
- def read_sections(self, titles: list[str]) -> list[Section]:
53
- folders = sorted(
54
- (d for d in self.long_dir.iterdir() if d.is_dir()),
55
- key=lambda d: [int(t) if t.isdigit() else t for t in re.split(r"(\d+)", d.name)],
56
- ) if self.long_dir.is_dir() else []
57
- use_titles = len(titles) == len(folders)
58
- sections = []
59
- for index, folder in enumerate(folders, start=1):
60
- images = sorted(
61
- (p for p in folder.iterdir() if p.suffix.lower() in IMAGE_SUFFIXES),
62
- key=lambda p: [int(t) if t.isdigit() else t for t in re.split(r"(\d+)", p.name)],
63
- )
64
- sections.append(Section(
65
- number=index,
66
- label=folder.name,
67
- title=titles[index - 1] if use_titles else folder.name,
68
- images=images,
69
- ))
70
- return sections
71
-
72
- @property
73
- def image_count(self) -> int:
74
- return sum(len(s.images) for s in self.sections)
75
-
76
- def describe(self, path: Path) -> str:
77
- return self.descriptions.get(path.resolve(), "")
78
-
79
- def relative(self, path: Path) -> str:
80
- return path.resolve().relative_to(self.long_dir).as_posix()
81
-
82
- def find_section(self, query: str) -> Section:
83
- wanted = str(query).strip().lower()
84
- if wanted.isdigit():
85
- for section in self.sections:
86
- if section.number == int(wanted):
87
- return section
88
- for section in self.sections:
89
- if wanted and (wanted in section.title.lower() or wanted in section.label.lower()):
90
- return section
91
- choices = "; ".join(f"{s.number}: {s.title}" for s in self.sections)
92
- raise ToolError(f"No section matches {query!r}. Available sections: {choices}")
93
-
94
- def resolve_image(self, relative_path: str) -> Path:
95
- candidate = (self.long_dir / relative_path).resolve()
96
- if not candidate.is_relative_to(self.long_dir) or candidate.suffix.lower() not in IMAGE_SUFFIXES:
97
- raise ToolError(f"{relative_path!r} is not an Advanced version image")
98
- if not candidate.is_file():
99
- raise ToolError(f"{relative_path!r} does not exist; call list_sections to see valid paths")
100
- return candidate
101
-
102
- def load(self, path: Path) -> Image:
103
- with PILImage.open(path) as picture:
104
- if max(picture.size) <= self.max_side:
105
- return Image(path=str(path))
106
- picture = picture.convert("RGB")
107
- picture.thumbnail((self.max_side, self.max_side), PILImage.Resampling.LANCZOS)
108
- buffer = io.BytesIO()
109
- picture.save(buffer, format="JPEG", quality=88)
110
- return Image(data=buffer.getvalue(), format="jpeg")
111
-
112
-
113
- def build_server(library: AdvancedImageLibrary) -> FastMCP:
114
- server = FastMCP("image-viewer")
115
-
116
- @server.tool
117
- def list_sections() -> list[dict]:
118
- return [
119
- {
120
- "section": s.number,
121
- "title": s.title,
122
- "image_count": len(s.images),
123
- "images": [
124
- {"path": library.relative(p), "description": library.describe(p)}
125
- for p in s.images
126
- ],
127
- }
128
- for s in library.sections
129
- ]
130
-
131
- @server.tool
132
- def view_section(section: str, offset: int = 0) -> list:
133
- chosen = library.find_section(section)
134
- start = max(int(offset), 0)
135
- batch = chosen.images[start:start + library.max_per_call]
136
- if not batch:
137
- raise ToolError(
138
- f"Section {chosen.number} has {len(chosen.images)} images; offset {start} is past the end"
139
- )
140
- end = start + len(batch)
141
- header = f"Section {chosen.number}: {chosen.title} - images {start + 1} to {end} of {len(chosen.images)}"
142
- footer = (
143
- f"{len(chosen.images) - end} more image(s) in this section; call view_section again with offset={end}."
144
- if end < len(chosen.images) else "End of section."
145
- )
146
- content: list = [header]
147
- for path in batch:
148
- description = library.describe(path)
149
- content.append(f"{library.relative(path)}" + (f" - {description}" if description else ""))
150
- content.append(library.load(path))
151
- content.append(footer)
152
- return content
153
-
154
- @server.tool
155
- def view_image(path: str) -> list:
156
- resolved = library.resolve_image(path)
157
- description = library.describe(resolved)
158
- return [library.relative(resolved) + (f" - {description}" if description else ""), library.load(resolved)]
159
-
160
- return server
161
-
162
-
163
- def main() -> None:
164
- skill_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_SKILL_DIR
165
- library = AdvancedImageLibrary(
166
- skill_dir,
167
- max_side=int(os.getenv("IMAGE_VIEWER_MAX_SIDE", "1600")),
168
- max_per_call=int(os.getenv("IMAGE_VIEWER_MAX_PER_CALL", "4")),
169
- )
170
- build_server(library).run(transport="stdio", show_banner=False)
171
-
172
-
173
- if __name__ == "__main__":
174
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ASP/qwen_agents/Profiler_agent/.ipynb_checkpoints/profiler_agent-checkpoint.py DELETED
@@ -1,141 +0,0 @@
1
- import logging
2
- import os
3
- import sys
4
- from pathlib import Path
5
- from fastmcp.client import Client as FastMCPClient
6
- from fastmcp.client.transports import StdioTransport
7
- from pydantic_ai import Agent, RunContext
8
- from pydantic_ai.mcp import MCPToolset
9
- from qwen_agents.model_utils import QwenVLModel
10
- from reference_version import ADVANCED, BASIC, version_from_messages
11
-
12
- log = logging.getLogger(__name__)
13
-
14
- PROFILER_MODEL_ID = os.getenv(
15
- "PROFILER_MODEL_ID", "Kizzington/Qwen3-VL-8B-Thinking-heretic"
16
- )
17
-
18
- PROFILER_LOAD_IN_4BIT = os.getenv("PROFILER_LOAD_IN_4BIT", "false").strip().lower() not in (
19
- "false", "0", "no"
20
- )
21
-
22
- SKILLS_DIR = Path(__file__).parent / ".agents" / "skills"
23
- CBA_SKILL_DIR = SKILLS_DIR / "criminal-behavioral-analysis"
24
- PROFILER_SKILL = CBA_SKILL_DIR / "SKILL.md"
25
-
26
- SHORT_REFERENCE_IMAGES = sorted(
27
- (CBA_SKILL_DIR / "assets" / "short").glob("*.jpg")
28
- )
29
- ADVANCED_IMAGE_COUNT = len(list((CBA_SKILL_DIR / "assets" / "long").rglob("*.jpg")))
30
-
31
- IMAGE_VIEWER_SERVER = Path(__file__).parent / "image_viewer_mcp.py"
32
-
33
- skills_client = FastMCPClient(
34
- StdioTransport(
35
- command=sys.executable,
36
- args=[str(IMAGE_VIEWER_SERVER), str(CBA_SKILL_DIR)],
37
- env={**os.environ}
38
- ),
39
- init_timeout=60
40
- )
41
- ALLOWED_SKILL_TOOLS = {"list_sections", "view_section", "view_image"}
42
- skills_toolset = MCPToolset(skills_client).filtered(
43
- lambda ctx, tool_def: (
44
- tool_def.name in ALLOWED_SKILL_TOOLS and version_from_messages(ctx.messages) != BASIC
45
- )
46
- )
47
-
48
-
49
- def load_skill_text(path: Path) -> str:
50
- try:
51
- return path.read_text(encoding="utf-8")
52
- except FileNotFoundError as e:
53
- raise RuntimeError(
54
- f"Profiler agent's required skill file is missing: {path} "
55
- "— this file defines the agent's core workflow and output "
56
- "format; the agent cannot run correctly without it."
57
- ) from e
58
-
59
-
60
- SKILL_MD = load_skill_text(PROFILER_SKILL)
61
- OUTPUT_MD = load_skill_text(CBA_SKILL_DIR / "references" / "OUTPUT.md")
62
-
63
-
64
- profiler_agent: Agent[None, str] = Agent(
65
- model=QwenVLModel(
66
- PROFILER_MODEL_ID,
67
- processor_path="Qwen/Qwen3-VL-8B-Thinking",
68
- reference_images=SHORT_REFERENCE_IMAGES,
69
- temperature=0.3,
70
- max_new_tokens=12288,
71
- load_in_4bit=PROFILER_LOAD_IN_4BIT
72
- ).function_model(),
73
- system_prompt=(
74
- "You are a criminal behavioral profiler analyzing case images plus "
75
- "a Forensic agent's report (received via A2A).\n\n"
76
- "You are NOT deriving forensic conclusions yourself — use the "
77
- "Forensic report's findings as evidence for your profile. Do NOT "
78
- "just repeat or extend the Forensic report's own format (cause of "
79
- "death, manner of death, etc.) — your job is a DIFFERENT, "
80
- "behavioral-profiling report, as described in your skill file "
81
- "below.\n\n"
82
- "Note: some runs of this pipeline supply the Forensic report "
83
- "concurrently with RAG grounding lookup rather than waiting for "
84
- "it, so grounded reference material from forensic_knowledge/ may "
85
- "or may not be present below — proceed with whatever you're given "
86
- "and don't assume its absence means none exists.\n\n"
87
- "You have already been given several reference images (the 'Basic "
88
- "version' of your training material) summarizing established "
89
- "criminological classification frameworks for serial offenders, "
90
- "including:\n"
91
- " - Palermo/Mastronardi's typology (Visionary, Missionary [with "
92
- "subtypes: social protection, pseudo-religious, pseudo-political, "
93
- "pseudo-cultural, racial, sexological-moralist, of the social "
94
- "order], Hedonistic, Power/Control-oriented, Lust-oriented, plus "
95
- "atypical variants)\n"
96
- " - The Holmes & Holmes typology (male and female variants)\n"
97
- " - Organized vs. Disorganized vs. Mixed crime-scene classification\n\n"
98
- "--- Your skill file (criminal-behavioral-analysis/SKILL.md) ---\n"
99
- "This defines your workflow. It is included here directly so you "
100
- "always have it regardless of whether you call any tool — you may "
101
- "still optionally use the image viewer tools to go deeper into the "
102
- "Advanced version images as it describes below.\n\n"
103
- f"TOOL RULES: your only tools are list_sections, view_section and view_image, "
104
- f"and they only show the {ADVANCED_IMAGE_COUNT} Advanced version reference images. "
105
- "list_sections lists the sections with a description of each image; view_section "
106
- "returns the actual images of one section (a few at a time, use offset to page); "
107
- "view_image returns one image by the path list_sections gave you. The case images "
108
- "are ALREADY attached to this message: never try to open, locate or re-read them "
109
- "with a tool, and never invent file paths such as /data/.... If a tool returns an "
110
- "error, do NOT repeat the same call; continue with what you already have.\n\n"
111
- f"{SKILL_MD}\n\n"
112
- "--- Your required output format (references/OUTPUT.md), verbatim ---\n"
113
- "You MUST use exactly these Markdown sections, in this order:\n\n"
114
- f"{OUTPUT_MD}\n\n"
115
- "---\n\n"
116
- "CRITICAL — avoid speculation: base every claim on what is "
117
- "actually visible in the images or stated in the Forensic report. "
118
- "Never invent specific unsupported narrative details (e.g. a "
119
- "specific relationship between victim and offender, a specific "
120
- "motive scenario, or a named category of crime) unless the "
121
- "evidence in front of you actually supports it — if the evidence "
122
- "is weak or absent for a claim, say so explicitly rather than "
123
- "filling the gap with an invented specific."
124
- ),
125
- toolsets=[skills_toolset],
126
- retries=4
127
- )
128
-
129
-
130
- @profiler_agent.output_validator
131
- def warn_if_advanced_unused(ctx: RunContext[None], output: str) -> str:
132
- if version_from_messages(ctx.messages) == ADVANCED:
133
- viewed = any(
134
- getattr(part, "tool_name", None) in ALLOWED_SKILL_TOOLS
135
- for message in ctx.messages
136
- for part in getattr(message, "parts", ())
137
- if getattr(part, "part_kind", None) == "tool-call"
138
- )
139
- if not viewed:
140
- log.warning("Advanced reference version was selected but the model never viewed an Advanced page")
141
- return output